clone

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

main
miau-is-true / openspec / specs / level-sharing / spec.md
spec.md 13.2K source

level-sharing Specification

Purpose

TBD - created by archiving change share-level-packs. Update Purpose after archive.

Requirements

Requirement: The app SHALL export a selected pack as a CommonJS file

The app SHALL export the selected pack's validated data as a .cjs file containing a deterministic module.exports pack manifest, levels, vocabulary, and palette data. Export SHALL include stable pack and level ids and SHALL not include transient engine state, history, or browser UI state.

Scenario: Export round-trips a selected pack

  • WHEN the player exports a registered pack
  • THEN the downloaded .cjs source contains the same pack id, level ids, and validated level data when imported through the trusted local path

Scenario: Export excludes a running turn

  • WHEN the player exports a pack after moving an entity in a level
  • THEN the exported pack contains the authored initial level data and no current positions, undo history, or win state

Requirement: The app SHALL create and consume a portable data-only URL

The app SHALL encode a selected pack as canonical data in the URL hash as #pack=<versioned-payload>. The encoded payload SHALL be URL-safe and no larger than 16 KiB. The current documented compatibility set contains schema v1; decoders SHALL keep that version usable across site revisions, reject unknown future versions before registration, and add a compatibility adapter only when a later version is explicitly documented. Decoding SHALL parse and validate data only; it SHALL never evaluate JavaScript, invoke CommonJS loading, resolve imports, or execute hooks. A malformed, unsupported, or oversized payload SHALL be rejected before registration.

Scenario: A shared URL loads data without code execution

  • WHEN the player opens a valid URL containing #pack=<versioned-payload>
  • THEN the app decodes and validates its data, registers the pack with URL origin metadata, and executes no module code

Scenario: A tampered URL is rejected

  • WHEN a shared URL contains invalid encoding, unknown schema version, malformed JSON, or invalid pack data
  • THEN the app reports a share-link validation error and does not register partial content

Scenario: An oversized share is rejected

  • WHEN the encoded #pack= payload exceeds 16 KiB
  • THEN the app does not create or register the link and reports that the pack must be exported as .cjs or reduced in size
  • WHEN a later site revision opens a payload version that remains in the documented compatibility set
  • THEN the decoder converts it to the current pack schema and registers it without executing code

Requirement: Local CommonJS import SHALL be explicitly trusted

The app SHALL provide a separate local-file import action that clearly identifies CommonJS execution as trusted code. The action SHALL pass the file through the existing CommonJS validator and loader, and SHALL retain previously registered packs when loading or execution fails.

Scenario: The player confirms a local CJS import

  • WHEN the player chooses a .cjs file through the trusted local import action
  • THEN the app executes it through the documented loader, validates its exported pack, and registers it only after complete validation

Scenario: A local module throws

  • WHEN the chosen .cjs file throws during module evaluation or requests an unsupported import
  • THEN the app shows the module error, registers no partial pack, and keeps existing packs available

Scenario: URL import never falls through to CommonJS

  • WHEN a #pack= payload is present in the current URL
  • THEN the app uses the data decoder only and does not pass the payload to the trusted local CommonJS loader

Requirement: A shared URL SHALL enter a pending Gift flow

When the app opens with a valid #pack= hash, it SHALL decode and validate the pack into a pending Gift state but SHALL not install it or replace the active pack automatically. The player SHALL explicitly choose Keep and play before registration; declining or leaving the Gift state SHALL preserve the existing registry.

  • WHEN the app starts with a valid #pack= hash
  • THEN it presents the pending pack identity for confirmation and the pack is absent from the registry until Keep and play is chosen

Scenario: Gift confirmation installs the pack

  • WHEN the player chooses Keep and play in the pending Gift state
  • THEN the app routes the pack through collision handling and installs or forks it only after the explicit choice is complete

Requirement: Pack management SHALL expose Workshop operations

The presentation-independent pack service SHALL expose operations equivalent to listPacks, importPackText(text, filename), exportPack(id), packLink(id), decodePackLink(hash), installPack(pack, {replace}), and removePack(id). Preview and installed packs SHALL use one normalized view shape containing id, name, author, origin, and level summaries; a preview SHALL include the full first level in levels[0] for a mini preview. A Workshop scene MAY present these operations as Import, Play, Share link, Export, and Remove; visual layout remains outside this capability.

Every player-facing service operation SHALL return a result envelope rather than throw. Successful results SHALL set ok: true and include the operation value with errors: []; failures SHALL set ok: false, preserve the registry, and return errors containing objects with a player-language message, raw diagnostic detail, and an optional levelId. importPackText and decodePackLink SHALL also return conflict: null or {id, name} before installation when the candidate id already exists. exportPack, packLink, and removePack SHALL report missing ids or protected bundled packs through the same envelope.

Scenario: Workshop operations use stable identity

  • WHEN the Workshop requests a pack list or selects a pack for Play, Share link, Export, or Remove
  • THEN the service resolves the requested pack by stable id and returns a plain-language result or error

Scenario: Preview exposes the same shape as an installed pack

  • WHEN Workshop previews a valid local module or Gift decodes a valid share link
  • THEN the result exposes the normalized pack view, including name, author, level count, and the full first level, without installing it

Scenario: A duplicate is visible before confirmation

  • WHEN a previewed pack id already exists
  • THEN the result returns conflict: {id, name} and leaves installation to the explicit Replace or Keep both choice

Scenario: Player errors do not escape the service

  • WHEN a player requests export, link, remove, import, or decode with invalid or unavailable content
  • THEN the operation returns ok: false with readable message values and raw detail values, and the caller receives no uncaught exception

Scenario: Temporary developer controls are hidden by default

  • WHEN the app runs without the ?dev query flag
  • THEN interim sidebar controls are absent from the default player-facing flow, while developer-only controls MAY be exposed when ?dev is present

Requirement: Sharing actions SHALL provide observable feedback

The app SHALL report success or failure for pack selection, URL copy, URL decode, .cjs export, trusted local import, validation, and collision handling through a readable status channel that does not depend on animation alone.

Scenario: Copy succeeds

  • WHEN the player requests a share URL and clipboard writing succeeds
  • THEN the app reports that the URL is ready and identifies the shared pack

Scenario: Clipboard access fails

  • WHEN the browser denies clipboard access
  • THEN the app reports the failure and exposes the generated URL for manual copying

Requirement: Pack previews SHALL expose nonfatal rule warnings without serializing them

Local CommonJS previews and data-only shared-link previews SHALL apply the same supported-feature and property validation. A successful preview SHALL return a bounded warnings collection separate from errors: [], with word, levelId, levelTitle, optional sentence, a player-language message, and technical detail. Warnings SHALL not prevent installation and SHALL not be serialized into exported CommonJS source, shared payloads, installed pack data, or engine snapshots. Unknown required features SHALL fail atomically before installation.

Scenario: Local and shared preview agree on warnings

  • WHEN the same valid pack with an initially formed unknown predicate is previewed from a local module and a shared link
  • THEN both previews succeed and expose the same warning context without installing the pack

Scenario: Diagnostics stay outside portable data

  • WHEN a warned pack is exported as CommonJS or encoded into a share URL and decoded again
  • THEN the authored data remains valid, the warning is recomputed on preview, and no transient diagnostic field appears in the exported data

Scenario: Unsupported shared feature fails before Gift installation

  • WHEN a shared payload declares a feature unsupported by the current host
  • THEN the decoder returns a player-readable error and Gift cannot install partial content

Requirement: Pack manifests SHALL declare or derive a semantic rules revision

The loader SHALL validate manifest.rules as a positive integer no greater than the host's current rules revision. The host's current revision SHALL be 2, representing the post-W6 precedence, transform-reading, and contact-order contract. An omitted field SHALL mean legacy revision 1 and SHALL be materialized in normalized pack data. A value greater than 2 SHALL reject the pack before any catalog mutation.

All pack-service manifest projections, including manifestFromView() and createPackView(), SHALL preserve the effective revision. A view round trip or persisted catalog record SHALL not silently remove manifest.rules.

Scenario: A current pack loads without a compatibility note

  • WHEN a valid pack declares manifest.rules: 2
  • THEN local preview succeeds with errors: [], no rules-version warning, and normalized data retains manifest.rules: 2

Scenario: An omitted revision uses legacy semantics metadata

  • WHEN a valid imported pack omits manifest.rules
  • THEN preview succeeds, normalized data contains manifest.rules: 1, and the catalog remains unchanged until the caller explicitly installs it

Scenario: A pack view preserves its revision

  • WHEN a legacy pack is previewed, installed, and projected back through a pack-service view
  • THEN the view and any later export/share operation retain manifest.rules: 1 rather than rebuilding a manifest without the field

Scenario: Malformed revisions fail validation with player copy

  • WHEN a pack declares manifest.rules as 0, -1, 1.5, or a string
  • THEN preview fails with the player message This pack's rules version isn't valid, so it can't be installed., preserves the technical manifest.rules validation detail, and does not install or partially register the pack

Scenario: Future revisions fail atomically with player copy

  • WHEN a pack declares manifest.rules: 3
  • THEN preview fails with the player message This pack needs a newer version of Miau Is True. Update the game to play it., preserves the technical unsupported-rules-revision detail, and the catalog contains none of that pack's levels

Requirement: Older rules SHALL produce one nonblocking W11 warning

The preview service SHALL return one bounded, nonblocking warning when a pack's effective rules revision is below the host revision.

When a pack's effective rules revision is below the host revision, local CommonJS and data-only shared previews SHALL return one warning, while keeping errors: [] and allowing explicit installation. The warning SHALL have word: 'RULES', levelId: null, levelTitle: null, sentence: null, and the player message This pack was made for older rules; some proofs may play differently. Its technical detail SHALL identify the declared or derived revision and the current host revision. The host SHALL continue to run current rules semantics; the warning is not a legacy simulation mode.

Scenario: An explicit old revision is surfaced once

  • WHEN a valid pack declares manifest.rules: 1 and contains seven proofs
  • THEN preview succeeds with one pack-level warning, the exact player message, and detail showing 1 versus current 2; it does not emit seven duplicate warnings

Scenario: Local and shared previews agree

  • WHEN the same legacy pack is previewed from CommonJS and from a data-only shared link
  • THEN both previews expose the same warning envelope and normalized manifest.rules: 1 without installing the pack as a side effect

Scenario: Warnings remain transient

  • WHEN a warned legacy pack is installed, exported, or encoded as a shared link
  • THEN its normalized manifest retains only rules: 1, no warning field is serialized, and the warning is recomputed when the pack is previewed again