Add ink integration UI and media playback
This commit is contained in:
+50
-15
@@ -56,6 +56,8 @@ This means:
|
||||
|
||||
All engines author tags using the same `key[value]` bracket convention. For Ink this maps directly to native `# key[value]` tags. For YAML and Z-Code the server synthesises equivalent tag objects.
|
||||
|
||||
Ink choice text uses square brackets for its own display/control syntax. For choice-local tags, the integration therefore also accepts the prototype form `# key: value` and normalises it to the same structured tag object. Prefer `# letter: o` and `# action: examine` on choices when inkjs treats bracketed tags as choice text.
|
||||
|
||||
| Tag | Scope | Meaning |
|
||||
|---|---|---|
|
||||
| `music[filename]` | paragraph or global | Start looping music track |
|
||||
@@ -70,6 +72,7 @@ All engines author tags using the same `key[value]` bracket convention. For Ink
|
||||
| `title[text]` | global (first turn) | Set document/story title |
|
||||
| `author[text]` | global (first turn) | Set byline |
|
||||
| `action[category]` | choice | Sort this choice into a named column |
|
||||
| `letter[x]` | choice | Reserve keyboard letter `x` for this choice |
|
||||
|
||||
### 1.3 Parsed Tag Object Shape
|
||||
|
||||
@@ -109,7 +112,7 @@ These work identically on all three servers:
|
||||
| Direction | Event | Payload |
|
||||
|---|---|---|
|
||||
| client → server | `gameApi` | `{ method, args }` → `respond(result)` |
|
||||
| server → client | `gameIntroduction` | `{ title, author, inputMode }` |
|
||||
| server → client | `narrativeResponse` | `TurnResult` |
|
||||
| server → client | `gameSaved` | `{ slot }` |
|
||||
| server → client | `gameLoaded` | `{ slot }` |
|
||||
| server → client | `error` | `{ message }` |
|
||||
@@ -128,6 +131,7 @@ This is the core change. Previously `narrativeResponse` carried `{ text, gameSta
|
||||
|
||||
```ts
|
||||
interface TurnResult {
|
||||
turnId: number; // Unique ascending id within the game session
|
||||
paragraphs: ParagraphResult[]; // Ordered list of story paragraphs this turn
|
||||
choices: ChoiceResult[]; // Available choices (empty in text-input mode)
|
||||
inputMode: 'choice' | 'text' | 'end';
|
||||
@@ -149,10 +153,11 @@ interface ChoiceResult {
|
||||
text: string; // Display text (SmartyPants applied)
|
||||
tags: StoryTag[]; // Per-choice tags, e.g. action[examine]
|
||||
category?: string; // Derived from action[...] tag for column grouping
|
||||
letter?: string; // Optional reserved keyboard letter, derived from letter[x]
|
||||
}
|
||||
```
|
||||
|
||||
The old `{ text }` field is removed. For backward compatibility during transition, the server may also emit a flattened `text` field containing all paragraph texts joined with newlines.
|
||||
The old flattened `{ text }` field is removed. Servers must emit `TurnResult` only.
|
||||
|
||||
### 2.3 `playerCommand` vs `chooseChoice`
|
||||
|
||||
@@ -245,6 +250,34 @@ async function handleGameApi(socket, method, args): Promise<object> {
|
||||
|
||||
## 5. Client-Side Changes
|
||||
|
||||
### 5.0 Choice UI Architecture
|
||||
|
||||
The first Ink integration should ship with the simplest useful choice UI:
|
||||
|
||||
- Display all available choices in one ordered list.
|
||||
- Ignore choice grouping tags visually for now.
|
||||
- Assign every visible choice a keyboard letter.
|
||||
- Allow at most 26 visible choices (`A` through `Z`), which is enough for the current interaction model.
|
||||
- Choices with explicit `letter[x]` tags reserve that letter first.
|
||||
- Remaining choices receive letters in ascending screen order, skipping letters already reserved by tags.
|
||||
- The chosen letter is shown in the UI and pressing that letter selects the same choice as clicking it.
|
||||
- Letter matching is case-insensitive.
|
||||
|
||||
The architecture must still be ready for later choice templates. The choice renderer should normalize choices into a presentation model:
|
||||
|
||||
```ts
|
||||
interface ChoicePresentation {
|
||||
index: number;
|
||||
text: string;
|
||||
tags: StoryTag[];
|
||||
category?: string;
|
||||
letter: string;
|
||||
templateCell: string; // initially always "default"
|
||||
}
|
||||
```
|
||||
|
||||
The first implemented template has exactly one full-size cell named `default`. It receives every choice whose tags do not match a registered template cell. A later template can register cells such as `examine`, `ask`, `inventory`, or `reflect`, and route choices by `action[...]`, `category`, or a future `cell[...]` tag without changing the server protocol.
|
||||
|
||||
### 5.1 Tag Event Dispatch
|
||||
|
||||
**Modified: `game-loop-module.js`** (or `socket-client-module.js`)
|
||||
@@ -288,12 +321,13 @@ Remove all tag-detection regex from the text stream. The module now only applies
|
||||
|
||||
**Effort: ~150 lines new**
|
||||
|
||||
Renders choice columns, exactly as in the prototype's `createChoiceContainer`. Registered in `module-registry.js`.
|
||||
Renders available choices from the canonical `TurnResult.choices` array. Registered in `module-registry.js`.
|
||||
|
||||
- Listens for a `story:choices` event (dispatched by `game-loop-module.js` from the `TurnResult.choices` array).
|
||||
- Groups choices by `category` field into named columns with localised headers.
|
||||
- Registers keyboard shortcuts (A/B/C... or 1/2/3... depending on whether choices are categorised).
|
||||
- Uses a template object with one initial full-size `default` cell.
|
||||
- Assigns keyboard letters from `letter[x]` tags first, then fills remaining choices with `A` through `Z` in visible order.
|
||||
- On click/keypress: calls `socketClient.sendChoice(index)` → `gameApi { method: 'chooseChoice', args: [index] }`.
|
||||
- Future grouped or column layouts should be implemented by adding more template cells and routing rules, not by changing the server protocol.
|
||||
|
||||
### 5.6 `ui-input-handler-module.js`
|
||||
|
||||
@@ -361,13 +395,14 @@ On `narrativeResponse`:
|
||||
|
||||
## 8. Recommended Implementation Order
|
||||
|
||||
1. Extract `server-base.ts`; verify YAML engine still works.
|
||||
2. Define `TurnResult` + `StoryTag` interfaces.
|
||||
3. Write `tag-parser.ts`; unit test it.
|
||||
4. Rewrite `GameRunner` to produce `TurnResult`; update `server.ts` to emit it.
|
||||
5. Update `game-loop-module.js` to consume `TurnResult` and dispatch tag events.
|
||||
6. Update `audio-manager-module.js` and `ui-display-handler-module.js` to listen for tag events.
|
||||
7. Remove tag parsing from `markup-parser-module.js`.
|
||||
8. Build `choice-display-module.js` and `ui-input-handler` mode switching.
|
||||
9. Build `InkEngine` and `server-ink.ts`; smoke-test with a compiled `.ink.json`.
|
||||
10. Add Z-Code server (see `zcode_inclusion.md`).
|
||||
1. Define `TurnResult` + `StoryTag` interfaces.
|
||||
2. Write `tag-parser.ts`; parse the single canonical tag shape `key[value](param)`.
|
||||
3. Update the client socket pipeline to consume canonical `TurnResult` objects only.
|
||||
4. Dispatch structured `story:tag`, `story:choices`, and `story:input-mode` events from the socket adapter.
|
||||
5. Add `choice-display-module.js` with the single-cell default template and letter assignment described in §5.0.
|
||||
6. Update `audio-manager-module.js` and `ui-display-handler-module.js` to listen for `story:tag` events by translating them into the current media/display paths.
|
||||
7. Convert Zork server emission from flattened text to canonical `TurnResult`.
|
||||
8. Convert YAML server introduction/responses to canonical `TurnResult`.
|
||||
9. Extract `server-base.ts`; verify YAML and Zork still work.
|
||||
10. Build `InkEngine` and `server-ink.ts`; smoke-test with a compiled `.ink.json`.
|
||||
11. Once all engines emit structured tags, remove structural tag parsing from `markup-parser-module.js`.
|
||||
|
||||
Reference in New Issue
Block a user