docs/visualization-invariant.md
Design Invariant — Truth-Preserving Visualization
Standing constraint on all future architecture. Not a task; a test to apply to every architectural decision from here.
The visualization layer is not an illustration of the system. It is a deterministic projection of the system's semantics.
Facts → Properties → Metrics → Geometry → Rendering
Each layer derives only from the layer above it.
The renderer has no semantic authority. It must never invent geometry, infer meaning, cluster entities, or choose relationships. It consumes geometry already determined by the metric layer.
Metrics are renderer-independent. They must remain meaningful if no visualization exists. Visualization is one consumer among reasoning, simulation, search, optimization, and analysis.
Every visual primitive has a mathematically defined inverse. For any position, distance, size, colour, opacity, clustering, orientation, or motion, the system must answer "why does this appear exactly this way?" with a complete derivation:
Visual Primitive ← Geometry ← Metric(s) ← Property(ies) ← Recorded Fact(s)No visual element may exist without an evidence chain, and every metric must exist because it expresses a property of the system — not because it produces an attractive visualization.
The test for every future decision: will this still permit geometry to be derived entirely from metrics, with complete traceability back to recorded facts? If no, the architecture is wrong regardless of whether it functions.
The layer contract
The five layers are distinct and must not collapse into one another.
- Property — an intrinsic fact about the system: ownership, dependency, entropy, trust, causal influence, load, volatility. It is true whether or not anyone measures or draws it.
- Metric — a mathematical function over one or more properties.
- Geometry — derived only from metrics.
- Rendering — projection only. The renderer never computes meaning. Its contract is intentionally dumb.
Every metric declares four things
A metric definition must answer all four, and the renderer may answer none of them:
- What facts produce it?
- What mathematical function computes it?
- What units does it inhabit?
- Which geometric degrees of freedom may it control?
Question 4 is directional and the direction matters: the metric declares which geometry it may drive. Geometry claiming a metric is the same edge pointing the wrong way, and it lets any geometric property help itself to any measurement.
Milestone — semantic closure precedes metric design
Metric definitions may not begin until the Legend completely and correctly describes the World contract. Until then the metric layer would be built on shifting semantics, and metrics would encode a moving target rather than an enduring property.
Semantic closure means, at minimum: every visual property the World produces is declared in the Legend; every property the Legend declares is produced; and each declares its source. Gaps 3 and 6 below are the measured distance from it.
Baseline — measured, not assumed
The invariant is stated above as given. What follows is where the current system stands against it, measured before any design work, so the metric layer is designed against reality rather than against an assumption.
Gap 1 — the renderer invents geometry
src/components/hq/CityMap.tsx:40
const SPIRE_H = 252;
City Hall's spire height is a renderer constant. It has no metric, no property, and no fact behind it. Asked "why is the spire that tall?", the system's only answer is "a renderer chose 252".
The boundary worth keeping precise: computing the vertices of a box whose height
was given is projection and is permitted. Choosing the height is invention
and is not. SPIRE_H is the second kind.
Gap 2 — visual properties with no inverse
accent appears on District, Building, and WorldAgent in the World contract. It
originates as a hardcoded hex in src/lib/hq/roster.ts (accent: "#8ea2ff"),
reaching products via accentForOwner. health is the same shape.
Neither appears anywhere in the fact vocabulary (src/lib/world/facts.ts). So
colour — which the eye reads as meaning — has no derivation. It cannot be
inverted, because there is nothing to invert to.
Gap 3 — the constitutional legend and the World have drifted apart, in both directions
src/lib/world/constitution.ts carries the LEGEND, and Article VIII.1.2 declares
it exhaustive: "A visual property absent from it may not be introduced."
| Declared in the legend, never produced by the World | campusTiers |
| Produced by the World, absent from the legend | accent, health, radius, center, load, progress |
Nothing checks either direction. This is the general form of the defect that
W5 found in miniature: the legend's quantised: true flag is declared on all
nine rows and read by nothing. The legend is a constitutional file describing
a system that does not exist — in one direction it promises what is absent, in
the other it is silent about what is present.
Gap 4 — there is no properties layer
src/lib/world/facts.ts exports fact types and nothing else. Between recorded
facts and geometry the system has an undifferentiated middle: no type, no
module, and no vocabulary corresponds to a property.
Gap 5 — property and metric are collapsed inside one function
deriveLoads in src/lib/world/build.ts performs both steps in one pass:
load.weight += priority * (item.state === "blocked" ? 2 : 1); // PROPERTY
// "Normalise against the busiest DEPARTMENT" // METRIC
The first line is an intrinsic claim about the system — an agent's queue weight, true whether or not anything draws it. The second is a function over that property producing a 0–1 value for geometry.
The cost of the collapse is visible in the first line: why is blocked work worth double? That is a semantic judgement about the domain, and it is currently buried inside what reads as a normalisation utility. Under the layer contract it belongs in the property definition, where it can be stated, questioned, and cited.
Gap 6 — the metric ↔ geometry binding points the wrong way
src/lib/world/structure.ts:43
| { kind: "measure"; metric: string; base: number; scale: number }
Geometry names the metric it consumes. The layer contract requires the
reverse: the metric declares which geometric degrees of freedom it may control.
As built, any geometric property may claim any measurement, and the measurement
has no say — which is how code-size came to drive building height (II.2)
without anything objecting.
Not a gap — units are already enforced, in core
Question 3 of the contract is already satisfied upstream. core/ingest.ts
requires every manifest metric to declare a unit and rejects the manifest
outright if it does not:
if (!m.unit || m.unit.trim().length === 0) {
throw new ManifestRejected(`${manifest.id}: metric '${m.metric}' declares no unit.`);
}
The legacy MeasureFact carries subjectId, metric, value, limit?, enforced?, evidence — no unit. So the unit declaration exists and is mechanized in
core, and was lost on the way into the legacy stack. This is a migration gap,
not a design gap, and it means one of the four questions needs no new
machinery — only the migration already underway.
Design implication
The metric layer's purpose, stated in the invariant's own terms, is to supply the middle of the chain so that every visual primitive becomes invertible. The legend is where that inverse is declared — it is the mapping from visual property to what it represents and which source it derives from.
So the metric layer cannot be designed against the current legend, because the legend does not describe the system. Reconciliation is prerequisite, and it decomposes into three decisions rather than one:
- Which visual properties exist at all.
accentandhealthare either promoted into the legend with declared sources, or removed as visual properties. Under VIII.1.2 the legend is exhaustive, so leaving them undeclared is not a third option. - What units geometry is quantised into. Already pending — the floor and
tier units blocking II.2 and II.3 (see
tripwires.mdW5). - Whether identity-only properties are exceptions or metrics. Footprint is
already a declared exception to Article I.1 — assigned by kind, never
derived.
accentis plausibly the same kind of thing, but it has never been declared as such. The invariant permits declared exceptions; it does not permit undeclared ones.
Decision 2 is already with the ratifying authority. Decisions 1 and 3 are new and follow from this invariant.
Status
Recorded, not implemented. Per the instruction accompanying it: the metric layer is to be designed once the ontology, constitutional semantics, and mechanization are stable enough that metrics represent enduring properties rather than moving targets. Gap 3 is direct evidence that they are not yet stable — a legend that disagrees with the system in both directions is the definition of a moving target.
The three gaps above are the measured baseline any future metric design must close, and they are recorded here so that design starts from measurement.
North star
The visualization is not an illustration of the system. It is the system projected into geometry.
Every visible primitive must admit an exact inverse:
pixel ← geometry ← metric ← property ← fact
If that chain cannot be reconstructed precisely, the visual element either lacks sufficient evidence or belongs outside the metric-driven visualization.
That is the standing test for the entire visualization architecture, and it is deliberately stronger than "looks right". A chain that reconstructs is auditable, explainable, and reproducible; one that does not is decoration with better manners.
Elimination attempt — the Properties layer
Applied under foundation.md §2.0, which requires an abstraction to earn its
existence. Recorded with the rigour of an admission, because discovering a layer
is unnecessary is worth more than successfully adding one.
Method. Take the seven properties named when the layer was proposed — ownership, dependency, entropy, trust, causal influence, load, volatility — and ask of each whether it is a recorded Fact, a function over Facts (a Metric), or genuinely neither.
| Candidate | Verdict | Evidence |
|---|---|---|
| load | Metric | build.ts:181 — load.weight += priority * (blocked ? 2 : 1), a function over WorkItemFacts |
| entropy, volatility, causal influence | Metric | functions over facts by construction |
| trust | absent | not present in this system; no evidence either way |
| ownership | neither | estate.ts:39 — owner: AgentId, declared in a connector register |
| dependency | neither | estate.ts:48 — dependsOn?: string[], likewise declared |
Five of seven collapse into Metrics outright. The layer holds nothing for them.
The two that do not collapse — and why they still do not justify the layer
ownership and dependency are declared relations between subjects. They
are not functions over facts, so they are not Metrics. But they are also not
Facts in core's sense: FACT_KINDS is ["condition", "measure", "exchange", "placement"], and a standing relation is none of those. exchange is movement
between subjects over time; placement is location. Neither expresses "X
belongs to Y" as a durable relation.
So there is a genuine expressive gap. It is not a missing layer. Under §2.0 rule 3, an abstraction must reduce overall complexity rather than redistribute it, and adding a layer above the fact vocabulary to hold what the fact vocabulary cannot express is redistribution by definition. The minimal fix, if one is needed, is at the fact layer — whether core's four families can express a standing relation. That is a lower-layer question and belongs to the core ontology, not to visualization.
Three steelmen, all failing
"A property is true whether or not it is observed; a fact is a recorded observation." This is the strongest case for the layer, and it fails against existing constitutional text. Article I.2: "Nothing exists in the world that cannot be explained by data." Article I.5: absence is represented, never hidden. The architecture deliberately refuses to model unobserved truth — an unmeasured condition is a Gap or an absence, not a hidden property. A layer holding unobserved truth would contradict Article I rather than extend it.
"Properties is where semantic judgement lives" — e.g. why blocked work
counts double. It fails because the metric contract already owns that: question
2 requires a metric to declare what mathematical function computes it, and
priority × 2 if blocked is exactly such a declaration. Gap 5's finding stands
— deriveLoads collapses two layers — but the repair is to declare the function
in the metric, not to add a layer above it.
"Metrics must be meaningful without visualization, so something must hold renderer-independent truth." Facts are already renderer-independent. This distinguishes nothing.
Result
The Properties layer does not currently earn its existence. The minimum model is:
Facts → Metrics → Geometry → Rendering
and the provenance chain shortens accordingly:
pixel ← geometry ← metric ← fact
The north star is the chain's completeness, not its length. A shorter chain that reconstructs exactly is strictly better than a longer one with a layer that holds nothing.
Provisional in both directions. This elimination is itself evidence-bound. If a measured ambiguity appears that Facts and Metrics cannot express between them, §2.0 admits the layer on the same terms as any other abstraction. What is recorded here is that no such ambiguity has yet been measured — not that none exists.
Open question, recorded rather than answered: can core's four fact families
express a standing relation between subjects? ownership and dependency
currently live in a connector register and are invisible to the fact
vocabulary. That is a core-ontology question, and it is the one thing this
elimination could not dispose of.
Gap 7 — the provenance chain is broken at two points
Found by re-testing the Properties elimination against explanatory sufficiency rather than minimality. Under the older criterion this was recorded as an open ontology question; under the newer one it is a live violation of the north star, because the test is no longer "does anything land in this layer?" but "can every rendered primitive be inverted back to evidence?"
Both ownership and dependency reach the render layer without passing through
a Fact or a Metric:
stroke colour ← accent ← accentForOwner(product.owner) ← connector register
structure.ts:248 estate.ts:39
rendered link ← Link{kind:"data"} ← product.dependsOn ← connector register
structure.ts:296-302 estate.ts:48
Consumed at CityMap.tsx:143, :144, :235 as gradient stops and stroke.
Asked "why is this district that colour?", the chain terminates at a hardcoded hex in a register — not at evidence. Asked "why is there a line between these two services?", it terminates at a hand-written array. Neither inverts.
This does not resurrect the Properties layer. §2.0 rule 3 requires an abstraction to reduce complexity rather than redistribute it, and a layer placed above the fact vocabulary to hold what the fact vocabulary cannot express is redistribution by construction. The repair is at the fact layer: whether core's four families can express a standing relation between subjects.
What changed is the finding's status. It is not a curiosity about where a concept sits; it is two rendered primitives with no inverse.
Re-test of recorded verdicts under explanatory sufficiency
Every §2.0 verdict was reached under "smallest architecture". Re-running them against the refined criterion, because a changed measure should not be assumed to preserve prior conclusions.
| Verdict | Survives? | Under the refined criterion |
|---|---|---|
| K eliminated | yes, with a better reason | Classification is a governance act, not an observation of the estate. It lies outside the chain's domain, so removing it costs no reconstructability. The old reason — "the trace reading absorbed it" — was true but weaker. |
| X.3 withdrawn | yes | Added no explanatory power; constrained process only. Independently refuted by the Article IX counterexample. |
| Liveness admitted | yes, strengthened | It expresses "the process never terminates", which no other class could state at all. Explanatory power gained; cost is one lattice cell. This is what "indispensable" looks like. |
CapacityFact failing |
yes, stated more precisely | Removing it preserves complete reconstructability — MeasureFact{limit, enforced} expresses every field. It therefore redistributes responsibility without increasing explanatory power, which is the exact definition of an abstraction to eliminate. |
| Properties eliminated | yes, but the residue is upgraded | The layer still holds nothing that Facts and Metrics cannot. But its two non-collapsing candidates turn out to reach rendering, which makes them Gap 7 rather than an open question. |
No verdict reversed. One was strengthened, one gained a better justification, and one exposed a defect that minimality could not see — which is itself evidence that the refined criterion is the more discriminating of the two.
Exhaustive reconstruction audit
Every prior gap was found by looking at something specific. This enumerates every field the renderer reads and traces each chain, so the baseline is complete rather than opportunistic.
Measured by extracting all property accesses in CityMap.tsx:
| Primitive | Reads | Chain | Status |
|---|---|---|---|
accent |
24 | none | not in Legend; no chain |
blocked |
5 | ← WorkItemFacts | complete |
depth (queueDepth) |
2 | ← WorkItemFacts | complete |
workload (load) |
1 | ← WorkItemFacts | complete |
x, y (position) |
8 | ← plot register (plots.ts) |
declared exception, Legend source structure |
kind, code, domain, glyph |
14 | ← roster | identity labels |
id |
19 | — | identity, not a visual primitive |
accent accounts for 24 reads — more than the three complete chains combined
(8) — and is the only primitive that is neither chain-complete nor
Legend-declared. The most-used visual property in the system is the least
grounded one.
The decisive finding — accent duplicates what position already declares
The Legend's own rows:
{ visual: "footprint", represents: "identity and architecture", source: "structure" }
{ visual: "position", represents: "ownership and topology", source: "structure" }
{ visual: "height", represents: "cumulative operational output", source: "output" }
{ visual: "illumination", represents: "current activity", source: "activity" }
The Legend assigns ownership to position. And accent derives from
accentForOwner(product.owner) — so colour represents ownership as well,
undeclared.
That makes accent not merely ungrounded but duplicative: two visual
properties carrying one meaning.
This is decidable under existing articles — no amendment required
- Article I.4 — "One fact has exactly one representation. If two things can disagree, one of them is wrong by construction." Ownership is represented twice, by position and by colour. I.4 rejects it.
- Article VIII.1.2 — the Legend is exhaustive; "a visual property absent
from it may not be introduced."
accentis absent. VIII.1.2 rejects it.
Two independent articles reject accent in its current form. The Constitution
has an opinion, and it needs no new text to express it. This moves accent from
unresolved gap to resolved by existing article, repair pending.
The repair is partly blocked. Ceasing to derive accent from ownership is an
implementation act. Retaining colour as a declared identity property — the
footprint treatment, an explicit exception to I.1 — requires a Legend row, which
is a constitutional file and therefore blocked while an amendment is pending.
Terminal condition: neither A nor B
Not Constitutional Falsification (B). Every measured primitive is accepted or rejected by an existing article. Nothing yet requires new constitutional text.
Not yet Constitutional Closure (A). Chains remain open, and the repairs are blocked behind governance rather than undecided:
| Primitive | Decided by | Repair blocked on |
|---|---|---|
accent |
I.4 + VIII.1.2 (rejected) | Legend row → constitutional |
data links (dependsOn) |
I.1 (rejected) | fact-family question |
SPIRE_H |
I.1 (rejected) | Legend row → constitutional |
height from code-size |
II.2 (rejected) | floor/tier units decision |
Four open chains, four existing articles deciding them, zero requiring an amendment. The distance to closure is decision and repair, not theory.
Audit revision — two self-corrections, and a located impossibility
Re-testing the audit against the complete Legend (all nine rows, not the four a truncating grep returned) overturns two findings recorded above.
Correction 1 — data links are declared; Gap 7 was half wrong
{ visual: "roadExistence", represents: "declared relationship", source: "structure" }
Link existence is in the Legend, and its declared source is structure — an
explicit exception of the same kind footprint and position hold. The data link's
evidence string reads "declared dependency in the estate register", which is
exactly what the Legend says it should be. Data links are constitutional and
honestly sourced. Gap 7's claim about them is withdrawn.
Correction 2 — position does not represent ownership; the I.4 finding was wrong
The Legend claims position represents "ownership and topology". Measured:
export function assignSlots(ids: string[], capacity: number,
recorded: Readonly<Record<string, number>> = {})
assignSlots structurally cannot see ownership — it receives ids, a
capacity, and a register. Products are placed by FNV-1a hash of their id. That
owner z's two products landed on plots 3 and 4 is coincidence, not design.
So accent does not duplicate an existing representation of ownership. It is
the only representation. The Article I.4 finding recorded above is withdrawn.
What replaces it is different and still decidable: the Legend's position row
is false. Position does not represent ownership. That is an Article II
semantic-correctness violation, and it is decided by existing text.
The reduced picture
Against all nine Legend rows, exactly one rendered primitive is undeclared:
accent. (health is undeclared in the World contract but is not read by the
renderer, so it is a contract defect rather than a rendered one.)
Measured constitutional impossibility — precisely located
accent's chain has two ungrounded terminals:
- The ownership relation —
estate.ts:39, a declared register entry. Groundable by emitting it as a fact. - The colour value itself —
roster.ts:76,accent: "#8ea2ff". It appears in no fact vocabulary, in core or legacy. A hue is not a measurement of anything; it is an identity assignment.
Grounding terminal 1 does not close the chain, because terminal 2 remains. Therefore a relation fact family — the abstraction that first suggested itself — fails §2.0 rule 2 for this purpose: it would not resolve the measured ambiguity. It is rejected on measurement, not taste.
Terminal 2 has exactly one constitutionally-valid repair: declare accent as an
identity-only property in the Legend, the treatment footprint already receives
as "a deliberate exception to Article I.1, explicitly declared as such". The
Constitution also already accommodates presentational values through Article VI's
ASSUMED_CONSTANTS, where walk-duration-ms is recorded with the basis
"presentational: how a journey is shown, not measured".
Both mechanisms live in src/lib/world/constitution.ts — a constitutional file
under Article VIII, which Article X forbids modifying while an amendment is
pending.
This is not lack of implementation. The implementation is identified, precedented, and constitutionally forbidden at this moment. The blockage is governance, and it is located at a single file and a single pending decision.
Exhaustive rejection proof — no existing mechanism terminates the accent chain
The claim "accent is the only primitive without a chain, and only a Legend amendment can close it" is treated here as a falsifiable hypothesis. Every existing mechanism is enumerated and rejected with evidence.
A. Core fact families
| Candidate | Rejected because |
|---|---|
condition |
A closed 20-member vocabulary of judgements. A hue is not a judgement about state. Extending it is the subject of the pending amendment. |
measure |
core/ingest.ts throws ManifestRejected if a metric declares no unit. A hue inhabits no unit. Rejected mechanically, not by argument. |
exchange |
{moved, from, to} — an event of movement. Colour does not move. |
placement |
{subject, at} — location. Colour is not a location. |
B. Legacy fact families
PlacementFact, ActivityFact, IncidentFact, ExchangeFact, MeasureFact,
CapacityFact, WorkItemFact. Each narrows a core family and inherits its
rejection. MeasureFact is the only near-miss and fails on semantics: its field
is value: number, a quantity, and a hue is not a quantity of anything.
C. Identity vocabularies
SUBJECT_KINDS, ConditionId, FACT_KINDS all participate in persisted
identity (conditionIdentity, urn, factIdentity). Admitting colour would
place it inside every identity string and fork every stored key — the precise
hazard Article XIII exists to prevent. subtype is open and connector-declared,
but names what kind of thing a subject is, not how it appears.
D. Presentation constants — the closest existing mechanism
Article VI's ASSUMED_CONSTANTS genuinely accommodates presentational values:
{ id: "walk-duration-ms", value: 6000, where: "substrate/runtime",
basis: "presentational: how a journey is shown, not measured", assumed: true }
It nonetheless fails twice. Type: AssumedConstant.value is number; a hex
colour fits only by coercion. Semantics: its own doc comment scopes it to
"every normalisation ceiling, threshold and band" — a hue is none of those.
And decisively, ASSUMED_CONSTANTS lives in src/lib/world/constitution.ts,
so even a successful fit would be the blocked constitutional act.
E. Constitutional exceptions
Article II.1 grants footprint an exception: "a deliberate exception to Article
I.1 and is explicitly declared as such." The operative words are explicitly
declared — footprint's exception exists because it holds a Legend row with
source: "structure". The exception mechanism is not available independently
of the Legend; invoking it is the Legend amendment.
F. Deriving hue from an already-grounded primitive
Deriving accent from activity would close the chain — and change what colour
means, from ownership to activity. illumination already represents activity,
so the result is an Article I.4 duplication. health is itself undeclared.
Rejected: a closed chain to the wrong meaning is the failure already recorded
against reconstructability.
G. Subsumption under an existing Legend row — and a new defect
The remaining candidate: eliminate accent by making position represent
ownership, which the Legend already claims it does. That would need no Legend
change and would classify as implementation under IX.3.
It is unsatisfiable, and the evidence is in the declarations themselves:
{ visual: "position", represents: "ownership and topology",
source: "structure", quantised: true, changeRate: "never" }
Article III.3 — "Buildings never move. Their plot is assigned once and held
for life."
Position is declared to change never. Ownership carries no immutability guarantee anywhere — it is a mutable field in a connector register, protected by no article and no test. Two things with different change rates cannot remain in faithful correspondence. Position therefore cannot represent ownership without violating Article III.
That is a second Legend defect, distinct from the first: the position row does
not merely fail to represent ownership today, it declares a correspondence
that Article III makes impossible to maintain. Decidable under Article II, and
requiring the same blocked file.
Conclusion
All seven categories exhausted; every candidate rejected on measured evidence.
The accent chain requires a Legend amendment — specifically a row
declaring colour an identity-only property, the treatment footprint holds.
This is not Constitutional Falsification. The Constitution adjudicates
accent without difficulty — VIII.1.2 rejects an undeclared visual property,
and the remedy is an ordinary amendment rather than new theory. What is blocked
is the act, not the reasoning.
Reconstruction completeness — achieved for the SVG renderer
The rejection proof above concluded a Legend amendment was necessary. That
conclusion was wrong, and the error was in its premise: it treated accent as
a terminal semantic object. Modelled instead as a deterministic transform of
identity, the chain closes with no constitutional action.
The derivation that survived
Seven deterministic derivations were enumerated. Six were rejected:
| Rejected because | |
|---|---|
f(activity) |
illumination already represents activity — I.4 duplication |
f(health) |
health is itself undeclared |
f(kind) |
footprint already represents "identity and architecture" — I.4 |
f(load) |
department height is heightRule {kind:"load"} — I.4 |
f(owner) |
the World carries no owner field, measured |
| fixed palette | the palette hexes remain hand-chosen; the terminal survives |
One survived: hueFor(id), a pure function of the entity's own identity.
The precedent is already in the repository. code (4 renderer reads),
domain (3) and glyph (2) are rendered, identity-derived, and hold zero
Legend rows — uncontroversially. So VIII.1.2's "visual property" means a
channel that encodes state, not everything visible. Colour that claims only
"this is that entity" joins the label category and needs no source
declaration. hash32 was reused rather than reinvented; it already maps
identity to plots and was chosen there for cross-runtime stability.
Audit re-run
| Primitive | Reads | Chain |
|---|---|---|
accent |
24 | hueFor(id) ← id — closed |
blocked, depth, workload |
8 | WorkItemFacts — closed |
x, y |
8 | plot register, Legend source: "structure" — closed, declared |
kind, code, domain, glyph |
16 | identity labels — label precedent |
id, key, queue |
— | not visual primitives |
Zero hex literals remain anywhere in src/lib outside the constitutional
file. 69 tests green, 9 architectural mutations caught.
Gap 1 resolves by the same principle
SPIRE_H = 252 was recorded as renderer-invented geometry. The sharpened test:
a visual value needs a provenance chain iff it VARIES with its subject.
SPIRE_H is constant across every composition — it encodes nothing, exactly as
stroke width and the SATURATION/LIGHTNESS constants in hue.ts encode
nothing. accent needed a chain precisely because it did vary per agent.
Gap 1 is withdrawn.
What this deliberately gave up
Colour previously encoded ownership — two products of one agent shared a
hue. That mapping had no evidence chain, no Legend row, and no available
carrier: Article III declares position changeRate: "never" while ownership is
mutable. The signal is not hidden; ownership remains in the estate register and
in the inspection panels. It is no longer asserted by a channel that could not
justify it.
What remains open — none of it reconstruction
| Article | Blocked on | |
|---|---|---|
| height derives from code-size | II.2 | floor/tier units |
Legend position row claims ownership falsely |
II | Legend row |
campusTiers declared, never produced |
II | Legend row |
health undeclared in the contract (unrendered) |
VIII.1.2 | Legend row |
All four are semantic correctness, not reconstructability. The provenance chain is complete; what remains is whether the chains terminate at the right evidence.
Renderer-level audit — the methodology required revision, and the revision found more
The previous completeness claim rested on an assumption now disproven: that reconstructing the World object reconstructs rendered output. This audit enumerates renderer decisions by syntactic position — every JSX visual attribute whose value is an expression — across every rendering surface, assuming no layer is authoritative.
Method comparison, measured. The old method found 9 conditionals, all
colour, in one file. The new method finds 26 across two renderers, and the
second renderer (WorldScene.tsx, the R3F path) had never been audited at
all.
Classification by provenance
| Class | Count | Discriminator | Verdict |
|---|---|---|---|
| A — interaction | 9 | selectedId (CityMap:156–187) |
Asserts nothing about the estate. See ambiguity below. |
| B — operational state → hardcoded value | 5 | node.blocked > 0, building.health === "fail", agent.status |
Article I.3 violation |
| C — undeclared normalisation band | 3 | activity, flow |
Article VI violation |
| D — structural | 9 | kind === "reserved" | "lot" | "handoff" |
Permissible under Article II.1's precedent |
Class B — Article I.3
"No renderer may create, classify, infer, aggregate or interpolate state. A renderer maps world values to pixels and does nothing else."
CityMap:436 fill={node.blocked > 0 ? "#ff8a6b" : node.accent}
CityMap:704 fill={reserved ? "#5a5a66" : node.blocked > 0 ? "#ff8a6b" : node.accent}
WorldScene:280 emissive={failing ? ALERT : building.accent}
WorldScene:389 emissive={agent.blocked > 0 ? ALERT : agent.accent}
WorldScene:390 emissiveIntensity={busy ? 1.5 : 0.35}
Each classifies operational state inside the renderer and selects a value
from that classification. Two of them override accent, the field the previous
proof declared closed.
Class C — Article VI, a violation class no colour-focused scan could find
"Every normalisation ceiling, threshold and band in the system" must be declared with its basis.
WorldScene:174 0.12 + district.activity * 0.3
WorldScene:239 0.1 + link.flow * 0.45
WorldScene:282 0.12 + building.activity * 0.85
Three normalisation bands, in the renderer, mapping grounded world values onto
visual intensity. ASSUMED_CONSTANTS declares eight entries; none of these
is among them. This is a different article, a different mechanism, and
invisible to every prior pass — which searched for colour.
Remaining ambiguity — the scope of Article I.3's "state"
Class A derives from selectedId, which the renderer creates. Read literally,
I.3 forbids a renderer creating state at all. Read against Article I's scope —
"the operational world is a projection of one canonical operational state" —
selection is view state and asserts nothing about the estate.
The Constitution does not explicitly scope I.3. Nine visual decisions rest on the narrower reading. Recorded as a tripwire, not resolved by assertion; it is the one place in this audit where an existing article is genuinely ambiguous rather than merely violated.
Verdict on the methodology
Revision was required, and it is specified: enumerate by syntactic position across every rendering surface, classify each decision's discriminator by provenance, and never validate a rendered invariant against a data-layer object.
Applied once, it produced an independent counterexample class (Article VI) that the colour-scoped method could not have found. That is evidence the repair is real, and equally evidence that no completeness claim is yet justified.