docs/world-diff.md
Snapshots and world diff
src/lib/world/snapshot.ts, src/lib/world/diff.ts, tests/world-diff.test.ts
Repository Adapter → Snapshot → World → Projection
↑ cache boundary
The renderer asks world questions. It never asks git questions.
1. Identity decides what can be cached
A world built from repo@revision is immutable — the same revision yields the
same facts forever — so the key is the identity and there is no invalidation
problem to solve.
The corollary is the load-bearing half. A world whose revision is null
cannot be snapshotted at all, because nothing can say whether a stored copy
is still that world. The estate is exactly this case: it aggregates live
substrates and reports revision: null honestly. store returns
NotStored { reason: "no-identity", basis } rather than a silent miss.
So the three-state origin model is not bookkeeping. A system that had faked a revision would have cached a world that quietly stopped being true.
Measured on this repository, 157 buildings:
| cold acquisition | 1638 ms |
| from snapshot | 9 ms |
| speedup | ×182 |
The lookup happens after git rev-parse HEAD and before everything else:
rev-parse is one process, and it is the only question that must be re-asked to
know the answer is still current. The ~1.6 s is dominated by one git show per
source file.
A world is a subject
The key was first hand-assembled as kind:name@revision, and the layering guard
I.4 rejected it — "a module that needs an identity must call core for it, so
there is exactly one implementation to be wrong."
Delegating forced the question the shortcut had skipped: what kind of subject
is a world? Core already had the answer — environment, "a context that other
subjects exist within", which is exactly what a world is to the buildings inside
it. Keys are now environment:repository/name@revision, and a world sits in the
same ontology as everything it contains.
The guard did not merely enforce a style. It located a missing entry in the ontology.
2. Attribution: most reported changes are not changes
The obvious diff reports "authentication-service: height +20%." That sentence
is usually false about the subject. Height is world-scoped
(warrant-audit): it is normalised against the largest value
currently observed, so a building grows when something else grows.
A diff that cannot separate those causes is worse than no diff, because it sends a reader to investigate a subject that did not move.
Every reported change is therefore attributed by comparing the underlying magnitude:
| magnitude | mark | cause |
|---|---|---|
| changed | changed | subject — the world really did change here |
| unchanged | changed | world-rescale — the frame moved, not the subject |
| not retained | changed | unattributable — stated, never guessed |
The defect this exposed
Attribution was impossible when it was written: worldFromFacts read
repo/bytes and repo/commits-touching, projected them into marks, and then
set measurements: []. The magnitudes were discarded at the moment of
projection, so no downstream consumer could ever recover them.
That is the fourth self-explanation question — what fact caused this — failing
in the world layer rather than the projection layer. Fixed by retaining the
measures beside the marks, carrying the observers' own citations through
citeAll rather than re-narrating them.
Measured across 12 real commits of this repository
58af62d → c35f2dd, 146 → 157 buildings:
| count | |
|---|---|
| buildings reported as changed | 146 |
| attributable to the subject | 11 |
| frame moved only | 135 |
| mark | changed | subject | rescale |
|---|---|---|---|
| height | 11 | 11 | 0 |
| activity | 11 | 11 | 0 |
| position | 146 | 9 | 137 |
A naive diff would have reported 146 changes. Eleven were real. The other 92.5% are the embedding re-solving around eleven edits.
3. Anchoring is two conditions, not one
Revisions say when; the subject says what. Two revisions of different
repositories diff perfectly and mean nothing — every building reads as added or
removed because they were never the same city. So anchored requires both a
matching origin subject and revisions on both sides.
The comparison is still produced either way. Refusing it would hide a legitimate use; what changes is whether the result may be called a history of one system.
4. Open finding — the embedding is not stable across revisions
Blocks the animation step, recorded rather than fixed.
Between those same two revisions, with the centroid fixed at the origin in both:
| distinct move distances | 111 of 146 |
| median displacement | 104.5 units (city span 300) |
| max | 218.8 units |
| buildings keeping their distance from centre (±0.5) | 9 / 146 |
So the motion is not a rigid rotation or reflection of the whole cloud — that hypothesis was tested and refuted. Classical MDS genuinely re-solves, and a third of the city's width is the typical displacement for a subject that did not change.
Animating position between two snapshots would therefore show 146 buildings flying an average of a third of the city, where 137 of those motions carry no information. Motion is the strongest visual signal available, and spending it on a solver artifact is the deception class from position-pipeline, not a polish issue.
Animation cannot be built on the current embedding. The candidate remedy is to align each new embedding to its predecessor before rendering, which is a different claim than the one measured here and is not yet tested.