Skip to content

Assert sticky controller settings before every run: controller: block, rig defaults, run header (LAB-230) - #219

Open
mbreiser wants to merge 3 commits into
mainfrom
claude/controller-settings-block
Open

mbreiser wants to merge 3 commits into
mainfrom
claude/controller-settings-block

Conversation

@mbreiser

@mbreiser mbreiser commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Why

The panel display mode (and refresh rate, panel firmware variant) is a sticky controller setting that was set by hand and never recorded with the data. On the 2P rig a wrong mode silently ruins imaging (22 Sept). The Bergamo color sweep (2026-10-16) still needed four manual Console steps before each run (see the old header of protocols/g6_2p_color_led_sweep.yaml). Design and adversarial review: docs/development/controller-settings-strategy.md. Linear: LAB-230.

What

  • Rig YAML: defaults.panel_mode (asserted at connect and every run), limits.max_refresh_hz, requires.panel_firmware, strict. New rig bergamo_g6_2x10_2p (triggered, 300 Hz cap, firmware "2p", strict). Course rigs gain defaults.panel_mode: persistent, so a bench left in triggered mode is put back before the next run.
  • Protocol controller: block (panel_mode, refresh_policy: line_sync_safe | refresh_hz, panel_firmware) + requires: [controller_block] so a runner without the feature refuses instead of ignoring it. Malformed values are blocking errors.
  • Studio preflight (Run and Test): read → set if different → read back; refuse on rig mismatch, unreadable setting, missing capability, wrong panel firmware or failed fleet verify. Before/after snapshot (mode, refresh, SPI, DIO roles, AO, panel-firmware footer, controller firmware, fleet verify) in the run header meta.controller and one transcript line. Run gate shows the planned verdict.
  • Settings → Controller card: declaration, live snapshot, verdict, Verify panels (fleet CRC via 0xC9). The batch flasher's verify result feeds the same header.
  • Validator prints the block and cross-checks the rig file. All three 2P protocols point at the Bergamo rig and carry the block: g6_2x10_2p_diagnostics, g6_2x10_2p_duty_sweep, and now g6_2p_color_led_sweep, whose header no longer lists the manual steps (panel mode 2, DIO 1 role, 300 Hz, panel firmware) — it says the Studio asserts them, and what remains yours (pick the Bergamo session rig, Verify panels once per session, telemetry on). Docs: docs/development/controller-settings.md, the protocol-yaml skill (.agents/skills/protocol-yaml/), the rig checklist.

Rebase 2026-10-18

Rebased onto main at 6af0140 (Studio v0.94; was 33 commits behind and conflicting). Resolutions: pixi.toml keeps main's by-name test discovery (tests/test-controller-settings.js is picked up automatically); the v0.88 release-notes entry is dropped from the notes file (it lives below, the candidate folds it); the skill edits live in .agents/skills/protocol-yaml/ (main's .claude/skills/ files are wrappers); the footer stays at main's v0.94. The Studio hunks merged cleanly over v0.89–v0.94 (replay, runtime variables, themes, Protocol ▾ / ⚙ Settings), so the Settings → Controller card and the run gate need one bench pass on the new base. Prettier run over the branch's JS.

Overlap with #254 (candidate 2026-10-20): protocols/g6_2p_color_led_sweep.yaml also exists there (old header, rig: cshl_g6_2x10). When #254 lands on main, this PR gets an add/add conflict on that file — take this PR's copy. Its _patterns/ folder and protocols/index.json entry stay with #254.

Not in this PR (phases 2–3)

Per-trial panel_mode and a boot-time per-panel firmware report (arena firmware, reiserlab/LED-Display_G6_Firmware_Arena#60); MATLAB runner support for controller:/requires:.

Tier

next candidate (after 2026-10-20), together with #260 (throttle-proof host-timed waits, LAB-303). No footer/version bump — the release commit does it (the candidate after 2026-10-20 becomes Studio v0.98).

Release notes

Studio

  • Panel display mode, refresh rate and panel firmware are now checked and recorded before every run. A rig file can set the panel mode it expects (defaults.panel_mode), a refresh-rate limit and the panel firmware it needs. A protocol that depends on a mode declares it in a controller: block. Before each Run or Test the Studio reads the controller, sets whatever differs, reads it back, and refuses to run if it can't get the arena into the required state: wrong rig, unreadable setting, wrong panel firmware, or failed panel verification. The before/after settings go into the run log.
  • Course rigs reset the panel mode to persistent at connect and before each run, so a bench left in triggered mode by an earlier session is put right automatically. Course protocols need no changes.
  • New rig bergamo_g6_2x10_2p for the two-photon setup (triggered mode, 300 Hz limit, "2p" panel firmware, strict). Its three protocols (diagnostics, duty sweep, color LED-intensity sweep) use the new block, so the four manual Console steps before a Bergamo run are gone.
  • Settings → Controller shows the declaration, the live controller settings, the verdict, and a Verify panels button (checks every panel's firmware; about 1 s per panel).

Test plan

  • pixi run test green on the rebased branch (61/61 files; tests/test-controller-settings.js, round-trip suite 39)
  • pixi run studio-smoke 0 failures, 0 warnings on the rebased branch
  • Validator clean on all three 2P protocols and the fixture
  • Prettier clean (pixi run format-check flags only two files untouched here)
  • Bench: connect a course controller → log shows panel mode: … (rig default); open a 2P protocol on a course rig → run refused (rig mismatch); on the Bergamo → mode asserted, header carries the snapshot; Verify panels → 20/20; Settings → Controller card renders on the v0.94+ shell

🤖 Generated with Claude Code

mbreiser added a commit that referenced this pull request Sep 29, 2026
…oller: block, rig defaults, run header (Studio v0.88)
mbreiser added a commit that referenced this pull request Sep 29, 2026
Candidate for Production. PRs: #219, #233, #234.

Release-Manifest: releases/2026-09-29.json
@mbreiser

Copy link
Copy Markdown
Contributor Author

Held from candidate 2026-09-29: fix list from the adversarial review

The course-rig path works: on the 2×10 bench, rc1/rc2 asserted persistent, recorded meta.controller, and didn't disturb the closed loop. But a Codex (gpt-6-astra) standard + adversarial review of the candidate, reconciled with Claude's own review, found that the checks don't hold where this PR matters most: the strict 2P rig. #233 and #234 ship without it (rc3). This PR returns for fixes and goes in a later candidate.

1. Make the checks actually hold (blocking)

  • Refresh cap: always SET when a cap or policy applies. Never skip because the readback already equals the target. Verified in the firmware (CommandProcessor.cpp): without an explicit SET, refresh_rate_explicit_ is false, and every pattern load recomputes the rate with defaultRefreshFor(block_byte_count_). So a 300 Hz reading can be the GS16 default, and a GS2 pattern then goes to ~1200 Hz, over the Bergamo 300 Hz cap. Fix both the planner (js/controller-settings.js, refresh section) and Studio.assertRefreshHz.
  • The grayscale lookup never matches. Verified: the SD map is keyed by name, with entries {index, sd_name, preview} and no .name. So referencedPatternInfo always returns gsVal: null. Resolve patterns the way the runner does (key lookup plus numeric pattern_ID).
  • A protocol must not weaken a rig requirement. block.panel_firmware || rigRequires.panel_firmware lets the protocol replace a strict rig's 2p. When both declare one, both must hold, and a conflict blocks. That's also what the validator already claims.
  • Bind Verify panels to its identity. Record controller id, SD footer version/CRC and the panel set. Accept only full-fleet coverage for the current controller and image. Clear it on disconnect. Merge a subset batch-flash into the result per panel instead of replacing the whole-fleet result.
  • Unreadable rig file. fetchRigIo() null → parseRigIo(null) silently drops strict/limits/requirements. Refuse ("rig file unavailable") when the protocol or rig is strict, warn otherwise, and don't cache a failed load.

2. Close the bypasses (significant)

  • Single-condition Test (runCondition → runSteps) skips the preflight and can reuse the previous run's _controllerHeader. Share one preflight + header helper with runOnce, and reset the header per run.
  • Start lifecycle: STOP doesn't cancel the preflight (reproduced: "STOP acknowledged → log created → run started"), and overlapping starts can clobber _controllerHeader / activeRunLog. Add a start token: disable Run/Test while starting, have STOP cancel it, and check it after each await.
  • Verify panels must own the device. Mark the session busy for the ~20 s loop so no run can start, and break out on disconnect.
  • refreshControllerGate checks only session.running. It must also skip while Studio._activePostmortem or a soak owns the link, since the dry refresh sends ~7 GETs.
  • Non-strict rigs: a failed SET (header.blocking.push in the action loop) should warn, not refuse, as the planner already does for other problems. Low risk: firmware accepts SET_PANEL_DISPLAY_MODE at any time, so this only fires on a transport failure.

3. Small fixes and docs

  • validate-protocol.mjs exits 0 after printing BLOCKING for a rig-limit violation (reproduced with refresh_hz: 500 on the Bergamo rig). Put rig-limit violations in blocking.
  • Drop the "MATLAB refuses this file" claim from controller-settings.md and the 2P protocol comments: maDisplayTools has no requires: handling. Optionally, file an issue there.
  • Deferred (new scope, not a bug): comparing Digital IO / analog-out roles against the rig on strict rigs.

Testing before it re-enters a candidate

  • New tests per fix, including lifecycle tests with a fake session: STOP mid-preflight, double start, verify during a run, disconnect.
  • A second Codex adversarial pass on the fixed PR.
  • A bench check on a controller that advertises v3_triggered. The 2×10 bench's firmware (c15d840a, caps g6_mode, v2_local_storage, io_ext, health) doesn't, so the triggered path needs the Bergamo rig or a triggered-capable build.

Rough size: ~5–6 h of work plus the bench check.

🤖 Generated with Claude Code

mbreiser and others added 2 commits October 18, 2026 13:10
…troller: block, rig defaults, run header (v0.88)

Design: docs/development/controller-settings-strategy.md (Codex-reviewed). Wiring lives in the rig
YAML, experiment semantics in the protocol, and every sticky setting a run depends on is asserted
(GET → SET → GET) before the run, refused on mismatch, and recorded in the run header. No
restore-at-end.

- js/controller-settings.js (new): pure planner — normalizeControllerBlock, planAssertions (rig
  identity, panel mode + capability, refresh derived from pattern grayscale + rig cap, panel
  firmware footer + fleet verify), formatSnapshot. tests/test-controller-settings.js (42 checks).
- js/plugin-registry.js: parseRigIo → defaults.panel_mode, limits.max_refresh_hz,
  requires.panel_firmware, strict, name.
- js/protocol-yaml-v3.js: top-level controller: block (extractControllerBlock; malformed values are
  blocking errors; missing requires token warns); WEB_RUNNER_CAPABILITIES = [controller_block].
- arena_studio.html: rig defaults.panel_mode applied at connect; Studio.assertControllerSettings
  runs in runOnce for Run AND Test, blocks with a banner; readControllerSnapshot → run header
  meta.controller + transcript line; run gate shows the planned verdict; Settings → Controller
  card with live snapshot and Verify panels (fleet 0xC9); batch flasher stores its verify result.
- validate-protocol.mjs: prints the block, cross-checks the rig file (cap, policy, firmware).
- configs/rigs: bergamo_g6_2x10_2p.yaml (triggered, 300 Hz cap, firmware 2p, strict) + index;
  course rigs gain defaults.panel_mode: persistent.
- protocols: both 2P protocols point at the Bergamo rig and carry the controller block.
- docs: controller-settings.md (user), strategy (implemented), SKILL.md section, rig checklist,
  release notes v0.88; pixi test list.

Tests: full pixi run test green (one legacy assertion updated for the new capability token);
every inline Studio script parses.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ader no longer lists manual steps

The 2P color LED-intensity sweep (first run on the Bergamo 2026-10-16, on PR #254's
branch) now points at configs/rigs/bergamo_g6_2x10_2p.yaml, declares
`requires: [controller_block]` and the `controller:` block (triggered, line_sync_safe,
panel firmware "2p"), and its header says that the Studio asserts the four Console
steps it used to ask the operator to do by hand (panel mode 2, DIO 1 role, 300 Hz,
panel firmware). What remains manual: pick the Bergamo session rig, Verify panels
once per session, telemetry on.

This file also exists on #254 (candidate 2026-10-20) with the old header and the cshl
rig; when that lands on main, resolve the add/add conflict in favor of this copy. The
pattern folder and the protocols/index.json entry stay with #254.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mbreiser
mbreiser force-pushed the claude/controller-settings-block branch from b6af8e7 to cb90952 Compare October 18, 2026 17:19
@mbreiser mbreiser changed the title Assert sticky controller settings before every run: controller: block, rig defaults, run header (Studio v0.88) Assert sticky controller settings before every run: controller: block, rig defaults, run header (LAB-230) Oct 18, 2026

This branch has not been deployed

No deployments
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