D and T: the dataset and threat-picture cycles (#45) - #68
Merged
Conversation
Two keys that move an axis WITHOUT leaving the beat the instructor is on. Park on a Pareto detent, press `T`, and watch the same plan buckle under three different disruptions; or hold the same step and press `D` to change network. Day 2's textbook-then-theater move becomes a keypress rather than a server restart. Both ladders are data-derived and their lengths genuinely move -- the theater's interdiction ladder is three rungs pristine and two under `strait_mined` -- so the beat index carries across and is clamped by the clamp `setSpine` already had. Neither cycle assigns it. The threat picture is per-dataset state. It is stashed on the way out of a dataset and restored on the way back in, checked against the target's own declarations, so the excursion to the textbook set comes home to the picture it left rather than carrying a name the new dataset never heard of. `T` on a dataset with no threat pictures is a LABELLED no-op -- two of the three shipped datasets declare none, so a silent decline is the common case, and a key that does nothing in silence is indistinguishable from one that is broken. The label is transient state, cleared by `resolveBeat` with everything else derived. Every picture's payloads are prefetched with the dataset, because `T` is a keypress and anything a keypress can reach is fetched before the first keypress. `T` is therefore synchronous and installs spines from payloads already in hand; `D` is the one key that awaits, and the one structural rebuild the architecture allows. `applyThreat` touches no DOM structure. The instructor-written `note` on each disruption is displayed for the first time -- it has been authored into every scenario file and drawn by nothing. It gets a slot of its own beside the headline's sentence rather than sharing it, because both are true at once on the textbook set in Cost & Risk: a flat frontier to declare AND no picture to cycle. Supply figures are resolved in the panel rather than by mutating the numbers. Removing a node zeroes its lanes and leaves its stock alone, so `bravo_struck` severs HUB-BRAVO and the theater still reports its full 180 of supply. The total stays the server's; a second line under it says how many hubs are severed, and the hub itself is greyed with its quantity struck through -- the same absent-not-alarming vocabulary a removed lane already wears. Severance is read out of the `changes` block through `isRemoved`, so it catches a hub isolated one lane at a time as well as a removed node, and it is deliberately NOT a fourth NODE_STATE: those are Day 1's per-beat plan failures, and filed with them a severed hub would flicker back to healthy on the next keypress. No disruption arithmetic anywhere, as ever: every disrupted number is read off the server's `changes` block, and the tests now fail if the frontend so much as names a disruption kind or reads a `factor`.
#45) Four findings from the standards pass and two from the spec pass. `severedNodes` was building a `nodeId -> [edgeId]` adjacency map inside every render pass. Which lanes touch a node is TOPOLOGY -- no picture, beat or view moves it -- so it joins the crossings, the anchors and the directed lookup in `frozen`, solved once per dataset by the module that owns structure. It is handed in rather than imported, the same import direction that keeps `laneOf` a parameter of every spine builder. `cycleDataset` and the menu's change handler each spelled out stash-then-load. They are the same act, so they are now one `switchDataset` -- and it is guarded against re-entry, which matters for exactly this key: `D` is designed to be pressed twice in quick succession, and two interleaved loads would build one dataset's DOM against another's payloads. `resetView` cannot finish `R` by itself -- clearing the picture cannot re-solve the theater -- so the driver reaches it through one named `resetToBaseline` rather than two calls inline, and the comment says plainly that `resetView` alone is never the whole act. The severed class is a named export beside `NODE_STATES` rather than a literal in the render pass, so a fourth state stays one edit plus a stylesheet rule. On the spec side, the one criterion the review called partial: `T` installs new spine arrays, so `repaint` rebuilds the beat timeline's rows. That is accepted rather than overlooked and is now recorded as such -- a picture genuinely changes how long the story is, so rows have to appear or go, and it is the same fact that makes `setSpine`'s clamp necessary. What the criterion protects is the topology pass, and the test now pins that directly: `buildGraph` has exactly one caller in the page. Grepping `applyThreat`'s body alone could never have seen a rebuild reached some other way. A severed transit node stays greyed and stays uncounted. Nothing can move through it either, so saying so is true; it has no stock to strand, so counting it would inflate a figure about supply.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #45.
Two keys that move an axis without leaving the beat the instructor is on. Park on a Pareto detent, press
T, and watch the same plan buckle under three different disruptions; or hold the same step and pressDto change network. Day 2's textbook-then-theater move becomes a keypress rather than a server restart.The beat index carries, and neither cycle assigns it
Both ladders are data-derived and their lengths genuinely move — the theater's interdiction ladder is three rungs pristine and two under
strait_mined. So the index carries across and is clamped by the clampsetSpinealready had. Nothing new was added to hold it in range, and neithercycleDatasetnorapplyThreatwrites it.The threat picture is per-dataset state
It is stashed on the way out of a dataset and restored on the way back in, checked against the target's own declarations — so the excursion to the textbook set comes home to the picture it left, rather than carrying a name the new dataset never heard of.
The dataset cycle opens on the default and moves away from it, so pressing
Dtwice at two datasets brings you home.Twith nothing to cycle is labelledTwo of the three shipped datasets declare no threat pictures, so a silent decline is the common case — and a key that does nothing in silence is indistinguishable from one that is broken. The refusal says so on screen. The label is transient state, cleared by
resolveBeatalong with everything else derived.Only
Dawaits, onlyDrebuildsEvery picture's payloads are prefetched with the dataset, because
Tis a keypress and anything a keypress can reach is fetched before the first keypress.Tis therefore synchronous and installs spines from payloads already in hand;Dis the one key that awaits, and the one structural rebuild the architecture allows.One accepted deviation, recorded rather than overlooked:
Tinstalls new spine arrays, sorepaintrebuilds the beat timeline's rows. A picture genuinely changes how long the story is, so rows have to appear or go — it is the same fact that makessetSpine's clamp necessary. What the criterion protects is the topology pass, and the test pins that directly:buildGraphhas exactly one caller in the page. GreppingapplyThreat's body alone could never have seen a rebuild reached some other way.Supply figures, resolved in the panel
Removing a node zeroes its lanes and leaves its stock alone, so
bravo_strucksevers HUB-BRAVO and the theater still reports its full 180 of supply. A panel reading an unchanged total beside a visibly severed hub invites exactly the wrong question at the moment the instructor is making the opposite point.The total stays the server's. A second line under it says how many hubs are severed, and the hub itself is greyed with its quantity struck through — the same absent-not-alarming vocabulary a removed lane already wears. Severance is read out of the
changesblock throughisRemoved, so it catches a hub isolated one lane at a time as well as a removed node.It is deliberately not a fourth
NODE_STATE: those are Day 1's per-beat plan failures, and filed with them a severed hub would flicker back to healthy on the next keypress. A severed transit node is greyed and stays uncounted — nothing can move through it either, but it has no stock to strand, so counting it would inflate a figure about supply.The note gets drawn
The instructor-written
noteon each disruption is displayed for the first time — it has been authored into every scenario file and drawn by nothing. It gets a slot of its own beside the headline's sentence rather than sharing it, because both are true at once on the textbook set in Cost & Risk: a flat frontier to declare and no picture to cycle.No disruption arithmetic, and the tests enforce it
Every disrupted number is read off the server's
changesblock. The tests now fail if the frontend so much as names a disruption kind or reads afactor.Review pass (second commit)
severedNodeswas building anodeId -> [edgeId]adjacency map inside every render pass. Which lanes touch a node is topology, so it joins the crossings, the anchors and the directed lookup infrozen— solved once per dataset, handed in rather than imported.cycleDatasetand the menu's change handler each spelled out stash-then-load. They are oneswitchDatasetnow, guarded against re-entry — which matters for exactly this key:Dis designed to be pressed twice in quick succession, and two interleaved loads would build one dataset's DOM against another's payloads.resetViewcannot finishRby itself (clearing the picture cannot re-solve the theater), so the driver reaches it through one namedresetToBaseline.NODE_STATES, so a fourth state stays one edit plus a stylesheet rule.Verification
tests/test_web_assets.py(the repo verifies the frontend from Python or not at all).strait_mined,bravo_strucksevering HUB-BRAVO with the 180 total intact, the labelledTrefusal on textbook and greedy_trap, and aDround trip restoring the picture it left./code-reviewaxes run and addressed in003f400.🤖 Generated with Claude Code