Skip to content

Flow & Cut view (Days 2-3), and the /maxflow trace a bad merge had reverted - #65

Merged
bgent19 merged 3 commits into
mainfrom
feat/42-flow-cut-view
Aug 16, 2026
Merged

Flow & Cut view (Days 2-3), and the /maxflow trace a bad merge had reverted#65
bgent19 merged 3 commits into
mainfrom
feat/42-flow-cut-view

Conversation

@bgent19

@bgent19 bgent19 commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Closes #42.

The Flow & Cut view (#42)

Edmonds-Karp stepped one augmentation at a time over the drawn network, with Day 3's min cut arriving as a layer over the completed flow. The two days share a screen because the min cut is a certificate of the flow just computed, and re-rendering it fresh severs the argument it is making.

Beat 0 is the pristine network and each augmentation is two beats, path-and-bottleneck then residual update. The coarse unit is the augmentation — three of them on the textbook set — because that is what the instructor says out loud; the pristine beat and the cut fold into the first and last units rather than taking units of their own, so the digit keys name what the board names.

The reverse arc gets its own geometry: an arc traversed against its lane's own direction is promoted in place at offset 22, dash 22 18, stroke 4, hollow head, clipped to the node rims by circle intersection rather than the usual trim — an offset chord is a different line, and trimming it by the centre-line rule leaves one end floating clear of the rim. Promotion replaces the same-side arc: the pair at 11 and 22 would merge into one heavy mark that says neither thing. The lane itself is not path-marked while promoted, so "here it uses a residual arc, which is not a lane" is a sentence the screen agrees with.

The promotion needed no new dash token. It is the residual arc moved and re-headed, not re-patterned, so it inherits a pattern the stroke floors already certify — which retires the 24 15 @ 11 the token block was holding a note against, a value the duty rule could not have certified.

⚠️ A closed ticket had been silently reverted (#36)

While building this I found /maxflow?trace=true missing from main. #36 shipped it as ba6f32d, but #38's ladder work was developed from a pre-#36 base and overwrote api.py wholesale; the merge at 094a1aa — labelled "keep main's trace implementation" — then took that side. The whole commit went with it across api.py, cli.py, throughput.py (TraceStep.flows) and three test files, leaving a closed ticket with none of its acceptance criteria holding.

The trap worth knowing: git merge-base --is-ancestor ba6f32d HEAD still answered YES, so ancestry did not reveal it — only reading the file did. Other closed backend tickets from that window may be worth a look.

Re-landed here by cherry-pick, reconciled against #38. Nothing of the ladder is given up.

Verification

356 tests pass. The frontend is verified from Python or not at all, so the new rules are pinned as assertions over the asset text in tests/test_web_assets.py: the two named offsets, the promoted arc spending no hue, the hollow head, circle-intersection clipping, the partition shading, the glyph scoped to on-path and against, and a standing ban on cap - flow anywhere in the JS.

Beyond the suite, the real modules were run against the real payload once by hand: 8 beats / 3 units on the textbook set, the promotion firing on the B→A traversal, cut == throughput == 7, the partition splitting s/A/B/C from D/t, and every beat identical whether stepped to or jumped to cold.

Not done, deliberately

The ticket's "default dataset: the textbook network" is left to #45, which owns dataset cycling. Coupling the dataset to the view would give a view a code path of its own, which views.js rules out precisely so a view cannot drift from what the same layers show when switched on by hand.

#36 shipped `/maxflow?trace=true` in ba6f32d, but #38's ladder work was
developed from a pre-#36 base and overwrote api.py wholesale; the merge at
094a1aa then took that side. The whole commit went with it -- the endpoint's
`trace` param and its Pydantic models, throughput.TraceStep.flows, the CLI's
print-time filters, and three test files' worth of coverage -- leaving a
closed ticket with none of its acceptance criteria holding on main.

Restored by cherry-picking ba6f32d and reconciling api.py and the two test
files against #38, which landed after it. The ladder section and the trace
section are independent and both stand; nothing of #38 is given up.

The Flow & Cut view (#42) reads every number it draws out of this payload, so
it could not be built until the endpoint was back.
Edmonds-Karp stepped one augmentation at a time over the drawn network, with
Day 3's min cut arriving as a layer over the completed flow. The two days share
a screen because the min cut is a certificate OF the flow just computed, and
re-rendering it fresh severs the argument it is making.

Beat 0 is the pristine network and each augmentation is two beats, path-and-
bottleneck then residual update, so `->` always means "and here is what that
does". The coarse unit is the augmentation -- three of them on the textbook set
-- because that is what the instructor says out loud; the pristine beat and the
cut fold into the first and last units rather than taking units of their own,
which keeps the digit keys naming what the board names. The uniform render rule
is residuals from step k-1 and path annotation from step k, which is what the
server's prepended step 0 exists for.

The reverse arc gets its own geometry. An arc traversed against its lane's own
direction is promoted in place at offset 22, dash 22 18, stroke 4, hollow head,
clipped to the node rims by CIRCLE INTERSECTION rather than the usual trim --
an offset chord is a different line, and trimming it by the centre-line rule
leaves one end floating clear of the rim. Promotion REPLACES: two arcs at 11 and
22 merge into one heavy mark that says neither thing. The lane itself is not
path-marked while promoted, so "here it uses a residual arc, which is not a
lane" is a sentence the screen agrees with.

The promotion turned out to need no new dash token. It is the residual arc moved
and re-headed, not re-patterned, so it inherits a pattern the stroke floors
already certify -- which retires the `24 15 @ 11` the token block was holding a
note against, a value the duty rule could not have certified. Three non-colour
channels and no hue: colour was never available, path-vs-lane being dE 66
normally and 3.1 under achromatopsia.

The cut draws as a partition -- S-side and T-side washes behind the nodes, solid
rather than translucent because the scaffold tier owns opacity alone -- with its
certifying lanes as `.in-cut` row states on the existing ledger and cut capacity
as a headline number. Cut capacity and throughput come off ONE /maxflow
response, since two numbers that must be equal by max-flow min-cut and are
fetched by two routes are a contradiction waiting for a projector.

No solver arithmetic in JavaScript. Per-edge flows and both residual lists are
read off the trace and never derived; `cap - flow` is banned by a test rather
than by agreement. The whole trace is fetched before the first keypress, so
stepping is an index change and never awaits anything.

Also here: the beat timeline slot, one clickable row per beat, and the direction
glyph on `.on-path` rows -- stylesheet `content` scoped to on-path AND against
together, so a forward traversal stays unmarked and no second element has to
exist for it.

Not done, and deliberately: the ticket's "default dataset: the textbook network"
is left to #45, which owns dataset cycling. Coupling the dataset to the view
would give a view a code path of its own, which views.js rules out precisely so
a view cannot drift from what the same layers show when switched on by hand.
#42)

Standards axis:

- The hollow head's outline weight was written into the marker's markup, which
  made it the one appearance value a projector retune could not reach. It is
  `--arrowhead-hollow-w` now, styled from a rule like every other weight.
- `directedLookup`/`laneFor` and `frozen.directed` had been left standing beside
  the residual lookup that supersedes them, so every dataset built two
  directed-pair maps and never read one. Folded into a single lookup keyed on
  every ordered pair a solver can hand back -- which is what the second map was
  for -- and the dead export is gone.
- `render.js` reimplemented its own `shade()` helper for the terminals rather
  than calling it; the side predicate is a parameter now.
- `state.js` documented its per-view memory as `{beat, layers}` where it stores
  `{beat, overrides}`, contradicting the paragraph underneath it. Also `layers()`
  shadowed `state.layers` and read as a noun -- `setLayers`.
- `SHADE_R` was exported through `geometry` and consumed by nobody.

Spec axis:

- Promotion suppressed BOTH of a lane's plain residual arcs where the rule
  suppresses one. The promoted mark is the counter arc moved from 11 to 22, so
  the counter arc is what it replaces; the along arc is a different arc on the
  other side, 33 units clear, carrying a residual the layer is on to show.
  Suppressing it took a real number off the board to fix a collision it was
  never part of.
- The bottleneck was written to state and read by nothing, so beat *a* was
  "path &" with the "bottleneck" half reaching the screen only as timeline text
  -- while the path layer's own hint promised it. It is a headline scalar now,
  beside Flow, and the pair is the sentence: Flow 0, Push 3, then Flow 3.
- Dropped the A/B pair this view had claimed. It is one slot for the whole page
  and its subject is two states never simultaneously visible; an augmentation's
  "after" arrives one keypress later and is then simply on screen.
- Dropped the `used_reverse` loop. Every arc it names is an arc of the same
  step's path, so it heated nothing already heated -- and what drives the
  promotion is the drawn lane's direction, a fact about the picture rather than
  about the solver's bookkeeping. The test's field list says so now.
- `bindKeys()` ran before the trace was awaited, leaving the arrows answerable
  during the fetch and declining on the fallback spine. The keyboard goes live
  last, once a complete spine is installed.
@bgent19
bgent19 merged commit d329e8d into main Aug 16, 2026
1 check passed
@bgent19
bgent19 deleted the feat/42-flow-cut-view branch August 16, 2026 13:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flow and Cut view (Days 2-3)

1 participant