Skip to content
← Documentation

docs/world-vocabulary.md


The World type catches up with the ontology

Item 1. BuildingKind and DistrictKind are gone — split, not widened, not renamed.


Measured first

Before replacing anything, what the domain furniture was actually used for:

building.kind reads    WorldScene.tsx:282   kind === "lot"      → geometry
                       page.tsx:69          kind === "service"  → a counter
                       WorldView.tsx:210    displayed as text
district.kind reads    page.tsx:70          kind === "product"  → a counter

Eight enum members, and the 3-D renderer read exactly one bit.

And that one bit drove five properties — colour, emissive, transparency, opacity, wireframe — all saying the same thing: this is reserved ground, not a built thing.

lot is not domain furniture. It is a presence state, and it belongs beside Placement.unlocated and unstaffed: claims about what is and is not there, independent of what field the subject comes from.

The split

type Presence = "realised" | "reserved";   // the ONLY categorical distinction geometry may read

Building.presence: Presence
Building.subtype:  string   // connector-declared; display and provenance only
District.subtype:  string   // DistrictKind removed entirely — it drove no geometry

This is not a new vocabulary. It is the rule core/subject.ts already states"carries display and provenance and never identity semantics" — finally adopted by the layer above it.

The seven remaining members (hall, department, service, datacenter, library, security, conference) survive as subtype strings, which is what they always were: labels for one organisation's furniture, feeding counters and an inspector. Nothing was renamed and nothing was lost.

The coercions are gone

coercions declared:

Empty. A generated world now says subtype: "file" and subtype: "directory" because that is what was observed. Previously a file had to claim it was a "service" and a directory a "product".

COERCIONS is kept as an exported list asserted empty, so a future coercion must be declared there rather than absorbed.

Nothing moved

composed world hash: 0305564a… → 563c278c…

The golden-hash test caught the change, which is what it is for. No geometry moved — every position, footprint and height is byte-identical. The hash differs because two field names became three, and the reason is recorded in the test beside the hash.

Generation still works, and the estate is unchanged:

Enterprise XI  → districts 43 · buildings 113 · agents 2 · links 1327
xi-hq          → districts 29 · buildings 132 · agents 1 · links  849

Mutation harness: all 40 defects caught by their intended invariant, working tree clean.


Standing

Enum expanded? no
Furniture renamed? no — it became subtype, which is what it was
Measured before repairing? yes — 8 members, 1 geometry read
Coercions remaining 0

What is still not established

  • Item 2 is not done. app/page.tsx still consumes deriveStructure; the generated world reaches only the harnesses. That is now unblocked — the World vocabulary no longer forces a lie — and it is the next thing.
  • Presence is asserted to be the only distinction geometry needs. True of this renderer today, measured. A renderer for a different domain might need another abstract distinction, and nothing here bounds that.
  • page.tsx counters now filter on subtype === "service" and "product"string comparisons against one domain's vocabulary, in the application layer. That is the right layer for it, and it is still a domain assumption, now visible rather than in the type.
  • The other listed abstractions — relations, measures, temporal facts, provenance — were not touched. Building still carries height, activity and accent as pre-derived geometry rather than magnitudes the renderer projects. That is the deeper form of item 1 and remains open.

231 tests, 26 DOM, 40 mutations, tsc clean.