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.tsxstill consumesderiveStructure; 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. Presenceis 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.tsxcounters now filter onsubtype === "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.
Buildingstill carriesheight,activityandaccentas 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.