Orbit lab: bounded interactive history, pan/zoom, mouse oracle (dynamics#20 rung 1, slice 2) - #22
Merged
Merged
Conversation
…1, slice 2) dynamics#20 rung 1, slice 2. Three things, each with a checker that has been shown to fail. 1. Bounded trajectory history. `sim.pts` grew forever, so an interactive session (runs until Escape) climbed ~20 MB/hour at 60 fps — the slow silent shape the constrained-machine discipline exists to catch. `sim.cap` now bounds it, set ONLY on the interactive path; auto/dump mode stays uncapped because `traj_text` needs the whole trajectory and the byte-identical oracle is built on it. The trim runs in chunks (at 2x the cap) so the per-frame cost is amortised O(1) and a full draw window is always resident. tests/test_orbit_hist.sh proves both halves headlessly — dump complete, interactive bounded, retained points byte-identical to the tail of the uncapped run — and is validated by a planted fault (cap removed => caught). tests/test_orbit_oracle.sh adds the wiring leg through the real window. 2. Pan / zoom on the phase portrait: drag to pan, wheel to zoom about the cursor, `r` or the button to reset. View state ONLY — the projection reads cx/cy/half and nothing in that section touches the simulation. tests/orbit_ui_pan_dump.eigs pins it: an oracle run that pans, zooms (both clamps) and resets on EVERY frame must still dump a trajectory byte-identical to the headless reference. It does. 3. tests/test_orbit_mouse.sh — mouse + render-decode oracle. A stubbed-gfx suite stays green while every real interaction is broken (#599), so this drives the real window with real xdotool input and verifies by decoding pixels: the plot advances, Pause freezes it to a pixel-identical frame, drag pans, wheel zooms and moves the decoded "zoom xN" label, `r` restores the pre-pan plot exactly, the slider moves the decoded "zeta = ..." label, unpause resumes. Two planted faults (a drag that never claims the pointer, a tick that advances while paused) must each be caught, and are. Driving the real window also caught a defect no test could see: side-panel labels overflowed the column and were truncated mid-word at the window edge. Labels are now sized to the column; docs/orbit-lab.png is regrabbed and docs/orbit-lab-zoom.png shows the new capability. Two lib/ui gaps logged rather than worked around (FINDINGS F-DYN-11, -12): the wheel event carries no cursor position, and `canvas` does not clip its own `on_paint` (nor does a panel clip its children). Bifurcation sweep view is deferred to slice 3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
dynamics#20 rung 1, slice 2. Builds on slice 1 (#21, efb0ae1) and takes the review finding filed there as its first work item. Not
Closes— the bifurcation sweep view is deferred to slice 3.1. Unbounded
sim.pts(the slice-1 review finding)frame_advanceappended every frame forever andTRAILcapped only what_paint_phasedraws. In auto/dump mode that is load-bearing —traj_textneeds the full trajectory and the byte-identical oracle is built on it — but an interactive session runs until Escape and climbed ~20 MB/hour at 60 fps.sim.capnow bounds the history and is set only on the interactive path (run_sessionwithframes < 0); auto/dump mode keepscap = 0and every point. The trim runs in chunks (once the list reaches 2x the cap) so the per-frame cost is amortised O(1) rather than a copy per frame, and a full draw window (HIST_CAP = TRAIL + 1) is always resident.Two checkers:
tests/test_orbit_hist.sh(headless, runs on every build) — dump path keepsframes + 1points; interactive path stays under the bound and above the draw window; the retained points are byte-identical, through the oracle's owntraj_line, to the tail of the uncapped run; both runs agree on frame count and current state. Validated with a planted fault: the same program with the cap removed retains 5001 points and is caught.tests/test_orbit_oracle.shgains a leg that pins the wiring through the real window: the interactive branch must wirecap = HIST_CAP = 601, and the trim must actually fire on the live per-frame path.2. Pan / zoom
Drag the plot to pan, wheel to zoom about the cursor,
ror the new Reset view button to restore. Azoom xNlabel reports the factor.This is view state only —
_view(cx,cy,half) feeds the projection and nothing in that section reads or writes simulation state. The claim is checked, not asserted:tests/orbit_ui_pan_dump.eigsruns the oracle again with the view panned, zoomed (including both clamps) and reset on every frame, and the dump must still be byte-identical to the headless reference.3. Mouse + render-decode oracle
The trajectory oracle never looks at what the user sees and never touches a control — a stubbed-gfx suite stays green while every real interaction is broken (#599).
tests/test_orbit_mouse.shdrives the real window with real xdotool input and verifies by decoding pixels (exact bitmap-font decode on the 12x14 scale-2 cell grid, not OCR):The paused step doubles as the comparator's validation: it comes back at exactly 0 differing pixels twice, so "the plot changed" is measured against a pinned zero floor. Two planted faults are then required to go red:
Driving the real app found what no test could
desktop-gui-qaon the real X session caught side-panel labels overflowing the 192 px column and being truncated mid-word at the window edge ("energy : converge", "drag pan - wheel z") — a plainpaneldoes not clip its children and nothing reports the overflow. Labels are now sized to the column.docs/orbit-lab.pngis regrabbed from the running app anddocs/orbit-lab-zoom.pngshows the new capability (zoom x2.44, panned, outer winding clipped to the plot rect).Upstream gaps logged, not worked around
ev.x/ev.yare the scroll deltas;dispatchknows the pointer position but keeps it in the private_ui. Cursor-anchored zoom therefore has to shadow the pointer by recording every hovermousemove, and is wrong for a wheel that arrives before the first motion event.canvasdoes not clip its ownon_paint, although several sibling widgets do clip their bodies. The moment the plot gained pan/zoom, trail segments painted over the control column. Same class as the label overflow above.Both for
eigenscript-ui-toolkit-engineer. F-DYN-9 (no x-y plot widget) still stands — this slice stays on canvas drawing, as agreed; the widget is filed upstream as EigenScript#819.Suite
Local, EigenScript v0.34.0 gfx build:
test_smoke.sh,test_lab.sh,test_orbit_hist.sh,test_orbit_oracle.sh,test_orbit_mouse.shall green, plus--lintclean on every changed.eigs. CI runs all five in the devcontainer, which now also shipsxdotool,x11-apps(forxwd) andpython3-pil— the mouse oracle exits 2 = SKIP when any is missing and CI treats a skip as a failure, so it can never be silently dropped.Deferred to slice 3
The bifurcation sweep view.
🤖 Generated with Claude Code