- commit
- 3e75be9f5229c1deb89955feaf5dc2d7c1eb37f8
- parent
- 46272da00ecf1e5249732f3d64b21ebaf2c11f3c
- author
- Dami <dami.etoile@gmail.com>
| M | docs/ | 32 | ++++++++++++++++++++++++++---- |
| M | src/ | 10 | ++++++++- |
| M | tests/ | 7 | ++++++- |
@@ -41,7 +41,7 @@ The exported object has these fields:
4141 4242 | Field | Required | Meaning |4343 | --- | --- | --- |44-| `manifest` | yes | `id`, `name`, and semantic `version`; optional `engine` must be `1` |44+| `manifest` | yes | `id`, `name`, and semantic `version`; optional `engine` must be `1`; optional `features` lists required host features |4545 | `levels` | yes | One or more level objects |4646 | `palette` | no | Uppercase noun to display string, or `{ glyph, faces: 'left' \| 'right' }` for an explicitly directional glyph |4747 | `vocabulary` | no | Uppercase noun tokens used by the mod |@@ -54,6 +54,14 @@ or vocabulary entries. Every level requires a unique lowercase `id` and may
5454 also include `title` and `hint`. A pack can contain at most 64 levels, each5555 with at most 4096 entities. The local CommonJS source limit is 512 KiB.5656 57+`manifest.features` is an optional array of unique lowercase feature IDs. The58+current host supports `automata`. A new automata pack should declare59+`features: ['automata']`; existing version 1 packs and links without the field60+remain valid. A required feature the host does not support stops import with61+“This pack needs a newer Miau.” A future mechanic that older hosts must reject62+also needs a higher `manifest.engine` version, because an older host may ignore63+an unfamiliar manifest field.64+5765 Palette strings never flip. A mod that opts into facing may use a bounded5866 object entry instead:5967 @@ -111,6 +119,22 @@ missing levels, out-of-bounds positions, duplicate IDs, unsupported object
111119 kinds, invalid rule tokens, and invalid palette entries. This lets an editor112120 show actionable errors without starting a partially loaded mod.113121 122+The pack service also returns nonblocking `warnings` beside `errors` when a123+word tile has no rule or noun meaning in its proof. This check covers loose124+words that might be pushed into a sentence later. It uses nouns placed in that125+proof and that proof's automata subjects, along with core and locally declared126+properties. A palette entry or a noun in another proof does not make a word127+meaningful here. For example, a loose `SHUT` word receives a note when `SHUT`128+is neither a noun nor a property in the proof. If the player forms129+`DOOR IS SHUT`, the engine interprets `SHUT` as a noun transformation; the note130+explains that consequence. Each record includes `word`, `levelId`,131+`levelTitle`, `sentence` when formed, player-facing `message`, and technical132+`detail`. Authors can still install and play a pack with notes.133+134+Automata properties are declared per proof. The same token cannot also be an135+object noun in that proof. A custom property declared only in another proof136+has no behavior here, and its word tile receives a note.137+114138 ## Bundled teaching packs115139 116140 The repository includes two authored packs in addition to the three core@@ -146,9 +170,9 @@ Local imports follow the same flow: Workshop, Import, Gift preview, then
146170 explicit **Keep and play**. Workshop also offers Play, Share link, Export147171 `.cjs`, and Remove. Installed authored pack data is persisted through the pack148172 library and reloaded without evaluating CommonJS. The service and pending149-install contract are covered by unit tests. [Revision 5 of the public stage](https://fcbc.n.zip/)150-verifies these flows with 150 Node tests and the full browser sharing suite, including151-missing-audio behavior and the mobile, reduced-motion, and `?dev` paths.173+install contract are covered by unit and browser tests. The174+[stable public stage](https://fcbc.n.zip/) includes the verified sharing flow;175+engine and preview revisions are recorded in `docs/roadmap.md`.152176 Workshop is reachable directly from Title and INDEX. The designer accepted153177 the stage visual gate.154178 @@ -129,7 +129,15 @@ export function createPackService(options = {}) {
129129 const previews = new WeakMap();130130 131131 function listPacks() {132- return catalog.getPacks().map(record => createPackView(catalog, catalog.getPack(record.id), record.origin, previews));132+ return catalog.getPacks().map(record => {133+ const source = catalog.getPack(record.id);134+ return {135+ ...createPackView(catalog, source, record.origin, previews),136+ // Notes are a view of the current host's rule vocabulary. They never137+ // become authored pack data or part of a portable share payload.138+ warnings: collectModWarnings(source),139+ };140+ });133141 }134142 function view(id) {135143 const record = catalog.getPacks().find(item => item.id === id);@@ -26,7 +26,9 @@ test('previews and installed packs share a full, presentation-ready shape', () =
2626 assert.equal(preview.pack.levels[0].id, 'hello');2727 assert.equal(preview.pack.levels[0].width, 3);2828 assert.equal(preview.pack.levels[0].objects.length, 4);29- assert.deepEqual(Object.keys(preview.pack).sort(), Object.keys(service.listPacks()[0]).sort());29+ const { warnings: installedNotes, ...listedPack } = service.listPacks()[0];30+ assert.ok(Array.isArray(installedNotes));31+ assert.deepEqual(Object.keys(preview.pack).sort(), Object.keys(listedPack).sort());3032 assert.equal(service.listPacks().length, 1);3133 const installed = service.installPack(preview.pack);3234 assert.equal(installed.ok, true);@@ -68,6 +70,9 @@ test('local and shared previews expose rule warnings outside portable pack data'
6870 assert.equal('warnings' in local.pack, false);6971 7072 assert.equal(sender.installPack(local.pack).ok, true);73+ const installed = sender.listPacks().find(pack => pack.id === 'gift-pack');74+ assert.deepEqual(installed.warnings, local.warnings);75+ assert.equal('warnings' in sender.catalog.getPack('gift-pack'), false);7176 const link = sender.packLink('gift-pack');7277 assert.equal(link.ok, true);7378 const receiver = createPackService();