clone

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

main
miau-is-true / openspec / specs / commonjs-mods / spec.md
spec.md 6.9K source

commonjs-mods Specification

Purpose

Define the trusted local CommonJS content format, validation boundary, and failure behavior for authored puzzle packs. Remote, data-only sharing is specified separately from executing local module source.

Requirements

Requirement: The app SHALL load local CommonJS mods through a validated manifest

The mod loader SHALL load local CommonJS source and read its module.exports object containing a required manifest (id, name, and semantic version), one or more levels, and optional vocabulary and palette records. The host SHALL validate the complete export before registering any content and SHALL keep built-in content available when a mod fails validation.

Scenario: A valid mod registers a level

  • WHEN a local module exports a valid manifest, level definition, vocabulary, and palette
  • THEN the loader registers its level and uses its palette for noun presentation under that mod id

Scenario: A malformed mod is rejected

  • WHEN a module has no id, duplicate identifiers, invalid coordinates, or unsupported vocabulary values
  • THEN the loader rejects it with a diagnostic and does not partially register its content

Requirement: Mods SHALL use the host engine contract

Mods SHALL define vocabulary, palette, and level data through the exported object. Mods SHALL NOT directly mutate the active rule table, DOM, renderer scheduler, or persisted engine state; the host SHALL pass validated level data to the engine.

Scenario: A mod adds a noun and emoji

  • WHEN a manifest declares noun FENN and its emoji representation
  • THEN levels from that mod can place FENN objects and the presentation uses the declared mapping

Scenario: A mod cannot bypass the engine state contract

  • WHEN a mod supplies malformed level state or attempts to change state through data outside the documented manifest fields
  • THEN the host registers only validated manifest, vocabulary, palette, and level data and keeps engine state under host control

Requirement: Mod loading SHALL be deterministic for the local MVP

The loader SHALL run local mods with the host's documented CommonJS compatibility layer, allow only the host's supported helper import, and avoid URL or package resolution. Module execution and validation errors SHALL be isolated to the affected mod and shown in diagnostics.

Scenario: A mod error does not break built-in play

  • WHEN a local module throws while loading or requests an unsupported import
  • THEN the app reports the mod error and keeps built-in levels playable

Requirement: The loader SHALL report pack identity collisions

The loader SHALL report a valid module whose pack id is already registered to the pack service, which SHALL offer explicit Replace or Keep both as a copy handling. Replacement keeps the authored id; a copy receives the next available suffixed id and forkOf provenance. Either operation SHALL validate completely before changing the registry.

Scenario: Duplicate pack ids are rejected

  • WHEN a second local module exports an already registered pack id
  • THEN the loader reports the collision without silently replacing the original pack and exposes the two explicit resolution choices

Scenario: A forked local pack gets fresh progress identity

  • WHEN the player chooses Keep both as a copy for a duplicate local module
  • THEN the registered pack receives a suffixed id with forkOf provenance and progress is keyed by the new id

Requirement: Pack validation SHALL distinguish unknown rule words from unsupported mechanics

The loader SHALL accept optional manifest.features as a list of unique supported feature identifiers and SHALL reject unknown required features before registration with an upgrade message. A level SHALL reject an object noun equal to a custom property implemented by that level. A word SHALL count as a known noun when it is placed or named as an automaton subject in that proof, or listed in the pack's palette or vocabulary. Other words that are neither core words nor implemented properties in that proof SHALL receive a nonfatal note with word and proof context, whether or not they currently complete a sentence. A word declared as a property in one proof and used as an object noun in another SHALL receive an ambiguity note even if it is in the pack palette or vocabulary. Existing packs with automata and no feature list SHALL remain valid.

Scenario: Unknown predicate receives a note

  • WHEN a proof contains DOOR IS SHUT with SHUT absent from that proof's implemented properties and nouns
  • THEN local preview succeeds, reports a note identifying SHUT, the proof, and the noun-transform consequence, without blocking installation

Scenario: Loose unknown word receives a note

  • WHEN a proof places a loose SHUT word that can be moved into a sentence but declares no SHUT behavior or noun in that proof
  • THEN preview reports a note for SHUT even before the sentence is formed

Scenario: Known noun transformation remains valid

  • WHEN a proof contains ROCK IS MIAU and places a MIAU object in that proof
  • THEN no unknown-word note is returned for MIAU

Scenario: Pack-defined transformation target is known

  • WHEN one proof contains ROCK IS GEM, the pack palette or vocabulary defines GEM, and no GEM object starts in that proof
  • THEN no unknown-word note is returned for GEM

Scenario: Pack-wide property and thing ambiguity receives a note

  • WHEN one proof declares SHUT as a custom property and another proof uses SHUT as an object noun and places its word
  • THEN preview reports a nonfatal ambiguity note for SHUT even if the pack palette or vocabulary names it

Scenario: Declared automaton property remains semantic

  • WHEN a level declares TICK through automata[].property and contains MOUSE IS TICK
  • THEN no unknown-predicate warning is returned for TICK

Scenario: Property declared only in another proof receives a note

  • WHEN one proof declares TICK through automata and another proof contains CLOCK IS TICK without a TICK automaton
  • THEN the second proof reports that TICK has no property behavior there unless the pack defines TICK as a noun

Scenario: Property noun collision is rejected

  • WHEN a level declares TICK as an automaton property and places an object whose noun is TICK
  • THEN the loader rejects the level with the object's path and does not register part of the pack

Scenario: Unsupported feature is rejected

  • WHEN a pack declares manifest.features: ['pairs'] in a host that supports only automata
  • THEN the loader rejects the pack with a readable newer-version message before any level is installed

Scenario: Legacy automata pack remains usable

  • WHEN a valid existing pack contains automata but omits manifest.features
  • THEN the current loader accepts it and preserves its automata behavior