Round-4 review fixes: pipeline/count desync, persistence, CLI, analysis, export - #64
Merged
Merged
Conversation
…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
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SH7ouoNymubArm7VEgALHg
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
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.
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
+/-and Amount on a Delay or Queue move the pipeline, viaSimEngine.setLiveCount_persistAutosave)--param,--steps,--runs,--pass-rateparse strictly and exit 1 on junkeco.set()keeps the pool's resource type instead of greying it--helpprints the header block, not implementation notesNotable 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
_queuewas only ever built inreset(), but Run and Step bootstrap at step 0 withsaveInitial()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.finallynever ran. Harmless before the finding-9 change and corrupting after it; the driver now callsjob.return().--helpprints, so they were user-facing copy under the project's copy rule.Measurements
detectLoopsat 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 errorsOne 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