- commit
- 73084e69c56278b98438dc2121b53694da45d305
- parent
- 7b6e2c92a8bffb82f73f455cf4cb036db746e375
- author
- Dami <dami.etoile@gmail.com>
| A | openspec/ | 113 | +++++++++++++++++++++++++++ |
| A | openspec/ | 42 | ++++++++++ |
| A | openspec/ | 124 | ++++++++++++++++++++++++++++++ |
| A | openspec/ | 32 | ++++++++ |
@@ -0,0 +1,113 @@
1+## Context2+3+The engine is the authoritative simulator, but its direct level validator and4+the CommonJS validator currently perform overlapping work with different5+limits and error shapes. The engine accepts a level without a pack identity and6+accepts `U`/`D`/`L`/`R`/`W` automaton steps. `mods.js` requires stable pack level7+IDs, has bounded pack limits, and currently expects object-shaped automaton8+steps. `share.js` repeats normalization before invoking the mod validator.9+10+The project already has stable rules and sharing contracts. This change makes11+the data boundary explicit so gates, switches, FRAIL tiles, and future12+automata metadata have one place to enter the host without forcing the13+simulator to understand pack-service diagnostics.14+15+## Goals / Non-Goals16+17+**Goals:**18+19+- Establish a pure normalization result that does not mutate caller data,20+ execute code, access the catalog, or depend on DOM state.21+- Validate shared structural fields once, including coordinates, object kind,22+ token casing, object IDs, automata IDs, pattern actions, and supported23+ scalar metadata.24+- Keep context policy explicit: standalone engine levels may omit pack-only25+ identity fields, while pack validation still requires stable level IDs and26+ applies pack count and size limits.27+- Preserve actionable source paths and map one canonical issue code into the28+ engine's thrown errors and loader/share diagnostics.29+- Make canonical data deterministic for export, share encoding, catalog30+ registration, solver input, and snapshot initialization.31+32+**Non-Goals:**33+34+- Adding gates, switches, FRAIL behavior, new rule vocabulary, or mechanic35+ lifecycle state.36+- Changing current movement, rule precedence, automaton ordering, or solver37+ semantics.38+- Sandboxing trusted local CommonJS or allowing remote package resolution.39+- Removing the legacy shorthand in this change.40+41+## Decisions42+43+### One canonical normalization pass44+45+Add a data-only level boundary with an explicit profile for standalone engine46+levels and pack levels. It clones input first, validates the shared structural47+contract, then returns canonical data. Canonical objects always have an48+explicit `kind`; word `text`, object `noun`, automaton `subject` and `property`49+tokens are uppercase; and automaton patterns contain `{ action: 'move', dx,50+dy }` or `{ action: 'wait' }` records. Optional metadata is preserved only51+when it is part of the documented level schema.52+53+Pack-only rules such as a required lowercase level ID, pack level count, and54+pack source limits remain profile checks around the shared boundary. They may55+reject a canonical level, but they may not normalize it differently.56+57+### Legacy shorthand is input-only58+59+For the current compatibility window, the boundary accepts an omitted object60+`kind` (inferred from `text`), case-insensitive word and noun tokens, and the61+automaton strings `U`, `D`, `L`, `R`, and `W`. It materializes these into the62+canonical explicit shape before any caller sees the result. Unknown strings,63+mixed direction vectors, and unsupported action fields remain validation64+errors. New authored content and CommonJS exports should use canonical object65+steps; a later compatibility change may remove shorthand after a measured66+warning period.67+68+### Adapters own presentation of errors69+70+The boundary reports stable issue codes, paths, and values. `createGame()` maps71+one issue into its existing `TypeError` or `RangeError` wording with the level72+label. The loader maps issues to `levels[i]...` strings and `ModValidationError`73+without executing or registering invalid data. Shared decoding maps issues to74+its existing `Invalid shared pack` error. These adapters may add filename or75+pack context but may not reimplement validation.76+77+### Round trips prove the boundary78+79+Fixtures run the same authored level through direct engine creation, local80+CommonJS loading, CommonJS export, shared encoding/decoding, and engine81+creation from the decoded result. Tests compare canonical gameplay fields and82+issue codes, not incidental message punctuation. They also prove source data83+is not mutated and that malformed data fails before catalog installation.84+85+## Risks / Trade-offs86+87+- A stricter shared boundary could reject a fixture that only passed through a88+ permissive path. → Inventory current fixtures, preserve documented legacy89+ shorthands, and add adapters before deleting old checks.90+- Different pack and standalone limits could look like a validation split. →91+ Keep shared structural checks in one module and make profile limits explicit92+ in the public contract and tests.93+- Future mechanic metadata could reintroduce ad hoc fields. → Require each94+ new metadata family to register its schema with the same boundary before95+ entering engine state or sharing.96+97+## Migration Plan98+99+1. Add the boundary module and fixtures without changing gameplay semantics.100+2. Route direct engine, CommonJS, and share paths through it, keeping their101+ public error adapters and pack-only checks.102+3. Update canonical exports and shared payloads to emit explicit shapes.103+4. Run focused parity tests, full Node tests, the production build, world104+ solver, and browser import/share flows.105+5. Record any remaining unowned metadata fields before adding a mechanic106+ proposal that depends on them.107+108+## Open Questions109+110+- Should future legacy shorthand produce a nonblocking authoring warning111+ before it is removed?112+- Which profile should own limits for mechanic metadata once gates and113+ one-step tiles become authored content?@@ -0,0 +1,42 @@
1+## Why2+3+Level data currently crosses several validation boundaries with slightly4+different rules. `createGame()` infers object kinds and accepts legacy5+automaton direction strings, while the CommonJS loader validates a related6+shape and normalizes it later. Data-only sharing has another copy of the same7+normalization. This makes a level valid through one path but invalid through8+another, and makes future mechanics expensive to add safely.9+10+## What Changes11+12+- Define one pure, host-owned level normalization and validation boundary for13+ authored level data.14+- Make direct engine creation, trusted CommonJS loading, and data-only sharing15+ use that boundary before their own context-specific error adapters.16+- Preserve the current legacy level shorthand during a documented migration:17+ inferred `kind`, case-insensitive word tokens, and `U`/`D`/`L`/`R`/`W`18+ automaton steps are accepted at input and materialized as canonical data.19+- Add parity fixtures proving valid data, invalid data, legacy normalization,20+ and CommonJS/share round trips agree before play or installation.21+22+## Capabilities23+24+### New Capabilities25+26+- `level-schema-boundary`: canonical level normalization, validation, and27+ adapters for engine, CommonJS, and data-only sharing.28+29+### Modified Capabilities30+31+- `commonjs-mods`: use the canonical level boundary instead of maintaining a32+ second level validator.33+- `level-sharing`: validate and normalize shared levels through the same34+ boundary used by local CommonJS previews and direct engine creation.35+36+## Impact37+38+The implementation will add a small data/schema module and reduce duplicated39+level checks in `src/engine.js`, `src/mods.js`, and `src/share.js`. It will add40+focused tests for direct creation, local module loading, shared payloads, and41+export/import. Existing normalized pack fields, level IDs, rules semantics,42+and trusted-local CommonJS execution remain unchanged.@@ -0,0 +1,124 @@
1+## ADDED Requirements2+3+### Requirement: Level inputs SHALL cross one canonical normalization boundary4+5+The host SHALL expose one pure level normalization and validation boundary for6+standalone engine levels, trusted CommonJS levels, and data-only shared levels.7+The boundary SHALL clone caller data before normalization, SHALL not execute8+code or access presentation state, and SHALL return canonical level data with9+explicit object kinds, uppercase word and noun tokens, and explicit automaton10+action objects. A context profile MAY add pack identity and size limits, but it11+SHALL reuse the same structural checks and canonical output.12+13+#### Scenario: Direct and pack paths produce equivalent canonical data14+15+- **WHEN** the same valid level is passed to direct engine creation and to a16+ CommonJS pack preview17+- **THEN** both paths use equivalent canonical gameplay fields, including18+ inferred kinds, token casing, IDs, and automaton actions19+20+#### Scenario: Input is not mutated21+22+- **WHEN** a caller supplies lowercase tokens, omitted kinds, or shorthand23+ automaton steps and then mutates the original input after normalization24+- **THEN** the canonical level and the active game remain unchanged and the25+ original input retains its original values26+27+### Requirement: Legacy level shorthand SHALL be normalized at input28+29+During the compatibility window, the boundary SHALL accept omitted object30+`kind` when `text` or `noun` makes the kind unambiguous, case-insensitive word31+and noun tokens, and automaton pattern strings `U`, `D`, `L`, `R`, and `W`.32+Canonical output SHALL materialize these as `kind: 'word'` or `kind: 'object'`,33+uppercase tokens, and `{ action: 'move', dx, dy }` or `{ action: 'wait' }`.34+Unknown shorthand, non-cardinal vectors, and unsupported action fields SHALL35+remain errors. The boundary SHALL not emit new shorthand in CommonJS exports36+or shared payloads.37+38+#### Scenario: Legacy automaton shorthand survives a share round trip39+40+- **WHEN** a valid level uses `['L', 'W', 'R']` in an automaton pattern and is41+ exported to CommonJS or encoded as a shared pack42+- **THEN** loading or decoding succeeds, the normalized pattern contains three43+ explicit action objects, and a game created from the result has the same44+ next transitions as the source game45+46+#### Scenario: Ambiguous shorthand fails consistently47+48+- **WHEN** a level contains an unsupported pattern string or a diagonal49+ `{ action: 'move', dx: 1, dy: 1 }`50+- **THEN** direct engine creation, CommonJS preview, and shared decoding all51+ reject the same structural issue before play or installation52+53+### Requirement: Pack-only policy SHALL be an explicit adapter54+55+The pack profile SHALL require a stable lowercase level ID, enforce the56+documented pack-level and pack-count bounds, and retain duplicate-ID checks.57+The standalone engine profile MAY omit pack-only identity fields and SHALL not58+silently inherit pack limits. Pack policy errors SHALL preserve the canonical59+field path and SHALL occur before catalog registration.60+61+#### Scenario: A standalone fixture remains playable without a pack ID62+63+- **WHEN** a valid direct engine fixture has width, height, and objects but no64+ `id`65+- **THEN** direct game creation succeeds with canonical gameplay data66+67+#### Scenario: Pack identity is required before installation68+69+- **WHEN** a CommonJS pack contains a level without a stable lowercase `id`70+- **THEN** preview reports the level path, rejects the pack atomically, and71+ leaves existing catalog content unchanged72+73+### Requirement: Error adapters SHALL preserve canonical issue identity74+75+The shared boundary SHALL report stable issue codes and source paths. The76+engine adapter SHALL retain its level-labeled `TypeError`/`RangeError` style;77+the CommonJS adapter SHALL retain `ModValidationError` paths and source78+context; and the shared decoder SHALL retain its player-readable invalid-pack79+error. Adapters MAY add filename, pack, or level labels but SHALL not duplicate80+structural validation or alter issue severity.81+82+#### Scenario: One malformed object has equivalent diagnostics83+84+- **WHEN** `objects[0]` has an invalid coordinate type through all three input85+ paths86+- **THEN** each path reports the same structural issue code and object index,87+ while retaining its existing caller-specific error envelope88+89+### Requirement: Canonical data SHALL round-trip through sharing and export90+91+CommonJS export and data-only shared encoding SHALL serialize canonical level92+data only. Decoding or loading SHALL revalidate through the boundary before93+returning normalized data. A valid canonical level SHALL preserve gameplay94+fields through CommonJS export/import and share encode/decode, including95+automata metadata and optional authored level metadata.96+97+#### Scenario: Export/share parity preserves gameplay98+99+- **WHEN** a pack with words, objects, automata, and level metadata is exported100+ and shared, then imported through both supported paths101+- **THEN** both results compare equal on canonical gameplay data and create102+ games with matching initial snapshots and first accepted transitions103+104+#### Scenario: Invalid shared data cannot bypass the boundary105+106+- **WHEN** a shared payload contains malformed level data or unsupported107+ metadata108+- **THEN** decoding fails before catalog installation and does not execute109+ CommonJS or leave partial content registered110+111+### Requirement: Boundary parity SHALL be a release gate112+113+The project SHALL maintain focused fixtures covering canonical valid levels,114+legacy shorthand, invalid coordinates, duplicate IDs, invalid tokens,115+automaton action errors, and share/export round trips. The gate SHALL run the116+direct engine, CommonJS loader, and data-only sharing paths against those117+fixtures, followed by the full Node suite and production build.118+119+#### Scenario: Parity fixtures catch validator drift120+121+- **WHEN** one path accepts or normalizes a fixture differently from the122+ canonical boundary123+- **THEN** the focused parity test fails with the path and issue difference124+ before the change can be marked complete@@ -0,0 +1,32 @@
1+## 1. Canonical boundary2+3+- [ ] 1.1 Inventory current direct engine, CommonJS, share, catalog, and solver4+ level fields and write the canonical normalization API and context profiles.5+- [ ] 1.2 Add a pure level schema module with cloned canonical output, stable6+ issue codes/paths, explicit object kinds, uppercase tokens, and explicit7+ automaton actions.8+- [ ] 1.3 Preserve and test the documented legacy shorthand as input-only;9+ ensure export and shared encoding emit canonical action objects.10+- [ ] 1.4 Keep pack-only identity, count, and size limits as explicit profile11+ adapters around the shared structural validator.12+13+## 2. Integrations14+15+- [ ] 2.1 Route `createGame()` through the canonical boundary while preserving16+ its current level-labeled error types and standalone fixtures.17+- [ ] 2.2 Route CommonJS validation/normalization through the boundary while18+ preserving `ModValidationError`, source context, and atomic registration.19+- [ ] 2.3 Route CommonJS export and data-only share encode/decode through the20+ canonical boundary and preserve current schema/revision behavior.21+22+## 3. Verification23+24+- [ ] 3.1 Add parity fixtures for valid data, legacy shorthand, malformed25+ coordinates/tokens/actions, duplicate IDs, and source immutability.26+- [ ] 3.2 Add CommonJS and shared round-trip assertions for canonical gameplay27+ data, automata metadata, and first-transition parity.28+- [ ] 3.3 Run focused engine/mod/share tests, full Node tests, production build,29+ world solver, and browser import/share flows.30+- [ ] 3.4 Run strict OpenSpec validation, document any deferred metadata fields,31+ and archive this change only after all adapter and round-trip evidence is32+ complete.