- commit
- 6d62db861631f8a63c1d05e26d94c58a88d625c3
- parent
- ae17de4c858beff29d544d3cee4f567e92b682db
- author
- Dami <dami.etoile@gmail.com>
| A | openspec/ | 2 | + |
| A | openspec/ | 120 | ++++++++++++++++++++++++++++++ |
| A | openspec/ | 81 | ++++++++++++++++++++ |
| A | openspec/ | 115 | +++++++++++++++++++++++++++++ |
| A | openspec/ | 63 | ++++++++++++++++ |
@@ -0,0 +1,120 @@
1+## World model2+3+Create one world record for each catalog pack, including the core pack:4+5+```js6+{7+ id: 'tidewater',8+ number: 4,9+ packId: 'tidewater',10+ name: 'Tidewater',11+ author: '...',12+ glyph: '🌊',13+ tagline: 'Everything floats until it does not.',14+ accent: 'sink',15+ origin: 'bundled',16+ registrationIndex: 3,17+ sheets: ['tidewater:sheet-1'],18+ levelCount: 9,19+ completedCount: 2,20+}21+```22+23+`number` is one-based in display and follows the final world order. The world24+record is derived from the catalog, not duplicated in level data. A pack may25+have multiple sheet records, but every sheet has exactly one `worldId`/`packId`.26+The core pack is world 1 and owns its own sheet range. No bundled pack is27+implicitly folded into the core world.28+29+The layout API should expose both `worlds` and `sheets`, plus lookup helpers for30+world by pack ID, sheet by ID, and node by global level ID. Each sheet keeps the31+existing fifteen-cell serpentine auto layout and links to the previous and next32+sheet in the same world. A world boundary is represented separately from an33+intra-world sheet boundary so callers can animate or announce it later.34+35+## Ordering and registration36+37+The catalog order is the source of truth:38+39+1. core;40+2. bundled packs in registration order;41+3. local and URL/imported packs in install order.42+43+Replacing a pack preserves its position. Forking an imported pack appends the44+fork as a new imported world. Removing an imported pack removes its world and45+its sheets while preserving the relative order of all remaining worlds.46+47+The layout must not infer world identity from `origin`, a hard-coded ID set, or48+the active level. This prevents a newly bundled or imported pack from silently49+joining core.50+51+## Manifest world metadata52+53+`manifest.world` is optional. When present it is an object with only these54+fields:55+56+- `glyph`: a non-empty string containing one display glyph, at most four Unicode57+ code points;58+- `tagline`: an optional non-empty string of at most 120 characters;59+- `accent`: an optional token from `you`, `push`, `stop`, `win`, `sink`, or60+ `defeat`.61+62+Unknown fields, invalid types, empty strings, an overlong tagline, an empty or63+ multi-glyph value, and an unsupported accent reject the complete pack before64+ catalog mutation. Missing fields normalize to `null`; missing `manifest.world`65+ normalizes to the default world presentation (first palette glyph, no66+ tagline, and the default foreground path). The normalized metadata is67+ available through catalog pack records and pack-service views without68+ changing share identity or level IDs.69+70+## Progress and chapter unlocking71+72+Solved state remains keyed by `{ packId, levelId }`. World completion is the73+ count of solved levels whose `packId` matches that world, over that world's74+ level count. Unlocking is evaluated in world order within each world: its75+ opener is always available and each later proof requires the previous proof in76+ the same world. Solving or selecting a proof in one world cannot unlock a77+ proof in another world.78+79+The active INDEX state must carry `worldId`, `worldIndex`, `sheetId`, and80+`nodeId`. World switching selects the first available node in the destination81+world, preserving that world's current cursor when one exists. If no proof in a82+world is available, its opener remains the selectable boundary target and the83+existing locked-node indication applies.84+85+## Navigation contract86+87+`Q` and `E` switch one world left or right. Gamepad LB/RB map to the same88+actions. Touch uses a two-finger horizontal swipe and must resolve one switch89+per gesture. Switching at the first/last world is a no-op with the existing90+blocked-navigation feedback and does not alter progress or the active proof.91+92+Walking from the terminal node of a world's final sheet in its path-forward93+direction enters the first node of the next world. Walking backward from the94+first node enters the final node of the previous world. There is no wraparound.95+Intra-world sheet crossings continue to use the current path boundary behavior96+and must not be mistaken for a world change.97+98+## Presentation seam99+100+The INDEX scene receives a pure view model containing:101+102+- `worldNumber`, `worldCount`, `worldName`, `worldGlyph`, `worldTagline`,103+ `worldAccent`;104+- `completedCount`, `levelCount`, and the current world-strip position;105+- `enteredWorldBefore`, `worldCardRequested`, and `cardOrigin` (`index` or106+ `workshop`).107+108+The first entry into a world and Workshop PLAY request a card once per session109+and then settle on the world sheet. The card displays the glyph, name, tagline,110+author, and an ENTER action. The follow-up presentation change owns its exact111+geometry, copy fitting, and 280ms camera ease; the engine must only emit stable112+world transition and card-request state.113+114+## Compatibility and migration115+116+Existing progress keys remain valid. Catalog callers that only ask for flat117+levels continue to receive the same ordered level records. The layout migration118+must update tests and callers that assumed `core`, `garden-basics`, and119+`word-lab` were one sheet range. Imported packs without `manifest.world` remain120+valid and receive normalized default metadata.@@ -0,0 +1,81 @@
1+## Why2+3+The accepted D20 design makes each registered pack a walkable world, but the4+current INDEX model still treats bundled packs as one `core` sheet and has no5+world identity or world-to-world navigation contract. That leaves registration6+order, chapter progress, boundary movement, and imported pack metadata to7+incidental catalog behavior.8+9+The short-term goal is a stable world model and navigation seam that the10+renderer can consume. It keeps the accepted world card and header treatment11+explicit while leaving their final visual implementation to a follow-up design12+review.13+14+## What Changes15+16+- Give every registered pack one world identity and an ordered chapter range of17+ INDEX sheets. A world with more than fifteen proofs continues onto additional18+ sheets belonging to that same world; worlds never share a sheet.19+- Preserve deterministic registration order: core first, then bundled packs in20+ registration order, then imported packs in install order. Remove the current21+ special case that groups bundled packs under `core`.22+- Define world-aware INDEX navigation: `Q`/`E` (gamepad LB/RB and the touch23+ equivalent) switch worlds, and walking off a world's terminal path enters the24+ adjacent world's opener. Boundary movement is clamped at the first and last25+ world.26+- Define per-world progress and active-world identity so completion counts and27+ unlock sequencing do not leak between chapters.28+- Validate optional `manifest.world` metadata (`glyph`, `tagline`, and the29+ allowed property-color `accent`) atomically with the rest of a pack. Expose30+ normalized metadata to INDEX, Workshop, and future world cards.31+- Record the accepted world header and first-entry card contract as a32+ presentation seam: header data includes world number, name, per-world33+ completion, and world strip state; first entry and Workshop PLAY can request34+ a world card. Pixel layout, animation timing, and final copy fitting remain35+ follow-up presentation work.36+- Clarify that the D20 Worlds section supersedes the earlier INDEX paragraph37+ that says built-ins share sheet 1. The rule is now one sheet range per pack,38+ including built-in packs.39+40+## Capabilities41+42+### New Capabilities43+44+- `world-index-navigation`: world identity, pack-owned INDEX sheet ranges,45+ deterministic world order, chapter navigation, progress scoping, and the46+ presentation data seam for D20.47+48+### Modified Capabilities49+50+- `level-packs`: validated world identity and registration order become part of51+ the pack handoff contract.52+- `game-presentation`: INDEX consumes a world-aware layout and exposes world53+ switching and first-entry card requests without changing simulation state.54+55+## Dependencies56+57+- `add-world-authoring-workflow` supplies the reviewed bundled-pack handoff,58+ stable pack IDs, registration order, and solver evidence.59+- `fix-rule-precedence-and-reading` remains an engine/content prerequisite for60+ world packs, but this change does not alter rule evaluation.61+- The existing catalog and progress stores remain the persistence boundary;62+ this change adds world/chapter keys without changing solved proof IDs.63+64+## Impact65+66+- Affected data model: catalog pack records, normalized manifests, INDEX layout,67+ navigator state, and world-scoped progress queries.68+- Affected input paths: keyboard, gamepad, touch, and path-edge traversal.69+- Affected presentation contract: world header/card request data and world strip70+ state. The follow-up implementation must preserve fixed integer board71+ geometry and existing hit targets.72+- No new rule vocabulary, movement semantics, solver behavior, or CommonJS73+ execution API is introduced.74+75+## Deliberately Deferred76+77+- The exact canvas composition, font fitting, iris timing, and copy for the78+ world card/header are left for a separate designer-reviewed implementation79+ change. This proposal defines the data and transition seam they consume.80+- World-specific custom map layouts remain an authoring extension after the81+ default auto layout is correct.@@ -0,0 +1,115 @@
1+## ADDED Requirements2+3+### Requirement: Each registered pack SHALL own a world and INDEX sheet range4+The catalog and INDEX layout SHALL create one world record and one or more5+pack-owned sheets for every registered pack, including core. Sheets SHALL hold6+at most fifteen nodes, and a world with more than fifteen proofs SHALL continue7+onto another sheet in the same world. World order SHALL be core first, then8+bundled registration order, then imported install order. Layout SHALL NOT infer9+world identity from origin or a hard-coded pack ID list.10+11+#### Scenario: Bundled packs remain separate worlds12+- **WHEN** core, garden-basics, and word-lab are registered in that order13+- **THEN** the layout exposes three worlds, each pack's nodes stay in its own14+ sheet range, and no garden-basics or word-lab node has `worldId: core`15+16+#### Scenario: A long pack continues within its own world17+- **WHEN** an imported pack has sixteen ordered proofs18+- **THEN** it receives two sheets with the same world ID, fifteen nodes on the19+ first sheet, and one node on the second sheet20+21+#### Scenario: Registration order survives replacement and forking22+- **WHEN** a bundled pack is replaced or an imported pack is forked23+- **THEN** replacement keeps the existing world position and the fork appends24+ as a new imported world without reordering other worlds25+26+### Requirement: World identity metadata SHALL validate atomically27+An optional `manifest.world` SHALL accept only a non-empty glyph of at most28+four Unicode code points, an optional non-empty tagline of at most 12029+characters, and an optional accent from `you`, `push`, `stop`, `win`, `sink`,30+or `defeat`. Invalid types, empty values, overlong values, unsupported accents,31+or unknown fields SHALL reject the complete pack before catalog mutation.32+Missing metadata SHALL normalize to the documented default presentation.33+34+#### Scenario: Valid world metadata is exposed35+- **WHEN** a pack declares `{ glyph: '🌊', tagline: 'A wet proof.', accent: 'sink' }`36+- **THEN** the normalized catalog world exposes those values with the pack's37+ stable ID, author, origin, and registration order38+39+#### Scenario: Invalid world metadata preserves the catalog40+- **WHEN** a pack declares `accent: 'glow'` or an overlong tagline41+- **THEN** registration fails with a field-specific validation error and the42+ catalog contains the same worlds and active level as before the attempt43+44+#### Scenario: A legacy pack receives defaults45+- **WHEN** an otherwise valid pack omits `manifest.world`46+- **THEN** registration succeeds and its world metadata uses the default glyph,47+ no tagline, and the default foreground path accent48+49+### Requirement: INDEX progress SHALL be scoped per world50+The INDEX view model SHALL expose the active world ID/number, world name, world51+completion count, total proof count, and current world-strip position. Solved52+state SHALL remain keyed by `{ packId, levelId }`. The opener of every world53+SHALL be available, later proofs SHALL unlock in order within that world, and a54+proof solved in one world SHALL not unlock or count toward another world.55+56+#### Scenario: Completion counts do not leak between worlds57+- **WHEN** one proof is solved in world `alpha` and none is solved in world `beta`58+- **THEN** alpha reports `1 / alphaTotal`, beta reports `0 / betaTotal`, and59+ beta's second proof remains locked behind beta's opener60+61+#### Scenario: Entering a world selects its local cursor62+- **WHEN** the player leaves world `alpha` after selecting its third proof and63+ later returns64+- **THEN** the INDEX restores alpha's saved cursor when it still exists, or65+ selects alpha's first available node, without changing the active proof in66+ another world67+68+### Requirement: INDEX SHALL navigate between worlds without wraparound69+The navigator SHALL map Q/E, gamepad LB/RB, and one two-finger horizontal swipe70+per gesture to one-world-left/right actions. Walking forward from a world's71+terminal path node SHALL enter the next world's first node; walking backward72+from the first node SHALL enter the previous world's terminal node. The first73+and last world boundaries SHALL be no-ops with existing blocked-navigation74+feedback. Intra-world sheet boundaries SHALL remain distinct from world75+boundaries.76+77+#### Scenario: Q and E switch one adjacent world78+- **WHEN** the active world is the second of three and the player presses Q79+ once, then E once80+- **THEN** the navigator enters world one, then world two, with one transition81+ per input and no proof move recorded82+83+#### Scenario: Outer boundaries do not wrap84+- **WHEN** the player presses Q in world one or E in world three85+- **THEN** the world and active proof remain unchanged and the navigator returns86+ a blocked-navigation indication87+88+#### Scenario: Path edges cross worlds in travel direction89+- **WHEN** the player walks right off the final path node of world one90+- **THEN** the cursor enters world two's first node; walking left from world91+ two's first node returns to world one's final node, without wrapping past the92+ first or last world93+94+### Requirement: INDEX SHALL provide a stable world presentation seam95+The INDEX scene SHALL consume a pure world view model containing world number,96+world count, name, glyph, tagline, accent, per-world completion, and strip97+position. The first entry into a world and Workshop PLAY SHALL emit a98+world-card request with origin `index` or `workshop`, at most once per world per99+session. The seam SHALL not change integer board coordinates or simulation100+state.101+102+#### Scenario: First entry requests a world card103+- **WHEN** the player enters an unvisited world from INDEX104+- **THEN** the scene emits one `world-card` request containing that world's105+ normalized identity and `origin: index` before settling on its sheet106+107+#### Scenario: Workshop PLAY requests the same card seam108+- **WHEN** the player chooses PLAY for an unvisited pack in Workshop109+- **THEN** the scene emits one `world-card` request with `origin: workshop`,110+ preserving the pack's first node and current simulation state111+112+#### Scenario: Re-entry does not replay the card113+- **WHEN** the player returns to a world whose card request was already settled114+ during the current session115+- **THEN** the world header view model updates without another card request@@ -0,0 +1,63 @@
1+## 1. World identity and pack validation2+3+- [ ] 1.1 Extend the normalized pack/catalog record with one world record per4+ pack, including stable order, origin, author, level count, and per-world5+ completion counts.6+- [ ] 1.2 Validate `manifest.world.glyph`, `tagline`, and `accent` atomically;7+ normalize missing metadata to documented defaults and preserve the existing8+ pack when validation fails.9+- [ ] 1.3 Add focused tests for valid metadata, each invalid field, unknown10+ fields, absent metadata, replacement position, imported registration order,11+ fork append order, and removal of an imported world.12+- [ ] 1.4 Add a pack-service/share view assertion that world metadata is exposed13+ without changing pack IDs, level IDs, or share collision behavior.14+15+## 2. World-owned INDEX layout16+17+- [ ] 2.1 Remove origin/ID-based bundled grouping from `src/scenes/map.js` and18+ create one chapter group and sheet range for every pack, including core.19+- [ ] 2.2 Expose `worlds`, `worldId`, and world-local sheet links while keeping20+ existing node coordinates, IDs, and fifteen-cell serpentine layout stable.21+- [ ] 2.3 Add tests for a one-sheet world, a multi-sheet world, sheet boundaries,22+ world boundaries, empty packs, and deterministic registration order.23+- [ ] 2.4 Update layout callers and accessibility text to use the active world24+ identity rather than deriving world names from the active level.25+26+## 3. World navigation and progress27+28+- [ ] 3.1 Add Q/E, gamepad LB/RB, and one-per-gesture two-finger swipe actions29+ to the INDEX navigator with no-op feedback at the outer boundaries.30+- [ ] 3.2 Make path traversal cross adjacent world boundaries only at the31+ terminal/initial node in the corresponding travel direction, without32+ wraparound or accidental jumps between nearby cells.33+- [ ] 3.3 Preserve a cursor per world, choose that cursor or the first available34+ node on entry, and keep proof unlocking and completion counts scoped to the35+ active pack ID.36+- [ ] 3.4 Add focused navigator/progress tests for Q/E, edge traversal,37+ boundary no-ops, locked openers, solved counts, undo/restart isolation, and38+ removal of the active imported world.39+40+## 4. Presentation handoff seam41+42+- [ ] 4.1 Expose the pure world header/strip view model and stable transition43+ events without changing canvas grid geometry or simulation state.44+- [ ] 4.2 Emit a first-entry world-card request and a Workshop PLAY card origin;45+ deduplicate repeated requests within a session and preserve the active node.46+- [ ] 4.3 Add accessible text coverage for world number/name, per-world47+ completion, adjacent-world actions, and card ENTER/return state.48+- [ ] 4.4 Leave final card/header geometry, font fitting, iris animation, and49+ copy to a separate designer-reviewed presentation change; post the seam50+ evidence to `docs/design-inbox.md` before implementing that follow-up.51+52+## 5. Verification and handoff53+54+- [ ] 5.1 Run the focused catalog, map, navigator, and progress tests.55+- [ ] 5.2 Run the complete Node suite, bundled-world solver gate, production56+ build, and desktop/mobile browser checks for INDEX, world switching, locked57+ nodes, Workshop PLAY, and share import.58+- [ ] 5.3 Confirm the map implementation no longer relies on the old paragraph59+ that grouped built-ins on sheet 1; update the live `game-presentation` and60+ `level-packs` specs only when implementation is complete.61+- [ ] 5.4 Post navigation and metadata screenshots/logs to both team inboxes,62+ obtain designer review for the presentation seam, and record the final63+ world order and pack validation result before archiving this change.