Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
# Thing 2.0 map
Campaign `thing2` in the eureka mind. Imagination pass `imagination-thing2-contract` ,
session `self-2026-10-10c-eureka` , 2026-10-10. Target `thing2:target:r1`
(`docs/thing2-target.md` ). Rulings in force and cited by id:
`thing:ruling:thing2-one-solver-taffy` , `thing:ruling:thing2-thing-owns-text` ,
`thing:ruling:wire-rename-freeze-fold` , `thing:ruling:operator-compat-rebuild` ,
2026-10-10 20:29:31 +02:00
`thing:ruling:operator-dsl-thing-script` , `thing:ruling:operator-member-names-keep` ,
`thing:ruling:operator-aetheria-eve-rename` , and the six `thing2:ruling:*` of
2026-10-10 (`code-home` , `thing-script-form` , `advertisement-owner` ,
`port-cutover` , `campaign-repo-limit` , `retired-repo-names` ).
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
This page holds body facts, the model page, the consumer contract at the level
consumers need, the cut order at the level of deliverables, and rationale. The
questions it raises are documents in the mind (`thing2:question:*` ); it does not
restate their options. Research for the forks:
`docs/research/thing2-contract-forks-prior-art.md` . Evidence it builds on:
`docs/research/thing2-consumer-census.md` (census, cited as C§n, BD1-BD9),
`docs/research/thing2-foundation-prior-art.md` ,
`docs/research/thing2-text-input-a11y-prior-art.md` , and `docs/thing-campaign.md`
(rename table T, edges E1-E7, body facts B1-B28).
## Body facts
2026-10-12 07:17:32 +02:00
Probes P1-P16 ran 2026-10-10, P17-P19 2026-10-12, against `origin/<default>` after `git fetch` unless a
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
local path is named. Scripts are in the session scratchpad
`imagination-thing2-contract/` .
**P1. The Thing 1 contract is 38 schemas, most of them process machinery.**
`p1_schemas.py` over Eve `schemas/` at `a758787` : 38 files. Eleven describe
conformance, hand-off, split-target, release, capture and witness bookkeeping
(`conformance_*` , `*_handoff` , `runtime_*` , `capability_matrix` ,
`provider_scenario` ); five describe one renderer's output (`unity_*` ,
`electron_shell_projection` , `web_layout_probe` , `tui_grid` ); seven describe the
plugin system (`plugin*` ). Every schema but `command_invocation` ,
`command_result` , `entity_soa_view` , `input_*` and `asset_catalog` sets
`additionalProperties: true` .
**P2. Commands travel in four overlapping envelopes, and the result is hand-built
JSON.** `command.v1` (descriptor, open), `command_invocation.v1` (closed),
`command_receipt.v1` (open, `state` enum `accepted|denied|pending|reconciled` )
and `command_result.v1` (closed: exactly `schema, receipt, transientProjection,
pluginPayload, draftDirective`). Ghostlight ` crates/ghostlight-dungeon/src/eve.rs`
at `327cbe3` : `validate_invocation` (`:665-700` ) re-checks the envelope by string
comparison; `command_result` (`:734-` ) takes `state: &str` and builds the receipt
with `json!` ; its own log filter names a state `"completed"` that the receipt
enum does not admit (`:746` ). Today it passes only `accepted` and `denied`
(tally of call sites). The browser rejects a flat result whole through `ajv`
(`web/package.json` depends on `ajv` ; `eve.rs:723-731` comment). Nothing but
convention stands between a provider and a result the viewer refuses.
**P3. One surface kind has two schemas and is stored as an untyped blob.**
`surface.v1` declares `type: const "surface-state"` ; `surface_state.v1` exists
only as code: CultLib `packages/cultcache-ts/src/swarm-documents.ts:82-96`
(members `providerId, title, version, updatedAt, surface: object` ) and Odin
`crates/odin-core/src/documents.rs` (`EveSurfaceStateRecord { surface: Value }` ;
advertisement and interface binding are `{ value: Value }` ). The load-bearing
shape is JSON inside CultCache.
**P4. `interface_binding.v1` is not a binding.** weksa
`scripts/weksa-daemon.mjs:321-341` writes routes (`endpoints` , `routes` ,
`cultMeshAddress` ) and the whole operator surface into it; its document type
parses anything (`schema: { parse: (value) => value }` , `:418` ). It is a third
copy of the advertisement's routes and the surface.
**P5. The text DSL lets the renderer write state.** `web/eve-dsl.js:103-155` :
`var` , `collection` , `derive` (`count` , `latest` ) live in a renderer-local mesh,
and `applyAction` appends or sets values there on a button press
(`web/fixtures/reactive-composition.eve` : `button "Append Event" append
swarm.events ...`).
**P6. A domain-looking kind is layout plus a server-side command.** Muninn
`crates/sleipnir-daemon/src/main.rs:1374-1415` : `binding.target` is a labelled
target with `x, y, anchorX, anchorY` over a controller image; "listening" is the
daemon's `pending_learn` state. Absolute positioning (Taffy `position:
absolute` with insets), a button and a bound label compose it.
**P7. Login needs a host effect, not a plugin payload.** Ghostlight
`web/src/heimdall-access-adapter.ts:48-66` : a `pluginPayload` of schema
`heimdall.auth_navigation_receipt.v1` makes the client call
`window.location.assign(url)` ; on return it reads the URL and resumes
(`:82` , `:134` ).
**P8. Two live viewers render surfaces.** Ghostlight's web client vendors
`@gamecult/eve-browser-lowering` and `eve-contracts` from `vendor/eve`
(`web/package.json` ). gamecult-ops `scripts/idunn/idunn-deployment-targets.ps1:262-272`
at `9aed01f` : `nightwing-eve-browser-reference` , `Repo = "Eve"` ,
`Status = "runtime-enforced"` , `Deploy = $null` . Odin aggregates and renders
nothing.
**P9. CultCache-rs members are serde types.** CultLib
`packages/cultcache-rs/src/lib.rs:5501-5520` : a `DatabaseEntry` whose member is a
struct holding an enum round-trips through the MessagePack store. Whether the TS,
C# and Python CultCache runtimes decode a recursive tagged union written by Rust
with parity was not established in this pass. It is the first thing the
contract cut proves (see the cut order).
**P10. The stale `.voidbot` lines are still on Eve main.** `a758787` :
`.gitignore:28-30` and `scripts/stage-to-eve.ps1:24` (`--exclude=.voidbot` ).
`git grep -n '\.voidbot' -- .gitignore scripts` prints exactly those four lines;
`Sources/EVEViewController.mm` mentions `voidbot.swarm` (no dot) and is not
matched.
**P11. The campaign's repo list has room for the early cuts only.** Mind,
`thing2:campaign:self` : repos `GameCult/Eve` , `GameCult/gamecult-ops` .
`eureka-substrate:cut_spec:cut-campaign-repos.r1` (target-revision repo sets, up
to 16) is in force with no report; both its questions are answered
(`eureka-substrate:ruling:campaign-repo-set-home` , `:campaign-repo-removal` ).
2026-10-10 20:29:31 +02:00
**P12. Rust CultCache writes a union as serde's external tag; the C# reference
writes `[key, fields]` . They do not read each other.** Probe pass
`imagination-thing2-contract-cut` , 2026-10-10, local scratch
`imagination-thing2-contract-cut/{rs,cs,ts}` against CultLib `6bdc23c9` . One
record type in all three runtimes: `id` , `version` , `root: Node` with `Node`
one of `Box { id, children: [Node] }` , `Text { id, content }` ,
`Use { id, composition, slots: [Node] }` , and `outcomes: [Outcome]` with a unit
arm. Rust (`#[derive(DatabaseEntry)]` , serde enums) writes the payload
`94 af..31 07 81 a3"Box" 92 ...` : each arm a one-entry map keyed by the variant
name, fields positional, a unit arm the bare string `"Denied"` . C# (MessagePack
`[Union(n, ...)]` , the reference) writes `94 af..31 07 92 00 92 ...` : each arm
`[key, [fields]]` , the unit arm `[2, []]` . C# reading the Rust store fails:
`MessagePackSerializationException: Failed to deserialize ProbeSurface value.
<- Unexpected msgpack code 129 (fixmap) encountered.` Rust reading the C# store
fails: `failed to decode CultCache entry "probe.surface" ... a value does not
fit the store format`. Each runtime round-trips its own store. The
`cultcache-rs` derive handles structs only
(`cultcache-rs-derive/src/lib.rs:51` ); enum members go through `rmp_serde` .
Owner: CultLib (`thing2:follow_up:cultcache-union-parity` ).
**P13. TypeScript CultCache has no typed codec.** `cult-cache.ts:679-692` : with
no `formatter` , a payload decodes to plain arrays and maps and `schema.parse`
sees them raw; the interop test hand-writes a positional formatter per type
(`test/cult-cache.test.ts:1263-1310` ). In the probe, a hand-written TS codec
for the union read the Rust store, wrote a store Rust read back equal, and
re-encoded byte-identical to Rust's payload; against the C# store the same
codec refused the `[key, fields]` arm (`node is not a tag map` ). A TS reader
matches whichever encoding its author hand-codes.
**P14. Nothing live builds from the six retired family repos.** `git grep` at
`origin/HEAD` of Ghostlight `327cbe3` , Odin `8e1de66` , VoidBot `46d891b` ,
Heimdall `7904e17` , Muninn `1d0ea0b` , weksa `d16cff5` , Stonks `3f6e91c` , Vili
`8773c15` , Gjallar `6a92976` , StreamPixels `8d40541` , CultLib, Idunn `6667f0f` ,
gamecult-site `8169ee5` and Ymir `f8c0e39` for `EveUnity|EveFlutter|EveElectron|
EveTui|EvePlugins|EveConformance` outside ` *.md`: no match. gamecult-ops ` 9aed01f`
matches only `docs/repo-census-2026-09/synthesis.html` ; Eve matches a local
smoke script (`scripts/run-aetheria-conformance-consumer-smoke.ps1:35` , a local
path) and a spec; parked Mimir names them in one configuration file. GitHub,
2026-10-10: all six unarchived; active workflows only on EveFlutter and EveTui
(`runtime.yml` ) and EveConformance (`conformance.yml` ); `GameCult/Thing` and
`GameCult/AetheriaThing` do not exist; AetheriaEve is archived.
**P15. Ghostlight's deploy compares its Eve clone's origin by exact string.**
gamecult-ops `9aed01f` `scripts/deploy-ghostlight-yggdrasil.sh:20`
(`eve_origin=https://github.com/GameCult/Eve.git` ) and `:871-876`
(`require_idunn_repo` refuses "a foreign origin" when `git remote get-url
origin` differs), called at ` :883`; the binding template carries the same URL
(`idunn/yggdrasil/bindings/ghostlight.toml.in:14` ). Editing either URL without
re-pointing Yggdrasil's Idunn-owned clone stops the next Ghostlight deploy;
leaving both on the old name keeps working through GitHub's rename redirect.
The `Repo = "Eve"` targets (`idunn-deployment-targets.ps1:223` , `:236` ,
`:262` ) are Thing 1 targets (iPad, Android, Nightwing browser reference) paired
with `LocalPath = "$projectsRoot\Eve"` .
**P16. gamecult-site carries a generated Thing 1 surface.**
`scripts/generate-vn-eve-surface.mjs` writes
`GameCult/static/interactive/gamecult-compound/gamecult-compound.eve-gui.json`
(`gamecult.eve.*` ids), referenced by `GameCult/Pitch.md:15`
(`data-eve-gui=` ). The census (C) does not list the site as a consumer.
2026-10-12 07:17:32 +02:00
**P17. The contract's integer leaves are signed because TS and Python have no
unsigned leaf; the C# reference has one.** CultLib `b3bd8dd5` :
`src/GameCult.Caching/CultSchemaTypeNames.cs` names any CLR type (so `UInt32` ,
`UInt64` and `Byte` slots exist in the reference); `cultcache-ts`
`src/codec.ts` exports only `int32` and `int64` integer codecs and
`cultcache-py` `leaves.py:147-148` only `int32` and `int64` . A `u64` above
2^63 has no TS or Python reading, so versions stay `Int64` and counts `Int32` ,
and the non-negative rule is the contract's, not the leaf's. `Rgba` keeps `u8`
(the reference's `Byte` ); the TS and Python packages mirror its 0-255 range
from a refusal golden until CultLib gives them a byte leaf.
**P18. A domain newtype's own `Deserialize` holds its rule at every decode
door, and leaves the bytes unchanged.** Probe `imagination-contract-rs-r2/`
(throwaway commit `fbbdaf79` on `649844aa` , Yggdrasil, 2026-10-12): `Seq(i32)`
refusing below 1 and `Version(i64)` refusing below 0, each `Serialize` the inner
integer and `Deserialize` through `new` . A result payload with seq 0 or -7 is
refused by `rmp_serde::from_slice` ("Seq: at least 1") and by
`cultcache_rs::decode_payload` (opaque: "a value does not fit the store
format"); seq 0 through serde's in-memory `IntoDeserializer` is refused the same
way, so the rule holds for any serde format. With `CommandResult.seq: Seq` and
both composition versions `Version` (an `Int32` slot widened to `Int64` ),
`goldens_match` still compares byte-identical, `surface.cc` included. The
derive reads each slot with `seq.next_element::<FieldType>()`
(`cultcache-rs-derive/src/lib.rs` , `slot_codec` , at `b3bd8dd5` ), so a field's
type is the one owner of its domain on both paths. `DatabaseEntry` requires
`DeserializeOwned` (`cultcache-rs/src/lib.rs:162` ), so the public
`Deserialize` of every kind cannot be removed: it is the decode door.
**P19. Dotted provider ids are the Verse's norm; dotted local names are not
needed.** `git grep` over Ghostlight, VoidBot, Odin, Heimdall, Muninn, Stonks
and Eve (operator checkouts, 2026-10-12): provider ids `voidbot.swarm` ,
`odin.allseer` , `stonks.market` , `sleipnir.input-mirror.raven` , `heimdall` ;
Thing 1 surface ids repeat the provider as a prefix (`voidbot.swarm.surface` ,
`ghostlight.play` in `crates/ghostlight-dungeon/src/mesh.rs:37` ). No
`gamecult.thing.*` record exists outside the contract's tests, and Thing 1
records are rebuilt, not read (`thing:ruling:operator-compat-rebuild` ), so the
Thing 2.0 key grammar strands nothing. Probe evidence of the collision itself:
Soul's `soul-thing2-contract-rs/rust1.log` KEY lines.
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
## The model page
Every persistent kind Thing 2.0 has. Ids follow rename table T: schema
`gamecult.thing.<x>.v<n>` , mesh key `thing:<family>:<id>` . No row keeps a
compatible eve id (`operator-compat-rebuild` ).
| Kind | What names it | What happens to it over time | Who decides (one writer) |
| --- | --- | --- | --- |
2026-10-12 07:17:32 +02:00
| Thing Script document (a published surface) | schema `gamecult.thing.surface.v1` ; key `thing:surface:<providerId>.<label>` , where `label` is one segment (non-empty, no `.` ), so the key splits at its last dot and is injective while provider ids stay dotted (P19); `version` is an `Int64` at least 0 in the record (P17) | Republished whole when its structure changes, `version + 1` ; core ignores a version at or below the one it holds. Live values never cause a republish (they arrive through bindings). Retired when the provider deletes the record. A contract revision is a new `v<n>` ; stores holding the old one are deleted and rebuilt by their daemon | The publishing provider. Core and viewers only read |
| Composition | schema `gamecult.thing.composition.v1` ; key `thing:composition:<publisher>.<name>.v<n>` , `name` one segment (no `.` ), `n` an `Int64` at least 0 | Immutable once published: a change is a new `v<n>` under a new key; references pin the version. The standard library (`thing.std.*` ) ships compiled into core and is also published by the Thing repo's release | The publisher (a provider, or the Thing repo for `thing.std` ). Core resolves and expands; nothing else writes |
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
| Binding | not a document: a field of a node in a Thing Script document or composition (`Bind` ) | Lives and dies with the document that holds it | Declared by the document's writer; resolved only by core |
| Bound state record | the provider's own schema and key (e.g. `voidbot.swarm_control_state.v1` ) | The provider's lifecycle | The provider. Core never writes typed state; a change goes through a command |
| Viewer session state (focus, scroll offsets, editing drafts, selection, IME composition, open overlays) | the core instance; node ids within a resolved document | Not persisted and never published; dies with the session. A draft that must outlive the session is state, and state belongs to a provider | Thing core |
| Command declaration | not a document: an entry of the Thing Script document (`CommandDecl` , id local to the document) | With its document | The document's writer |
| Command invocation | schema `gamecult.thing.command_invocation.v1` ; key `thing:invocation:<invocationId>` (a UUID minted by core per user action, also the idempotency key) | Written once, never edited; retained by the provider's command boundary until its final result, then the provider's retention | Thing core, on behalf of the viewer; the transport sets the caller identity |
2026-10-12 07:17:32 +02:00
| Command result | schema `gamecult.thing.command_result.v1` ; key `thing:result:<invocationId>.<seq>` (`seq` an `Int32` from 1, refused below 1 on construction and decode; the key is injective because `seq` is the last dot segment and never holds a dot; a pending result may be followed by a final one) | Written once per `seq` ; the final outcome ends the invocation | The provider that handles the command, through the contract's constructors only; core validates on receipt |
| Asset catalog | schema `gamecult.thing.asset_catalog.v1` ; key `thing:asset_catalog:<providerId>.<catalogId>` , `catalogId` one segment (no `.` ); `version` an `Int64` at least 0; asset width and height `Int32` at least 0 | Republished whole, `version + 1` ; image nodes reference `catalogId` + asset id | The provider |
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
| Theme | schema `gamecult.thing.theme.v1` ; key `thing:theme:<themeId>` | Versioned like a document; documents reference tokens, never a theme | The host application that embeds Thing (the GameCult brand theme is one such document); `thing.std` default theme in core |
2026-10-10 20:29:31 +02:00
| Provider discovery (today `gamecult.eve.provider_advertisement.v1` ) | Not a Thing kind (`thing2:ruling:advertisement-owner` ): owned by the Verse discovery owner and renamed once there (`thing2:follow_up:discovery-owner-campaign` ) | Until its owner lands, the eve advertisement is read through core's discovery port, unchanged | The discovery owner; the provider writes, Odin aggregates |
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
| Accessibility tree | not persisted: derived by core per frame from the resolved document and layout (AccessKit tree updates) | Rebuilt on every change | Thing core; backends bridge to the platform |
| Render-command list | not persisted and never published: in-process from core to backend | Per frame | Thing core |
| Geometry and render-command goldens | test fixtures in the Thing repo (`tests/goldens/` ) | Regenerated only by a reviewed change to core; a backend never writes one | The Thing repo's core tests |
## The consumer contract
Consumers see four things (target, "The consumer contract"). Each is a set of
types in the contract crate and its language packages; nothing in core is
visible to them (`narrow-consumer-contract` ).
### 1. Thing Script
**The node set is closed.** A document's node is exactly one of:
| Node | Carries | Covers today's kinds (census C§7) |
| --- | --- | --- |
| `Box` | layout style (Taffy: display flex, grid or block; direction, wrap, gap, padding, margin; size, min, max in px, %, fr, auto, content; position relative or absolute with insets; overflow visible, clip or scroll), paint (background, border, radius as theme tokens or literal colours), accessibility role from a closed set (group, region, list, list item, heading, dialog, toolbar), label, children | `pane` , `card` , `panel` , `dashboard` , `cockpit` , `rail` , `row` , `grid` , `list` , `partition` , `surface` , `form` , `interface` , `binding.target` layout |
| `Text` | content (literal or `Bind` ), text style token, wrap, max lines, selectable | `text` , `stat` , label parts of `metric` |
| `Image` | asset reference (catalog + id), fit, alt text | `image.*` , `avatar` |
| `Button` | content children, `on_activate: CommandRef` , enabled (literal or `Bind` ) | `control.button` , `command` (Vili) |
| `Toggle` | checked (`Bind` ), `on_change: CommandRef` | `control.toggle` |
| `Slider` | min, max, step, value (`Bind` ), commit command | `control.slider` , `control.range` |
| `TextInput` | single or multi-line, value kind text or number, placeholder, initial value (`Bind` ), submit command | `control.input.text` , `control.input.textarea` , `control.input.number` , `input.number` |
| `Select` | options (literal or a bound collection), value (`Bind` ), command | `input.select` , `control.select` , `control.choice-group` |
| `Repeat` | a bound collection and a template subtree instantiated per item, keyed by item key; template bindings may read item members | `list ... bind <collection>` (BD5) |
| `Overlay` | anchored to a node or the viewport, modal or not, dismiss command | `modal` , `control.popup` , tooltips |
| `Plot` | a bound numeric series, closed marks (line, bar, area), axis ranges | `graph` ; sparklines a dashboard consumer expects |
| `Embed` | a host slot kind (string the host declares it supports) and slot id; laid out as a leaf of a given size; draws one opaque host command | `embed.norn` , `embed.tex` , `world.scene*` , `field.*` , `canvas.editor` , game viewports |
| `Use` | a composition reference (`publisher.name.v<n>` ) and typed arguments | `metric` , `inspector.kv` , `control.tabs` , `heimdall.access_gate` , `vn.stage` , every domain kind |
Every node has an id (unique within its document after expansion), `visible`
(literal or `Bind` ) and an optional accessibility label. A domain need is met by
a composition (`compose-not-kind` ); a need no composition can meet is an `Embed`
slot drawn by the host, never a new node.
**Compositions are documents, resolved by core.** A composition declares typed
parameters (text, number, bool, `Bind` , `CommandRef` , child slots) and a body of
nodes that reference them. Core expands `Use` by substitution only: there is no
expression language. Compositions never own commands; a composition that needs
an action takes a `CommandRef` parameter, and the command is handled by the
publisher of the document that uses it. `thing.std` provides the common ones
(`metric` , `card` , `pane` , `dashboard` , `rail` , `key-value` , `stat` ), so no
consumer language re-implements them.
**Text form.** Whether Thing Script also has a text syntax beside the typed
document, and which is the authority, is `thing2:question:thing-script-form` .
The node set, compositions and rules here hold under every option.
### 2. Binding (BD1-BD9 collapse to one path)
`Bind { source: StateRef, member: MemberPath, format: Option<Format> }` , where
`StateRef` is `{ provider: Option<ProviderId>, schema: SchemaId, key: RecordKey }`
(absent provider = the document's publisher) or `Item` inside a `Repeat`
template. A bound collection is `{ provider, schema, key_prefix }` (the records of
one type) or a list member of one record. `Format` is closed: number (precision,
percent, unit), duration, timestamp, prefix and suffix text.
| Shape | Fate |
| --- | --- |
| BD1 `stateBindings` | Survives as `Bind` ; `routeKind` , `routeDescription` and `sourceId` die (core routes through discovery, a document names no transport) |
| BD2 local drafts | Die as bindings: a draft is core's editing state of `TextInput` , `Slider` or `Select` |
| BD3 `writeCommand` , `captureBindings` , `draftDirective` | Fold into commands: a `CommandDecl` lists the input nodes it captures; a completed result says whether captured drafts clear |
| BD4 world-root props | Leave the contract: an `Embed` slot whose host owns the world view |
| BD5 `var` , `collection` , `derive` , `bind` | `bind` and `collection` survive as `Bind` and `Repeat` ; renderer-local `var` and `derive` die (P5): derived values are published by the state's owner |
| BD6 whole-document republish | Survives for structure only (`version + 1` ); per-tick republish of values dies |
| BD7 `interface_binding` | Dies (P4): routes are discovery's, the surface is the document |
| BD8 embedded documents | `Embed` |
| BD9 reload and poll | Dies: core subscribes through the CultMesh port |
### 3. Commands (one envelope)
- `CommandDecl { id, payload_schema: SchemaId, captures: [NodeId], confirm: Option<Text> }`
in the Thing Script document. A node may reference only a declared command.
- `CommandInvocation { invocation_id, surface: (key, version), command, payload, captured: [(NodeId, Value)], client, issued_at, delegation }` .
`payload` is a typed record of `payload_schema` . The caller's identity is set by
the transport session and has no field a consumer can write.
- `CommandResult { invocation_id, seq, outcome }` , with `outcome` one of
`Pending { message }` , `Completed { source_version, clear_drafts, effect }` ,
`Denied { reason, node }` and `Failed { reason }` . `effect` is closed:
`OpenSurface(key)` or `OpenExternal { url, resume: CommandRef }` (P7). The
receipt is the result: `command.v1` , `command_receipt.v1` ,
`transientProjection` and `pluginPayload` have no successor.
- Handlers in every language receive a decoded invocation and return an
2026-10-12 07:17:32 +02:00
`Outcome` ; the language package writes the result. No package takes a state
as a string or converts a map into a result. Decoding is the one other door
(every CultCache kind is deserializable, P18): it holds the same domain as the
constructors because each field's type owns its rule. A decoded result can
name any invocation; binding a result to an invocation core issued is core's
validation (`thing:ruling:thing2-one-solver-taffy` ), not the contract's.
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
### 4. Advertisement
What Thing needs from discovery is only "this provider exists and is reachable
here": its surfaces are its `gamecult.thing.surface` records and its commands are
2026-10-10 20:29:31 +02:00
declared inside them. Discovery leaves Thing (`thing2:ruling:advertisement-owner` );
the contract has no advertisement type, and core reads discovery through a port.
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
### Rules that must die under their own mutation
Each is a test or negative check in the cut that lands it; deleting the rule's
code fails it.
1. A result decodes only into `CommandResult` ; a record with an extra or missing
field is refused at decode in every language package (golden fixture with an
extra field).
2. Core turns a result that fails to decode, or one for an unknown
`invocation_id` , into a visible `Malformed` state for that invocation and a
log line; no invocation stays pending past its timeout without one.
3. `outcome` is an enum in every package; no API takes a state as a string.
4. A document with an unknown node kind, an undeclared `CommandRef` , or a
duplicate node id after expansion is refused whole; nothing falls back along
a kind path.
5. A `Use` that does not resolve renders a visible placeholder with a diagnostic;
it is never dropped silently.
6. A `Bind` whose record has a different schema, or whose member is missing,
renders a typed binding error; no value is coerced.
7. Core holds no API that writes typed state; its only outbound write is a
`CommandInvocation` (negative check over the core crate).
8. No backend crate or package depends on `taffy` or `parley` (negative check on
the dependency tree): backends receive positioned commands and shaped glyph
runs.
9. A lower or equal `version` of a document is ignored.
10. Geometry and render-command goldens for the fixture set are byte-identical
across runs; a backend's parity test consumes the core's list and asserts it
drew every command it was given.
### Boundaries
- Consumers depend on the contract packages only. Core, backends and the
standard library are internal.
- Core reaches CultMesh, the clock, fonts and the platform through narrow ports
(traits), so a parser, resolver, layout or command test runs without a daemon.
- Backends draw and bridge: input events in, render commands and the
accessibility tree out. IME uses the platform channel the ruling names (hidden
textarea on the web).
## Thing 1 kinds and their fate
| Thing 1 id (`gamecult.eve.*` ) | Fate |
| --- | --- |
| `surface.v1` , `surface_state.v1` (code only) | Collapse into `gamecult.thing.surface.v1` (P3) |
| `command.v1` , `command_invocation.v1` , `command_receipt.v1` , `command_result.v1` , `operation_payload.v1` | Collapse into `command_invocation.v1` and `command_result.v1` (P2) |
| `local_draft.v1` , `interface_binding.v1` | Die (BD2, P4) |
| `asset_catalog.v1` | Survives as `gamecult.thing.asset_catalog.v1` |
| `provider_advertisement.v1` | Per `thing2:question:advertisement-owner` |
| `plugin.v1` , `plugin_advertisement.v1` , `plugin_abi.request.v1` , `plugin_abi.response.v1` , `plugin_abi_fixture.v1` , `plugin_receipt.v1` , `plugin_witness.v1` , `plugin_handoff.v1` | Die: domain plugins become compositions or `Embed` slots |
| `conformance_*` , `capability_matrix.v1` , `provider_scenario.v1` , `provider_handoff.v1` , `runtime_*` , `local_provider_catalog.v1` | Die: parity is geometry goldens; discovery is not a fixture catalog |
| `unity_*` , `electron_shell_projection.v1` , `web_layout_probe.v1` , `tui_grid.v1` | Die: renderer outputs are not contract kinds |
| `entity_soa_view.v1` , `input_bindings.v1` , `input_capability.v1` | Die (no carrier outside the kernel, B25) |
| `mimir.eve_*` , `cultmesh.eve_surface.v0` | Mimir's own ids, renamed by Mimir's port when it is revived (Mimir is archived) |
## The authority map
- **Owner of geometry, text, focus, editing, accessibility, binding resolution
and result validation:** Thing core (`one-solver` , `core-owns-text` ,
`live-state-core` ).
- **Owner of a surface's content and of its commands' outcomes:** the provider
that publishes the document.
- **Owner of the contract types:** the contract crate; each language package is
held to the crate's golden wire fixtures and owns nothing of its own.
- **Derived:** the resolved tree, layout, render-command list, accessibility
tree and goldens. Viewer session state is core-owned and never published.
- **Demoted:** the browser is no longer the layout oracle; renderers are no
longer owners of kinds, drafts, pointer resolution or result validation; the
text DSL's local mesh is no longer a writer of state; `surface_state` ,
`interface_binding` and the receipt are no longer documents.
- **Forbidden writers:** a backend computing layout or shaping text; a provider
writing a result through anything but the package constructor; core writing
typed state; a document naming a transport; any compatible eve id; a new node
kind added for one domain.
- **Shared paths:** every viewer (web shell, Ghostlight's client, TUI, future
hosts) embeds the same core; every consumer language publishes through a
package held to the same goldens.
- **Deletion line:** Thing 1's tree in the kernel repo, its six family repos and
every `gamecult.eve.*` reader and writer in the ported consumers. Each port
deletes its consumer's Thing 1 path in the same cut.
## Cut order (deliverables)
0. * * `stale-voidbot-cleanup` ** (Eve main, any time): spec
`thing2:cut_spec:cut-stale-voidbot-cleanup.r1` . Owned here by
`thing:follow_up:stale-voidbot-cleanup-on-main` .
1. **Contract ** (code home per `thing2:question:code-home` ): the contract crate
with the types above, published schemas `schemas/gamecult.thing.*` , golden
wire fixtures, and the TypeScript and C# packages held to them (the live
2026-10-10 20:29:31 +02:00
consumers' languages: Rust, JS/TS, C#). The first proof (P9) was run: a
Rust-written recursive union does not decode in the C# reference, nor the
reverse (P12), and TS decodes only what a hand-written codec says (P13). The
contract waits on CultLib's union parity
(`thing2:follow_up:cultcache-union-parity` ). Three cuts: `contract-rs` , then
`contract-ts` and `contract-cs` . The published schema of each kind is its
CultCache catalog entry, identical in the three packages; no JSON Schema
files are written, because Thing 2.0 has no JSON reader of these kinds (the
browser reads them through core, not `ajv` ). Python and Kotlin packages
follow as their own deliverables.
2026-10-12 07:17:32 +02:00
`contract-rs` attempt 1 landed on `hands/thing2-contract-rs` (`649844aa` );
Soul's `thing2:verdict:cut-contract-rs.s1` returned seven findings. Its fix
batch is `thing2:cut_spec:cut-contract-rs.r2` . Waiting, one line each:
- `contract-ts` , `contract-cs` : their r1 specs predate r2's domain types,
key grammar and refusal goldens, and are refreshed when the frontier
reaches them.
- CultLib pin bump: when `cultlib-gaps:follow_up:decode-stack-safety` lands,
move the pin and run `deep_tree` on the default thread (its 16 MiB thread
goes); the crate docs' hazard note goes with it.
- Missing-slot golden: re-added only when
`cultlib-gaps:follow_up:rust-missing-slot-vs-reference` settles the rule.
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
2. **Core ** , in three cuts: (a) resolution, compositions with `thing.std` ,
binding through the CultMesh port, Taffy layout, render commands, the command
2026-10-12 07:17:32 +02:00
dispatcher and result validation, goldens; (a)'s decode of Verse surfaces
waits on `cultlib-gaps:follow_up:decode-stack-safety` ; (b) Parley text, focus, editing and
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
IME state; (c) the AccessKit tree.
3. **TUI backend ** (Rust): cell measurement in the Taffy measure function,
terminal drawing, keyboard input. First, because it exercises the whole
pipeline with no platform bridge and gives agents a reader.
4. **Web backend and shell ** : core to wasm, a thin JS host (canvas drawing,
hidden-textarea IME, DOM accessibility mirror), the discovery client, and the
web shell that replaces the Nightwing browser reference.
5. **Consumer ports ** , one cut per live consumer, each carrying its rename and
deleting its Thing 1 path (`consumer-port-carries-rename` ). Cutover shape per
`thing2:question:port-cutover` . Proposed order: Odin (reads Thing 2 kinds),
VoidBot (richest binding, one command), Ghostlight with Heimdall (text inputs,
the auth effect, the flat-result case; two cuts, Heimdall's access gate
becomes a composition), Muninn and sleipnir, weksa, Stonks, Vili, Gjallar,
StreamPixels, then gamecult-ops (the Nightwing target), then CultLib (its eve
document definitions in `cultcache-ts` and `cultmesh-kotlin` are deleted, B26).
These need more repos than the campaign lists: `thing2:question:campaign-repo-limit` .
2026-10-10 20:29:31 +02:00
6. **Retirement ** : delete Thing 1's tree from the kernel repo. The renames,
archives and site prose are re-specified as `thing:cut_spec:cut-repo-renames.r2`
and `cut-site-prose.r2` and do not wait for it (P14).
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
**Repos this map retires:** EveUnity, EveFlutter, EveElectron, EveTui, EvePlugins
and EveConformance (no Thing 2.0 role: no domain kinds, no per-renderer
2026-10-10 20:29:31 +02:00
lowerers, parity by goldens). They are archived under their Eve names
(`thing2:ruling:retired-repo-names` ) by `thing:cut_spec:cut-repo-renames.r2` ,
ahead of retirement, because nothing live builds from them (P14). The kernel
repo is renamed Thing in the same cut (`thing2:ruling:code-home` ); its URLs in
gamecult-ops stay on the redirect until the port that owns each one (P15).
AetheriaEve stays under its rulings.
Thing 2.0 map: model page, consumer contract, cut order; fork prior art
Imagination pass imagination-thing2-contract (session self-2026-10-10c-eureka).
docs/thing2-map.md holds body facts P1-P11, the model page, the consumer
contract (closed node set, compositions, one binding path, one command
envelope), the fate of every Thing 1 kind, the authority map and the cut
order by deliverable. docs/research/thing2-contract-forks-prior-art.md holds
the prior art for the forks code-home, thing-script-form,
advertisement-owner and port-cutover.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:06:28 +02:00
**Parked (dormant or blocked) consumers:** Bifrost, repixelizer, Mimir, Hermodr,
Loki, Sai, AquaSynth, Brokkr, Ymir. Their Thing 1 pins keep resolving at the
archived repos; each ports when it is next revived. Unity and Flutter backends
have no live consumer and are parked until one appears.
## Rationale
- **Why the contract before core.** Every later cut, and every consumer port,
depends on the types; the cross-runtime decode of a recursive union (P9) is the
riskiest unknown and is cheapest to find before core exists.
- **Why TUI before web.** It has no platform bridge, so the first end-to-end run
tests core, not a host; the web backend then adds only drawing, IME and the
accessibility mirror.
- **Why compositions are resolved by core, not expanded by each language's
builder.** Builder-side expansion would put `metric` , `card` and every shared
composition in each consumer language, which is Thing 1's per-renderer lowerer
problem moved to the publishers.
- **Why no expression language in documents.** P5 shows where it leads: the
renderer becomes a second owner of derived state. Owners publish derived
values; `Format` covers presentation.
- **Why Odin ports first.** It is the aggregator every viewer discovers
through; under either cutover option it must read Thing 2 kinds before a
provider publishes them.
- **Why the result has no free-form payload.** P2 and P7: the only live uses of
`pluginPayload` and `transientProjection` were a navigation and a status,
which are `effect` and `outcome` .
2026-10-12 07:17:32 +02:00
- **Why each integer's rule lives in its field type.** The decode door cannot be
closed (P18), so a rule held only by a constructor is a rule the wire skips.
A newtype per domain (`Seq` , `Version` , `Count` , `Segment` ) is read by both
paths, keeps the bytes, and gives the TS and C# packages one refusal golden
each to mirror. This is serde's `try_from` pattern, written by hand because
the CultCache derive emits the codec itself.
- **Why the key grammar constrains only the local segment.** Provider ids and
publishers are owned by discovery (`thing2:ruling:advertisement-owner` ) and
are dotted in the Verse today (P19); the contract owns only `label` , `name`
and `catalogId` . Refusing the separator there makes every key split at its
last dots, the way DNS labels and the mind's own `<root>:<kind>:<local>` keys
stay unambiguous. A different separator between provider and label would
instead impose a grammar on ids the contract does not own. Listing a
provider's records filters on the record's provider field, not a key prefix,
because a dotted provider is a prefix of a longer one.
- **Why contract-rs merges before CultLib's stack fix.** The overflow is the
decode door's (`cultlib-gaps:follow_up:decode-stack-safety` ) and every Rust
reader of these kinds inherits it whether or not the contract merges. The
contract decodes no hostile input; core's Verse decode does, so that is where
the wait sits (cut 2a). The crate docs name the hazard meanwhile.