Skip to content

feat: pulseplot v1 — native live pulse rendering replaces PNG display (Closes #66) - #69

Closed
kateebonner wants to merge 3 commits into
mainfrom
kate/66-pulseplot
Closed

feat: pulseplot v1 — native live pulse rendering replaces PNG display (Closes #66)#69
kateebonner wants to merge 3 commits into
mainfrom
kate/66-pulseplot

Conversation

@kateebonner

Copy link
Copy Markdown
Contributor

Implements #66 (design-of-record; closed — full context mirrored here). Successor to #67 (auto-closed when its base branch #65 merged and was deleted); review history and Raghav's approval-in-substance live there. All review findings addressed (AGENTS.md contract, poll docstring, :a-fallback reachability, canonical-signal comment).

What this does

The inspector's live pulse now renders natively from raw solve data — fully replacing PNG display. The solve emits the pulse's knot values per iteration as additive run.log lines; the extension parses, routes, and renders them as a theme-aware SVG component. iter_<N>.png files are still written by every solve (archival/publication artifacts — plot_pulse remains canonical there) but the inspector no longer reads them.

Originally scoped as a side-by-side compare with the PNG preview; amended mid-implementation by the design-lane owner to full replacement (see the scope-amendment comment on #66). Net effect: −231/+61 on the display path, and the mid-write PNG race class is structurally gone from the inspector — it can't mis-render a file it never reads.

Wire format (prototype-grade, candidate GA)

Additive lines in the run.log stdout tee — consumers that don't know them ignore them (anchored-regex no-match), so the β contract freeze is untouched:

AMICODE_PULSE_META drives=2 knots=50 labels="a_1","a_2" bounds=-0.2:0.2,-0.2:0.2   (once)
AMICODE_PULSE iter=6 dt=0.204082 a=<comma-sep values; drives ;-separated>          (per iter, ~1KB)

Numeric domain shared with AMICODE_ITER (incl. Inf/NaN). Cross-line policy: records before meta dropped; last meta wins and its shape governs; count-mismatched records and internally inconsistent metas ignored (duplicate metas are expected — the tailer re-reads from offset 0 on truncation).

The pieces

  • Grammar + PulseStream policy — pure, in the vscode-free reader; unit-tested against the pinned grammar incl. all edge cases.
  • Delivery — replay ingest forwards meta + the newest record only (no history burst); the live tailer forwards meta and each record in order (on a live run the tailer is the only meta carrier). Replay-seeded meta arms the live stream across mid-flight run switches. LogTailer.poke() now self-attaches when run.log appears before its retry timer fires.
  • HostpostPulse with pre-materialization buffering (meta + newest record, replayed before any buffered completion) and the same 5 Hz trailing-edge throttle the PNG path used. New webview messages: pulsemeta, pulse.
  • Componentmedia/ui/components/pulseplot.ts: stacked per-drive panels matching plot_pulse (zero-order-hold stairs, bounds band + dashed limits, per-drive labels, shared time axis). Fixed-order categorical series tokens in brand.css ride VS Code's theme chart colors; dev fallbacks validated (dataviz six checks) light + dark. >512 knots stride-decimated. Owns idle / warming / no-pulse-data states.
  • EmitterPulseEmitCallback in the solve template, on the same solver-agnostic (primal, iter) hook as LivePulsePlotCallback, delegating to it and printing the lines. (Two real-solve-discovered fixes: update! must be qualified — Makie exports the same name — and the drive component is :u, not :a.)
  • Demo re-recorded from a converged solve (F = 0.9998): run.log carries META + 61 records (round-trip verified, 0 dropped). "Replay demo run" exercises the whole pipeline.
  • Dev harnesspnpm run dev:pulseplot (visual iteration + AC8 render bench); excluded from the VSIX.

Not in scope (follow-ups)

Hover/scrub interactivity · convergence trace (objective + rollout fidelity, needs an AMICODE_FID line) · populations/leakage panel · GA contract-docs pass for the pulse lines · upstream atomic-save fix in Piccolo's live-plot callback.

Verification

94 unit tests green (grammar, policy, both delivery paths, buffering, throttle, seam), typecheck + build clean, two real solves through amico-run (converged, F ≥ 0.9998), parser round-trip on the real log: 1 meta + 61 records, 0 dropped.

🤖 Generated with Claude Code

kateebonner and others added 3 commits July 2, 2026 20:34
…m raw data, replacing PNG display

The solve emits pulse telemetry as additive run.log lines and the
inspector renders it natively — theme-aware SVG, updating every
iteration (5 Hz cap) instead of every 6th via PNG. iter_<N>.png stays a
run-dir/archival artifact (plot_pulse remains canonical for
publication-grade output) but the inspector no longer reads it, which
also structurally removes the mid-write PNG read race.

Wire format (prototype-grade, additive to the stdout tee — unknown lines
are ignored by all existing consumers, β contract freeze untouched):
  AMICODE_PULSE_META drives=2 knots=50 labels="a_1","a_2" bounds=-0.2:0.2,-0.2:0.2
  AMICODE_PULSE iter=6 dt=0.204082 a=<values ,-sep; drives ;-sep>   (~1KB/iter)

- run_dir_reader: pinned grammar (NUM domain shared with AMICODE_ITER,
  incl. Inf/NaN) + PulseStream cross-line policy: records before meta
  dropped; last meta wins and its shape governs; count-mismatched
  records and inconsistent metas ignored (duplicate metas are expected —
  the tailer re-reads from offset 0 on truncation).
- Delivery: replay ingest forwards meta + the NEWEST record only (no
  history burst); the live tailer forwards meta and each record in order
  (on a live run the tailer is the only meta carrier); replay-seeded
  meta arms the live stream across mid-flight run switches;
  LogTailer.poke() self-attaches when run.log appears early.
- Host: postPulse with pre-materialization buffering (meta + newest
  record, replayed before any buffered completion) and a 5 Hz
  trailing-edge throttle. New webview messages: pulsemeta, pulse.
- Component: media/ui/components/pulseplot.ts — stacked per-drive panels
  matching plot_pulse (zero-order-hold stairs, bounds band + dashed
  limits, per-drive labels, shared time axis); fixed-order categorical
  series tokens riding VS Code's theme chart colors (dev fallbacks
  validated light + dark); >512 knots stride-decimated; owns idle /
  warming / no-pulse-data states.
- Template: PulseEmitCallback on the same solver-agnostic (primal, iter)
  hook as LivePulsePlotCallback, delegating to it and printing the
  lines. (update! qualified — Makie exports the same name; drive
  component is :u with :a fallback.)
- Demo re-recorded from a converged solve (F=0.9998): run.log carries
  META + 61 records (round-trip verified, 0 dropped); PNGs + pulse.jld2
  NOT bundled — the demo ships only what replay exercises.
- Dead code from the PNG display path removed: preview component,
  frame delivery/buffering/dedup, ITER_PNG_RE, img-src CSP grant,
  runs-root/workspace resource roots.
- Dev harness (pnpm run dev:pulseplot) for visual iteration + render
  bench; excluded from the VSIX.

Implements #66 (closed; design record and scope amendment there).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… docstring

Review should-fix: AGENTS.md (the agent-facing prompt) still described
iter_<N>.png as "the live plot the Inspector shows" and never mentioned
the pulse lines — a hand-authored script per that contract would get a
dead live plot (stuck on warming, then the no-data hint). The contract
section now leads with AMICODE_PULSE_META/AMICODE_PULSE (prototype-grade
shapes shown), points at the template's PulseEmitCallback idiom (incl.
the DTO-0.9.6 fallback path), and re-labels PNGs as archival.

Review nit: the poll-backstop docstring in file_watcher.ts still cited
iter_*.png frame creations; it now describes what the tick actually
backstops (latest swings, FINISHED, tailer poke/self-attach).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…l-signal comment

- Template: drive-component selection now uses a names-membership check
  instead of something(traj.u, traj.a) — the fallback no longer leans on
  property access returning `nothing` for missing components (eager
  `something` evaluates both anyway). Verified on a real saved trajectory
  (picks :u, 2×50) and a full solve (converged F=0.9998, 61 pulse records).
- run_inspector: the drop-iter-records-pre-materialization comment cited
  the removed "image stream is canonical" rationale; it now states the
  actual one (next record refreshes the row; replay re-ingests on switch;
  pulse events are buffered because the log line is their only delivery).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kateebonner

Copy link
Copy Markdown
Contributor Author

Redundant — #67 restored (base branch temporarily recreated to reopen it, then retargeted to main). Review history stays on #67.

@kateebonner kateebonner closed this Jul 3, 2026
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