Skip to content
← Documentation

docs/position-pipeline.md


Position as a transformation graph

It was one sentence — "shortest-path metric, symmetrised, solved by MDS" — hiding six operations, each with its own losses. That is exactly where prose drift returns.

src/lib/projection/pipeline.ts, tests/pipeline.test.ts.


The criterion needed a qualifier — and I had it wrong

First pass: a step contributes convention when it collapses, creates, or selects distinctions that were not present in its input. Applied literally, that made co-change a stipulation for reducing commits to a count.

Wrong, and wrong in a way that empties the category. Almost every projection loses something; if loss alone is conventional then everything is, and the distinction stops distinguishing. The qualifier:

A stage contributes convention when it removes or invents distinctions relevant to the claim being made.

A thousand commits reduced to a count loses the commits, and the claim — "this changed frequently" — survives intact. So the field is preservesClaimDistinctions, not injective, and every stage now states the claim its effect is assessed against.

The fold, corrected

co-change      evidence    —
weight         evidence    —
distance       evidence    —
shortest-path  convention  selects a reference its input does not contain
symmetrise     convention  removes distinctions relevant to its own claim
embed          convention  removes distinctions relevant to its own claim

→ 3/6 stages are semantic interventions

Position still appears the most evidential channel — every input an observed commit, no ceiling anywhere — and is the least. But the shape is sharper than the first count: the three interventions are the last three, the stages closest to the pixel. A chain is no more authoritative than its weakest stage.

shortest-path also moved from removes to selects: the minimum over routes is a defensible metric and so are mean path length and resistance distance, and nothing in the data chooses between them.

The losses the sentence was hiding

Stage Discarded
co-change which commits coupled a pair, their order, their authors — two files coupled in 2020 and 2026 are indistinguishable from two coupled twice last week
weight absolute counts
distance nothing
shortest-path which path realised each distance, and every longer route between the same pair
symmetrise directiona→b at 0.9 and b→a at 0.1 become one number, and the unreachable-pair factor is a chosen constant
embed every dimension beyond the second — measured at 54.5% of proximity comparisons reversed

Every one was invisible inside the sentence. distance is the only lossless stage, and it says so — null is a claim, not an omission.

Why this is not a ProjectionRule

ProjectionRule folds a scalar through steps. Position consumes a graph, changes its shape repeatedly, and emits coordinates. Forcing it into the scalar vocabulary would have produced one giant op — a sentence with extra syntax.

Same discipline, different shape: each stage declares preserves, discards, scope and the two checkable facts, and nothing declares a warrant.


The criterion, sharpened and now derived

Previously: a step contributes convention when it introduces a chosen reference that changes what the value means.

Now:

A step contributes convention when it collapses, creates, or selects distinctions that were not present in its input.

And it is no longer a judgement written per operation — which would have been the same defect one level up. Each operation states two checkable facts:

StepEffect = { injective: boolean; parametersFromInput: boolean }
warrant = injective && parametersFromInput ? "evidence" : "convention"
  • collapses — not injective. A clamp, a bucket, a min over paths.
  • selects — a parameter the input does not determine. Yesterday's peak, a declared ceiling, a top-N cutoff.

sqrt, scale and offset are injective and parameterless-from-choice, so they cannot introduce a claim. This ties back to the distinction work: a transformation is evidential exactly when it neither destroys nor invents a distinction.

The two failure modes are reported separately, not merged:

floor → collapses distinctions its input had
stale → selects a reference its input does not contain

Standing

Measurable defect exposed? yes — position is 4/6 intervention, and looked evidential
Measurable improvement? yes — the sentence is generated from six stages
Prose remaining in the manifest? none for position, height, activity, hue or footprint

Executable end to end

co-change      8 co-changed pairs over 8 subjects              ran here
weight         weights in (0, 1], peak-normalised              ran here
distance       8 edge distances                                ran here
shortest-path  a complete metric over 8 subjects               ran here
symmetrise     a symmetric metric; N pairs lost direction      ran here
embed          8 coordinates in 2 dimensions                   ran here

→ 6/6 stages execute here
12 coordinates, identical to layoutFrom

layout.ts now exports symmetrise and embed, and layoutFrom composes the same functions — one implementation, no copy. The pipeline is the mechanism rather than a theory of it.

The load-bearing test is coordinate identity: running the stages yields exactly what layoutFrom yields, asserted per coordinate. A divergence would mean the explanation describes a different computation from the one that drew the scene. stillDeclared is asserted empty.

The three-way loss taxonomy

A framework for trustworthy visualization cannot demand losslessness — visualization is compression, and demanding it makes the theory self-defeating.

lossless     distance
compression  co-change, weight          removed distinctions irrelevant to the claim
convention   shortest-path, symmetrise, embed   chose among equally valid meanings, and said so
deception    —                          removed claim-relevant distinctions, silently

Deception is the only class the engine must prevent, and what separates it from convention is declaration. Symmetrising away direction is a choice; symmetrising it away silently is a false claim of mutuality. The test proves the guard bites by hiding one declaration and watching the class flip.

A correction to the null rule

The previous round asserted "a stage with discards: null must fold to evidence". Wrong — a stage can be perfectly lossless and still select a reference its input does not contain. The invariant is about declaration, not about warrant:

Discarded information must be declared whenever it could alter the viewer's interpretation of the claim.

What is still not established

  • The stage metadata is still authored. claim, preserves and discards are written by hand; only the warrant and scope are derived, and only from two hand-set booleans. Executing the stages closed the gap between the pipeline and the computation, not between the metadata and either.
  • preservesClaimDistinctions is asserted per stage. It is checkable in principle — feed two inputs differing in the claim, see if the outputs differ — and nothing checks it.
  • injective and parametersFromInput are asserted per operation. They are checkable in principle and nothing checks them; a wrong pair would silently change a warrant.
  • The 54.5% figure is carried from an earlier measurement, not recomputed here.
  • Position remains the channel most likely to mislead, and this round did not change a pixel of it.

282 → 290 tests, 26 DOM, 40 mutations, tsc clean.