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-validate-mod-feature-contract / design.md
design.md 5.8K source

Context

The engine treats core properties and each level's automata[].property as semantic predicates. Every other predicate becomes a noun transform. The loader validates token syntax but has no author warning for a property typo, allows an object noun to equal a custom property, and preserves unknown manifest.features silently. Local CommonJS and data-only shared packs both use validateMod; pack-service previews currently expose only errors.

Clockwork needs diagnostics without changing turn semantics. Existing #pack v1 links and the shipped automata example omit feature declarations, so the new host must continue accepting them.

Goals / Non-Goals

Goals:

  • Distinguish unsupported property intent from a legitimate noun transform, even before a loose word forms a sentence.
  • Reject known semantic property/noun collisions and unknown required features before a pack is installed.
  • Return bounded nonfatal warnings in the same preview envelope for local and shared packs.
  • Preserve existing v1 links, authored automata data, and the manifest.engine: 1 contract.

Non-Goals:

  • Executing custom property callbacks, pairs, or any new engine effect.
  • Predicting every future sentence or blocking legal noun transformations.
  • Treating a presentation glossary alone as proof that the engine implements a property.
  • A visual layout decision for Gift/Workshop; that remains with design.

Decisions

Use one normalized diagnostic pass after validation

Keep validateMod() as the existing error-array API. Add a pure diagnostic function that takes validated, normalized pack data and returns a bounded list of warning records with rule/word source context. It uses the engine's rule parser for authored horizontal, vertical, stacked, and AND sentences so the warning logic does not invent a second grammar. Warnings are nonfatal because a new noun transform is legal.

The diagnostic scans every authored word tile, including loose words. Its known noun set contains placed object nouns and that level's automata subjects, plus nouns in the pack palette or vocabulary. This recognizes intentional future transformation targets such as Word Lab's ROCK IS GEM. An object in another proof alone does not make a word known here. The semantic property set is core properties plus implemented declarations in this level. If a word names a property implemented elsewhere in the pack but not here, the note says it has no property behavior here unless the word is a known noun. If a word is both an implemented property somewhere in the pack and an object noun somewhere else, the note calls out that ambiguity even when the palette or vocabulary names it.

The rule parser still supplies context for already formed sentences. The note records word, levelId, levelTitle, sentence (or null for a loose word), player-language message, and a source-path detail. It is de-duplicated by word text and proof, with a cap for hostile or oversized packs. Thus two physical SHUT tiles in one proof produce one note, while a SHUT in another proof receives its own note.

Enforce custom property/object collisions at the level boundary

Before validating level objects, collect that level's implemented custom property tokens. An object whose noun equals one of them is invalid, just as a core-property noun is invalid today. The engine's direct createGame() validator receives the same check so local modules and direct engine callers agree. This does not reserve an automaton property in unrelated levels where it has not been declared.

Treat feature flags as explicit minimum requirements

manifest.features is an optional array of unique lowercase feature IDs. The current host recognizes automata; an unknown ID is a blocking validation error with player-readable upgrade guidance. A valid pack with automata data and no feature array remains supported so old v1 links and current examples keep working. New authored packs should declare automata explicitly. A future feature that needs old hosts to reject it must also change the engine schema version, because old hosts ignore unknown manifest fields; flags alone cannot retroactively protect them.

Keep warnings out of authored and shared data

Local import and shared decode both run the diagnostic pass and return warnings in their pack-service preview envelopes. Successful results retain errors: []. Warnings are not serialized into CommonJS exports, share URLs, installed pack records, or engine snapshots. Gift shows an amber NOTE line and expandable detail card; Workshop shows an amber notes chip and action, as specified in design D24. A warning never silently becomes an error.

Risks / Trade-offs

  • [Risk] A future legitimate noun transform can mention a noun not yet placed in the proof. → Recognize palette/vocabulary nouns; keep other notes nonfatal and explain the transform consequence.
  • [Risk] A word may form several different sentences during play. → Report one note for that word and proof, using an authored sentence for context when available.
  • [Risk] Feature flags do not protect old deployed code. → Preserve v1 compatibility here and require a future engine version bump for a new unsupported mechanic.
  • [Risk] Warning rendering can crowd the Gift card. → Use design's expandable NOTE card with at most five visible lines and a count for the remainder.

Migration Plan

  1. Add diagnostics and validation fixtures at the loader and direct-engine boundaries.
  2. Expose warnings in pack-service local/share previews and render design's Gift and Workshop note affordances.
  3. Verify old v1 automata links still import; confirm unknown features fail before installation.
  4. Run browser and sharing acceptance, then ask design to review the warning card.

Open Questions

None for the initial note contract. The host accepts legacy automata packs without a feature list.