- commit
- dc618c760165e48ceffab664e08c9b4b0e54497c
- parent
- 2e6b487ada58c1e66789798dd6ef34385f61e63f
- author
- Dami <dami.etoile@gmail.com>
| M | openspec/ | 35 | ++++++++++++++++------------- |
| M | openspec/ | 8 | +++++-- |
| M | openspec/ | 36 | +++++++++++++++++++++--------- |
| M | openspec/ | 13 | +++++++---- |
@@ -50,22 +50,25 @@ joining core.
5050 5151 ## Manifest world metadata5252 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.53+`manifest.world` is optional. When present it is an object with these fields:54+55+- `glyph`: exactly one grapheme cluster, counted with `Intl.Segmenter` using56+ `granularity: 'grapheme'`, and no more than 16 UTF-16 code units. This permits57+ visible emoji such as `⚙️`, ZWJ sequences, and flags while restricting the58+ card to one glyph;59+- `tagline`: at most 80 characters, one source line with no newline, sentence60+ case, and no `·` (U+00B7). The diagnostic for the middle dot is explicit:61+ `Tagline can't use ·; use a comma or a dash.`;62+- `accent`: an optional token from `you`, `push`, `stop`, `win`, `sink`,63+ `defeat`, or `tick`.64+65+World identity is presentation metadata, so an invalid optional field produces66+a nonblocking W11 note and does not reject the pack. The world falls back to67+the first palette glyph, no tagline, and the default `--fg` path. Missing68+`manifest.world` uses the same defaults. Invalid gameplay fields continue to69+reject the complete pack atomically through the existing pack validator.70+Normalized metadata and notes are available through catalog pack records and71+pack-service views without changing share identity or level IDs.6972 7073 ## Progress and chapter unlocking7174 @@ -26,8 +26,10 @@ review.
2626 - Define per-world progress and active-world identity so completion counts and2727 unlock sequencing do not leak between chapters.2828 - 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.29+ allowed property-color `accent`) as a nonblocking W11 authoring note. An30+ invalid identity falls back to the first palette glyph, no tagline, and the31+ default foreground path while gameplay-field validation remains atomic.32+ Expose normalized metadata to INDEX, Workshop, and future world cards.3133 - Record the accepted world header and first-entry card contract as a3234 presentation seam: header data includes world number, name, per-world3335 completion, and world strip state; first entry and Workshop PLAY can request@@ -65,6 +67,8 @@ review.
6567 6668 - Affected data model: catalog pack records, normalized manifests, INDEX layout,6769 navigator state, and world-scoped progress queries.70+- Gameplay fields continue to use the existing atomic pack validation path;71+ optional world presentation metadata does not weaken those checks.6872 - Affected input paths: keyboard, gamepad, touch, and path-edge traversal.6973 - Affected presentation contract: world header/card request data and world strip7074 state. The follow-up implementation must preserve fixed integer board@@ -23,23 +23,37 @@ world identity from origin or a hard-coded pack ID list.
2323 - **THEN** replacement keeps the existing world position and the fork appends2424 as a new imported world without reordering other worlds2525 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.26+### Requirement: World identity metadata SHALL use nonblocking W11 validation27+An optional `manifest.world` SHALL accept a glyph that is exactly one grapheme28+cluster counted with `Intl.Segmenter` at `granularity: 'grapheme'` and no more29+than 16 UTF-16 code units. Its tagline SHALL be at most 80 characters, contain30+no newline, use sentence case, and contain no `·` (U+00B7). Its accent SHALL31+be one of `you`, `push`, `stop`, `win`, `sink`, `defeat`, or `tick`. An invalid32+optional identity field SHALL add a nonblocking W11 note and fall back to the33+first palette glyph, no tagline, and the default `--fg` path. Invalid gameplay34+fields SHALL continue to reject the complete pack atomically.3335 3436 #### Scenario: Valid world metadata is exposed3537 - **WHEN** a pack declares `{ glyph: '🌊', tagline: 'A wet proof.', accent: 'sink' }`3638 - **THEN** the normalized catalog world exposes those values with the pack's3739 stable ID, author, origin, and registration order3840 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 attempt41+#### Scenario: Invalid world metadata keeps the pack playable42+- **WHEN** a pack declares `accent: 'glow'`, a multi-grapheme glyph, or an43+ overlong tagline44+- **THEN** registration succeeds with a W11 note, uses the fallback world45+ identity, and keeps the pack's gameplay and active-level data available46+47+#### Scenario: World tagline reports the middle-dot correction clearly48+- **WHEN** a tagline contains `·`49+- **THEN** the pack receives a W11 note saying `Tagline can't use ·; use a50+ comma or a dash.` and the normalized world has no tagline51+52+#### Scenario: Gameplay validation remains atomic53+- **WHEN** a pack has valid or invalid `manifest.world` metadata but an invalid54+ gameplay field55+- **THEN** the pack is rejected by the existing gameplay validator and is not56+ added to the catalog4357 4458 #### Scenario: A legacy pack receives defaults4559 - **WHEN** an otherwise valid pack omits `manifest.world`@@ -3,11 +3,14 @@
33 - [ ] 1.1 Extend the normalized pack/catalog record with one world record per44 pack, including stable order, origin, author, level count, and per-world55 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,6+- [ ] 1.2 Validate `manifest.world.glyph` with `Intl.Segmenter` grapheme7+ counting and the 16 UTF-16-unit cap; validate tagline length, newlines,8+ sentence case, and U+00B7; validate `accent` against the closed token set,9+ including `tick`. Emit nonblocking W11 notes and normalized fallback metadata10+ for invalid optional identity fields.11+- [ ] 1.3 Add focused tests for valid metadata, grapheme and UTF-16 edge cases,12+ each invalid field, the exact middle-dot diagnostic, absent metadata,13+ gameplay-field rejection, replacement position, imported registration order,1114 fork append order, and removal of an imported world.1215 - [ ] 1.4 Add a pack-service/share view assertion that world metadata is exposed1316 without changing pack IDs, level IDs, or share collision behavior.