Skip to content

Round-4 review fixes: pipeline/count desync, persistence, CLI, analysis, export - #64

Merged
zntznt merged 6 commits into
mainfrom
claude/ui-ux-run-4qmy5r
Aug 28, 2026
Merged

zntznt merged 6 commits into
mainfrom
claude/ui-ux-run-4qmy5r

Conversation

@zntznt

@zntznt zntznt commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Fixes all twelve findings from the fourth multi-agent review, which targeted surfaces no previous round had covered: cli.js, the codegen/loops/assertions modules, persistence under stress, analysis numerics, scale, and round 3's own diff.

Each fix has a test that I verified fails against the unpatched code first. 251 unit tests, smoke green.

Findings fixed

# Severity Area Fix
1 high properties panel +/- and Amount on a Delay or Queue move the pipeline, via SimEngine.setLiveCount
7 high persistence undo and redo write the autosave slot (_persistAutosave)
8 high persistence File > Open confirms first and commits the swap, so Ctrl+Z works
3 high cli --param, --steps, --runs, --pass-rate parse strictly and exit 1 on junk
5 high codegen eco.set() keeps the pool's resource type instead of greying it
6 medium loops depth limit 10 → 32; panel and CLI stop claiming "no loops" after a truncated search
9 medium analysis a batch saves and restores the live run's RNG position
10 medium analysis the sweep range re-seeds from the parameter actually picked
11 medium export PNG scale clamps to the canvas limit rather than saving a 0-byte file
12 low analysis Monte Carlo yields per step, not per whole trial
2 low mobile timeline drawer holds at 190px instead of 324px
4 low cli --help prints the header block, not implementation notes

Notable detail

Finding 1 is the panel-side sibling of the trader guard merged last round. The engine now refuses to let a trader pay out of a Delay or Queue, but the properties panel still called the raw model primitives on those same node types. A delay holding ten units in transit, minus three clicks, still delivered all ten: three units created from nothing. Three plus clicks stranded three forever in an empty queue.

Found while fixing, not raised by the review

  • A delay never releases its authored stock unless you press Reset first. _queue was only ever built in reset(), but Run and Step bootstrap at step 0 with saveInitial() alone. Place a Delay, set Amount to 10, press Run, and the ten sit there forever. Same for a Queue. Both now seed through _seedPipelines(). This needs no mid-run interaction at all, so it is strictly worse than finding 1.
  • Cancelling a Monte Carlo batch leaked the RNG. The driver dropped the generator instead of closing it, so its finally never ran. Harmless before the finding-9 change and corrupting after it; the driver now calls job.return().
  • Two em dashes in the CLI header, which --help prints, so they were user-facing copy under the project's copy rule.

Measurements

  • Monte Carlo on a 300-node model: worst event-loop gap 276ms → 22ms, for about 2% more total batch time.
  • Mobile timeline drawer at 768x1024: 324px (32% of the screen) → 190px (19%), with the scrub row still inside it at the legend's two-row cap, tested at 768, 390 and 320 wide.
  • detectLoops at depth 32: worst case ~12ms on a dense 120-node graph, bounded by the existing edge budget.
  • --help: 56 lines → 45, with no implementation notes.

Testing

  • node test/run.js — 251 passed (was 242 on main)
  • npm run smoke — passed, no console or page errors
  • Every new test baselined against the unpatched code: each fails without its fix.

One caveat

_pngScale's limits (16384px longest side, 2.0e8 total area) are conservative constants, not feature-detected. A browser with a lower ceiling than Safari's would still fail, though it now says so instead of silently saving an empty file.


Generated by Claude Code

claude added 6 commits August 28, 2026 21:57
…sing long loops

Round-4 review findings 1, 5 and 6, plus a sibling of 1 found while fixing it.

- A Delay's _queue and a Queue's _fifo ARE its resources in transit, but they
  were only ever built in reset(). The app's Run and Step buttons bootstrap at
  step 0 without resetting, so a freshly authored delay held its starting stock
  forever and released nothing. Both reset() and the step-0 bootstrap now seed
  the pipelines through _seedPipelines().
- The properties panel's Amount field and +/- steppers wrote the count through
  the model primitives, which know nothing about the pipeline. Editing a delay
  or queue mid-run left it owing the old amount: three '-' clicks on a delay
  holding ten in transit still delivered ten, creating three units from nothing;
  three '+' clicks stranded three forever. They now route through
  SimEngine.setLiveCount, which moves the pipeline with the count. This is the
  panel-side sibling of the trader guard added in the previous round.
- The generated economy module's eco.set() wrote resources and let reconcile()
  backfill, typing every added unit untyped grey, so colour-filtered flows and
  converter recipes stopped accepting a pool the host game had just topped up.
  It now adds and removes through the primitives, keeping the pool's own type.
- detectLoops chased cycles only ten nodes deep, so a twelve-stage ring economy
  reported 'No feedback loops found' when the diagram is nothing but one loop.
  The edge budget is what actually bounds the runtime (~12ms worst case on a
  dense 120-node graph), so the depth limit is now 32. The Loops panel and the
  CLI also stop claiming there are no loops when the search was cut short.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SH7ouoNymubArm7VEgALHg
Round-4 review findings 7 and 8.

- undo() and redo() moved _lastState and repainted the canvas but never wrote
  the autosave slot, which only _commit() and _commitReplace() did. An
  accidental Ctrl+A Delete looked repaired by Ctrl+Z, and the next reload
  brought back the deletion with the undo stack gone, so it was permanent.
  All four paths now go through _persistAutosave().
- File > Open replaced everything on the canvas with no confirmation, and reset
  the history instead of committing the swap, so Ctrl+Z did nothing and the
  replaced work could not be recovered. It now asks first (only once the file
  has parsed, so a cancelled or invalid pick never nags) and commits the swap
  like New, Load template and Load from library already do.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SH7ouoNymubArm7VEgALHg
…the parameter picked

Round-4 review findings 9 and 10.

- Batch Analysis stops the live run without resetting it, so the run resumes on
  the shared RNG. Every trial reseeds that RNG, and the batch cleared it back to
  Math.random when it finished, so a paused seeded run silently completed its
  remaining steps unseeded: the seed no longer reproduced the run. The batch now
  saves the stream position before its first trial and restores it after the
  last one, cancelled batches included.
- The sweep's from/to were seeded once, from the first parameter in the list,
  and nothing re-ran that when the user chose a different parameter. Sweeping a
  capacity of 500 while the first parameter was a rate of 3 swept 1.5 to 4.5.
  The range now re-seeds on change, and a parameter sitting at zero gets a 0 to
  1 range instead of a degenerate 0 to 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SH7ouoNymubArm7VEgALHg
…as help

Round-4 review findings 3 and 4.

- parseFloat and parseInt stop at the first character they cannot use, so
  --param carrying=1,000 ran with a carrying capacity of 1 and exited 0. On the
  Predator and Prey template that is the difference between a stable run and
  both populations dying, with nothing on stdout to say the value was not the
  one asked for. --param, --steps, --runs and --pass-rate now parse strictly and
  fail as usage errors (exit 1) on anything that is not a complete number.
- --help printed every line in cli.js starting with //, so after the Examples
  block it trailed into implementation notes from the middle of the file. It now
  prints the header block only, stopping at the first non-comment line. The two
  em dashes in that block are gone as well: it is copy a user reads.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SH7ouoNymubArm7VEgALHg
…d settle the mobile drawer

Round-4 review findings 11, 12 and 2, plus a cancellation leak found while
fixing 12.

- PNG export sized the raster at 2x the diagram and never checked the result.
  Past a browser's canvas ceiling (268 Mpx in Chromium, and a longest-side cap
  besides) drawImage silently does nothing and toDataURL hands back a stub, so a
  large diagram downloaded a 0-byte file with no error anywhere. The scale now
  drops to whatever fits, an unrasterizable canvas says so instead of saving an
  empty file, and a scaled-down export tells the user and points at SVG.
- runMonteCarloAsync is time-boxed at 14ms per chunk, but the generator yielded
  only after a whole trial, so one trial of a large model ran uninterruptible:
  measured on a 300-node model, the longest event-loop gap was 276ms, and Cancel
  went unanswered for that long. It now yields after every step as well, which
  brings the worst gap to 22ms and costs about 2% in total batch time.
- Cancelling a batch dropped the generator instead of closing it, so its finally
  block never ran and the shared RNG stayed parked on the last trial's sub-seed.
  The driver now calls job.return() before resolving.
- The mobile timeline drawer overshot the room it needed. #timeline-canvas is a
  <canvas>, so its intrinsic height drove the drawer's content height whatever
  its flex basis said, and the drawer took 324px of a 1024px-tall screen with a
  230px chart in it. Zeroing the canvas's CSS height lets the flex layout decide
  instead: a steady 190px from 320px wide to 768px, the scrub row still inside
  it at the legend's two-row cap, and #tl-resize still grows the chart.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SH7ouoNymubArm7VEgALHg
@zntznt
zntznt merged commit b12370f into main Aug 28, 2026
1 check passed
@zntznt
zntznt deleted the claude/ui-ux-run-4qmy5r branch August 30, 2026 18:52
zntznt added a commit that referenced this pull request Aug 31, 2026
Round-5 review fixes: two PR #64 regressions, undo/redo, embed, engine, a11y
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.

2 participants