Skip to content

Orbit lab: bounded interactive history, pan/zoom, mouse oracle (dynamics#20 rung 1, slice 2) - #22

Merged
InauguralPhysicist merged 1 commit into
mainfrom
ui-rung1-slice2
Aug 3, 2026
Merged

Orbit lab: bounded interactive history, pan/zoom, mouse oracle (dynamics#20 rung 1, slice 2)#22
InauguralPhysicist merged 1 commit into
mainfrom
ui-rung1-slice2

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Contributor

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_advance appended every frame forever and TRAIL capped only what _paint_phase draws. In auto/dump mode that is load-bearing — traj_text needs 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.cap now bounds the history and is set only on the interactive path (run_session with frames < 0); auto/dump mode keeps cap = 0 and 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 keeps frames + 1 points; interactive path stays under the bound and above the draw window; the retained points are byte-identical, through the oracle's own traj_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.sh gains a leg that pins the wiring through the real window: the interactive branch must wire cap = 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, r or the new Reset view button to restore. A zoom xN label 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.eigs runs 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.

--- UI-stepped trajectory (real window, real per-frame draw) ---
PASS: headless and UI-stepped trajectories are byte-identical (201 lines)
PASS: planted fault (zeta=0.16) is caught by the checker
--- UI-stepped trajectory with the view panned/zoomed every frame ---
PASS: pan/zoom on every frame leaves the trajectory byte-identical (201 lines)
--- interactive-session history bound (real window) ---
WIRED_CAP 601
LEN 41
CAP 40
FRAME 120
HIST_CAP 601
PASS: interactive session caps history at HIST_CAP=601; live size 41 points after 120 frames

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.sh drives 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):

PASS running: the plot advances between frames (3024 px differ)
PASS render-decode: zeta label reads 'zeta = 0'
PASS render-decode: zoom label reads 'zoom x1'
PASS pause: advancement freezes — two frames 0.4s apart are identical (0 px differ)
PASS unpause: advancement resumes (884 px differ)
PASS drag: panning redraws the plot while paused (25510 px differ)
PASS wheel: zooming redraws the plot (26265 px differ)
PASS render-decode: zoom label moved off 'zoom x1' to 'zoom x1.56'
PASS reset: `r` restores the pre-pan plot pixel-for-pixel (0 px differ)
PASS render-decode: zoom label back to 'zoom x1' after reset
PASS slider: dragging moves zeta 'zeta = 0' -> 'zeta = 1.79'

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:

--- planted fault: pause ---
FAIL pause: advancement freezes — two frames 0.4s apart are identical (934 px differ)
PASS: planted fault 'pause' is caught by the oracle
--- planted fault: pan ---
FAIL drag: panning redraws the plot while paused (0 px differ)
PASS: planted fault 'pan' is caught by the oracle
PASS: mouse + render-decode oracle, and both planted faults caught

Driving the real app found what no test could

desktop-gui-qa on 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 plain panel does not clip its children and nothing reports the overflow. Labels are now sized to the column. docs/orbit-lab.png is regrabbed from the running app and docs/orbit-lab-zoom.png shows the new capability (zoom x2.44, panned, outer winding clipped to the plot rect).

Upstream gaps logged, not worked around

  • F-DYN-11 — the wheel event carries no cursor position. ev.x/ev.y are the scroll deltas; dispatch knows the pointer position but keeps it in the private _ui. Cursor-anchored zoom therefore has to shadow the pointer by recording every hover mousemove, and is wrong for a wheel that arrives before the first motion event.
  • F-DYN-12canvas does not clip its own on_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.sh all green, plus --lint clean on every changed .eigs. CI runs all five in the devcontainer, which now also ships xdotool, x11-apps (for xwd) and python3-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

…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>
@InauguralPhysicist
InauguralPhysicist merged commit a71297a into main Aug 3, 2026
1 check passed
@InauguralPhysicist
InauguralPhysicist deleted the ui-rung1-slice2 branch August 3, 2026 01:13
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.

1 participant