clone

a browser grid puzzle game where you push words around to rewrite the rules of each level

main
miau-is-true / openspec / changes / archive / 2026-09-24-consolidate-level-schema-boundary / specs / level-schema-boundary / spec.md
spec.md 5.9K source

ADDED Requirements

Requirement: Level inputs SHALL cross one canonical normalization boundary

The host SHALL expose one pure level normalization and validation boundary for standalone engine levels, trusted CommonJS levels, and data-only shared levels. The boundary SHALL clone caller data before normalization, SHALL not execute code or access presentation state, and SHALL return canonical level data with explicit object kinds, uppercase word and noun tokens, and explicit automaton action objects. A context profile MAY add pack identity and size limits, but it SHALL reuse the same structural checks and canonical output.

Scenario: Direct and pack paths produce equivalent canonical data

  • WHEN the same valid level is passed to direct engine creation and to a CommonJS pack preview
  • THEN both paths use equivalent canonical gameplay fields, including inferred kinds, token casing, IDs, and automaton actions

Scenario: Input is not mutated

  • WHEN a caller supplies lowercase tokens, omitted kinds, or shorthand automaton steps and then mutates the original input after normalization
  • THEN the canonical level and the active game remain unchanged and the original input retains its original values

Requirement: Legacy level shorthand SHALL be normalized at input

During the compatibility window, the boundary SHALL accept omitted object kind when text or noun makes the kind unambiguous, case-insensitive word and noun tokens, and automaton pattern strings U, D, L, R, and W. Canonical output SHALL materialize these as kind: 'word' or kind: 'object', uppercase tokens, and { action: 'move', dx, dy } or { action: 'wait' }. Unknown shorthand, non-cardinal vectors, and unsupported action fields SHALL remain errors. The boundary SHALL not emit new shorthand in CommonJS exports or shared payloads.

Scenario: Legacy automaton shorthand survives a share round trip

  • WHEN a valid level uses ['L', 'W', 'R'] in an automaton pattern and is exported to CommonJS or encoded as a shared pack
  • THEN loading or decoding succeeds, the normalized pattern contains three explicit action objects, and a game created from the result has the same next transitions as the source game

Scenario: Ambiguous shorthand fails consistently

  • WHEN a level contains an unsupported pattern string or a diagonal { action: 'move', dx: 1, dy: 1 }
  • THEN direct engine creation, CommonJS preview, and shared decoding all reject the same structural issue before play or installation

Requirement: Pack-only policy SHALL be an explicit adapter

The pack profile SHALL require a stable lowercase level ID, enforce the documented pack-level and pack-count bounds, and retain duplicate-ID checks. The standalone engine profile MAY omit pack-only identity fields and SHALL not silently inherit pack limits. Pack policy errors SHALL preserve the canonical field path and SHALL occur before catalog registration.

Scenario: A standalone fixture remains playable without a pack ID

  • WHEN a valid direct engine fixture has width, height, and objects but no id
  • THEN direct game creation succeeds with canonical gameplay data

Scenario: Pack identity is required before installation

  • WHEN a CommonJS pack contains a level without a stable lowercase id
  • THEN preview reports the level path, rejects the pack atomically, and leaves existing catalog content unchanged

Requirement: Error adapters SHALL preserve canonical issue identity

The shared boundary SHALL report stable issue codes and source paths. The engine adapter SHALL retain its level-labeled TypeError/RangeError style; the CommonJS adapter SHALL retain ModValidationError paths and source context; and the shared decoder SHALL retain its player-readable invalid-pack error. Adapters MAY add filename, pack, or level labels but SHALL not duplicate structural validation or alter issue severity.

Scenario: One malformed object has equivalent diagnostics

  • WHEN objects[0] has an invalid coordinate type through all three input paths
  • THEN each path reports the same structural issue code and object index, while retaining its existing caller-specific error envelope

Requirement: Canonical data SHALL round-trip through sharing and export

CommonJS export and data-only shared encoding SHALL serialize canonical level data only. Decoding or loading SHALL revalidate through the boundary before returning normalized data. A valid canonical level SHALL preserve gameplay fields through CommonJS export/import and share encode/decode, including automata metadata and optional authored level metadata.

Scenario: Export/share parity preserves gameplay

  • WHEN a pack with words, objects, automata, and level metadata is exported and shared, then imported through both supported paths
  • THEN both results compare equal on canonical gameplay data and create games with matching initial snapshots and first accepted transitions

Scenario: Invalid shared data cannot bypass the boundary

  • WHEN a shared payload contains malformed level data or unsupported metadata
  • THEN decoding fails before catalog installation and does not execute CommonJS or leave partial content registered

Requirement: Boundary parity SHALL be a release gate

The project SHALL maintain focused fixtures covering canonical valid levels, legacy shorthand, invalid coordinates, duplicate IDs, invalid tokens, automaton action errors, and share/export round trips. The gate SHALL run the direct engine, CommonJS loader, and data-only sharing paths against those fixtures, followed by the full Node suite and production build.

Scenario: Parity fixtures catch validator drift

  • WHEN one path accepts or normalizes a fixture differently from the canonical boundary
  • THEN the focused parity test fails with the path and issue difference before the change can be marked complete