Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5b0b30d
fix(studio): ISP time estimate ~5 s/panel, not ~20 s (#244)
mbreiser Oct 2, 2026
4d258b0
fix(pattern-designer): degrees per pixel uses the full circle on part…
mbreiser Oct 6, 2026
9b851e6
fix(3d-viewer): partial arenas show each PAT panel on its installed c…
mbreiser Oct 6, 2026
c2a83f2
fix(icons): pattern thumbnails were mirrored top-to-bottom
mbreiser Oct 6, 2026
e440507
feat(pattern-designer): 2D view shows physical panel numbers and an a…
mbreiser Oct 6, 2026
3d5da0e
feat(pattern-designer): short, SD-safe filenames with a compact ON/OF…
mbreiser Oct 6, 2026
4c17055
fix(studio): check pattern names before uploading, flag X001_ re-uplo…
mbreiser Oct 6, 2026
84daa99
fix: rc1 review — SD name rule matches the card, cache tokens, partia…
mbreiser Oct 6, 2026
546d9c6
fix(link): first command after Connect no longer waits ~60 s behind t…
mbreiser Oct 7, 2026
564fecd
feat(studio): Console → Qwiic — scan, identify and test the Qwiic per…
mbreiser Oct 12, 2026
22f24a0
fix(studio): Qwiic panel — pick a device by radio row, category + con…
mbreiser Oct 12, 2026
ab6e8af
feat(rigs): G6_1x12of18 bench + G6_3x10of14 ring arenas and rigs (LAB…
mbreiser Oct 12, 2026
8935a69
docs: Qwiic peripherals rules in CLAUDE.md + parity ledger row (LAB-214)
mbreiser Oct 12, 2026
c5eff70
feat(studio): live Console sliders, firmware-sized test-display frame…
mbreiser Oct 12, 2026
0a0c03a
chore: drop the self-bump (v0.96 footer + notes entry) — the release …
mbreiser Oct 12, 2026
6bbd8d1
fix(studio): rc4 review — full-grid stream mapping (2×8of10 regressio…
mbreiser Oct 13, 2026
662815c
Merge #245: fix(studio): ISP time estimate ~5 s/panel, not ~20 s (#244)
mbreiser Oct 13, 2026
25cfc8b
Merge #246: Pattern Designer + Studio: partial-arena °/px and 3D/2D m…
mbreiser Oct 13, 2026
6157db0
Merge #248: Studio: first command after Connect no longer waits ~60 s…
mbreiser Oct 13, 2026
233b117
Merge #250: Studio: Console → Qwiic peripherals, rig peripherals:, li…
mbreiser Oct 13, 2026
a4a2a9c
release: 2026-10-06 rc5 — Arena Console v11, Pattern Icon Generator v…
mbreiser Oct 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ Some JavaScript modules are shared between multiple tools and must support diffe
**Arena Session — the single connection broker** (`js/arena-session.js`, Stage A of the Arena Studio unification):
- Owns ONE `ArenaLink` + ONE `ArenaRunner` per page and multicasts the link callbacks (`log`/`error`/`disconnect`) to subscribers via `on(event, fn)`. Run mechanism: `runTrial` / `runSequence` / `stop` / `running`; lifecycle: `connect` / `disconnect` / `connected` / `send`. Page-wide singleton via `ArenaSession.shared()`.
- **Must stay a classic `<script src>` module** (window-global + CommonJS dual-export, **no bare ES `export`**), loaded AFTER `arena-wire-g6.js` / `arena-link.js` / `arena-runner-g6.js` and BEFORE any `<script type="module">` that reads `window.ArenaSession`. This keeps Connect/STOP/run-state alive even if a stale ES-module import fails (the catastrophic-cache gotcha) — never move connection ownership into the module block.
- **Link lanes + read buffer (`js/arena-link.js`, 2026-10-07):** the port opens with a 1 MiB `bufferSize` (Chrome's 255-byte default loses bulk bytes or ends in "Break received" on macOS), and sends have two FIFO lanes: `{ background: true }` waits while any foreground send is queued. Connect-time sweeps and prefetches (GET_PATTERN_INFO frame counts, picker thumbnails) MUST use the background lane. Nothing on the wire is preempted, so a background bulk read must stay small (thumbnails: `PatPreview.liveFetchVerdict`, ≤ 1 MiB). A bulk body fails after `idleTimeoutMs` (5 s) without a byte instead of waiting out `timeoutMs`. Tests: `tests/test-arena-link.js`, `tests/test-studio-link-priority.js`.
- Both `arena_console.html` and `experiment_designer_v3.html` use `window.ArenaSession.shared()` instead of constructing their own `ArenaLink` (the console uses only connect/send/events; the designer uses the run mechanism). On involuntary disconnect the broker calls `runner.abort()` (public, added to `arena-runner-g6.js`) — falling back to the legacy private `_clear()` to tolerate a stale-cache runner. Tests: `tests/test-arena-session.js` (`pixi run test` runs the full suite; run a single file with `pixi run node tests/test-arena-session.js`).

**Pat-Parser Dual Export Pattern** (`js/pat-parser.js`):
Expand Down Expand Up @@ -252,7 +253,7 @@ fix flows to every page automatically; two hand-written HTML pages never will.
rig, source), the top-bar chips, and the Run-view sequence. `dirty` means
"text ≠ last load/save" (`savedText` baseline), NOT a snapshot-captured flag.
- **Display quiesce:** firmware refuses SD-write + ISP commands
(0x8D/0xE0/0x8A/0xC8/0xC9) unless the display is stopped (`CE_DISPLAY_ACTIVE`,
(0x85/0xE0/0x8A/0xC8/0xC9) unless the display is stopped (`CE_DISPLAY_ACTIVE`,
status 10). Any new handler for a guarded op must `await quiesceDisplay()`
first. STOP also blanks panels (they latch frames) — a persistent on-arena
display during ISP is impossible on current firmware; progress maps blink.
Expand Down Expand Up @@ -622,6 +623,31 @@ fix flows to every page automatically; two hand-written HTML pages never will.
like 0xA8 on the 0xCB telemetry flag; `Studio.readCrashReport()` logs a `crash_report` event.
Breadcrumb ops 6–9 are the SET_FRAME_POSITION sub-steps (disarm timer / preload / arm / respond).

- **Qwiic peripherals (LAB-214):** the controller exposes only a sensor-agnostic I2C bridge
(`GET_I2C_SCAN` 0xB0, `I2C_TRANSFER` 0xB1; 0xB2–0xBF stay reserved), advertised by the 0xC2
feature bit `qwiic_i2c` — **gate EVERY I2C send on it** (jack-less 10-10 / 2x10 builds answer
CE_UNKNOWN_CMD + an error glyph). All register maps are host-side in `js/qwiic-drivers.js`
(port of the firmware repo's `tests/qwiic_sensors.py` + `qwiic_peripherals.py`); the `KINDS`
registry there is THE one place to add/drop a part (category, role env|tilt|light|actuator,
display fields, log columns) — consumers pick by role, never by part. `js/studio-qwiic.js` holds
the rig `peripherals:` parser (tolerant, parseRigIo contract), the scan-vs-YAML check in BOTH
directions (declared-but-missing = error, never retried; attached-but-undeclared = amber flag,
never logged), setup, chip and "Copy as rig YAML". Rules: (1) ONE page-wide bus
(`Studio.qwiicBus()`) — its `exclusive()` mutex orders whole sequences (mux select + reads,
SHT4x's two legs); never nest `exclusive()`. (2) Scan only at SETUP (connect, the Scan button,
run start): ~10 ms controller loop stall; refused during runs. (3) The LSM6DSOX bump flag is
read-to-clear: one owner (the trial logger during runs; Console "Check" is disabled then);
connect-time setup never arms it. (4) Bench quirks are load-bearing (SHT4x two-leg read; LIR +
INT_CLR_ON_READ + INT1 routing; PCA9685 ALLCALL shadow at 0x70 decided from MODE1, cleared by
configure). The rig fetch (`fetchRigSections`) parses `io:` and `peripherals:` from one read.
- **Test display streams at the CONTROLLER's frame geometry** (0xCB rows × cols = fw
`panel_count_per_frame`), not a fixed 20 panels. The mapping lives in `js/studio-stream-geometry.js`
(Node-tested): figures are drawn on the rig's FULL arena grid (`activeArenaGeom()` = num_rows ×
num_cols — NEVER installed-columns-only: G6_2x8of10's panels are physical columns 1–8), grid column
c → firmware column c (only installed columns must fit), rig rows repeated up the frame; no 0xCB →
the legacy 20-panel rule. Live Console sliders go through `liveSender()` (single-flight, latest
wins) and pause during runs, ISP batches and post-mortems.

## Pattern Designer (`pattern_editor.html`)

Renamed from "Pattern Editor" (v0.10, 2026-07-04); the FILENAME stays `pattern_editor.html`
Expand All @@ -643,6 +669,22 @@ localStorage) — no auth UI of its own. Repo layout: free-standing patterns in
exists-check overwrite confirm.
- Classic deps added for this: `js/pattern-set.js`, `js/studio-url-state.js` (both
dual-export; same files the Studio loads).
- **Pattern filenames must fit the controller's SD rules** (`PatternSet.checkSdFilename`, js/pattern-set.js):
plain ASCII `[A-Za-z0-9._-]`, ends `.pat`, ≤ 63 characters (firmware 64-byte buffer), and keep
≤ 58 — a same-name re-upload is stored as `X001_<name>` and past 58 that cuts off `.pat`. The
Designer keeps generated names short (`grat`/`star`/`edge`/`sine`/`offon`/`anim`, `rot`/`exp`/
`trans`) and fits every saved/renamed name (`fitPatternFilename`: shortens the middle, keeps the
arena prefix + color tag); the Studio refuses a bad name BEFORE sending bytes (`sdUploadOne`)
and `sdLogicalName` strips `X001_`, so a re-upload shows as a duplicate. Upload opcode = 0x85.
- **Partial arenas (LAB-295) — three rules every view and helper follows.** (1) Angular pitch is
the FULL circle: `PatternGenerator.getDegreesPerPixel(arena)` = 360 / (num_cols × px/panel)
(G4_3x12of18 = 1.25°/px), never the installed width; the generators read the same
`getArenaDimensions().circleCols`. (2) A partial PAT stores only the installed columns, in
order: pattern block k = physical column `columns_installed[k]` (G6_2x8of10: block 0 = column 1
= panels 2/12) — ThreeViewer `_getPatternColumnMap()`, the 2D `physicalPanelNumber()`, the icon.
(3) 0° = straight ahead = the middle of the pattern (MATLAB `arena_coordinates`, Mollweide, the 2D
azimuth axis). The icon draws math angles negated on the canvas (`toCanvas`; y points down).
Hardware does NOT play partial G6 patterns yet (firmware ignores the panel mask) — LAB-296.
- **Multi-color G6 panels (v0.12, LAB-228) — color is a PANEL property, not a generation.**
`js/panel-color.js` (classic dual-export, also read as `globalThis.PanelColor` by the ES-module
viewers) is the ONLY place color logic lives: the layouts table (`g6-green` default,
Expand Down
17 changes: 8 additions & 9 deletions TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,14 @@

## Arena Orientation Notes

Current column mapping (column_order='cw'):
- Column 0 starts at south (-90°)
- Columns increase counter-clockwise (angles decrease)
- For partial arena test: columns 0 and 9 are missing

Expected orientation for partial arenas:
- Gap should appear at bottom (south) for R/L symmetry
- Currently gap appears bottom-right
- May need further adjustment to column_order or angle_offset
The icon is the same top-down picture as the 3D viewer's "Top Down" view and MATLAB
`design_arena` (fixed 2026-10-06, LAB-295; before that the icon was mirrored top-to-bottom):
- South (behind the fly) is at the bottom, the front at the top.
- Column 0 starts at the south boundary; with column_order='cw' the columns run clockwise
seen from above (behind → fly's left → front → right).
- On a partial arena the gap sits at the bottom, centered (e.g. G6_2x8of10: columns 0 and 9
missing, pattern column 1 lights physical column 1 at the lower-left of the gap).
- `tests/test-icon-orientation.js` checks this against the 3D viewer for every standard config.

## Validation Summary

Expand Down
8 changes: 4 additions & 4 deletions arena_console.html
Original file line number Diff line number Diff line change
Expand Up @@ -1521,7 +1521,7 @@
</div>
<div id="log"></div>
</div>
<footer id="footer">Arena Console v10 | 2026-09-28 10:45 ET</footer>
<footer id="footer">Arena Console v11 | 2026-10-13 11:51 ET</footer>
</div>

<!-- Paste-a-frame modal -->
Expand Down Expand Up @@ -1577,7 +1577,7 @@
(window.PatternSet / window.PatPreview); the import-only modules
(pat-parser, arena-configs, icon-generator) load in the deferred module
block at the end of <body>. -->
<script src="js/pattern-set.js"></script>
<script src="js/pattern-set.js?v=1006-partial-cols"></script>
<script src="js/pat-preview.js"></script>
<script>
'use strict';
Expand Down Expand Up @@ -2867,7 +2867,7 @@
let ispReportText = ''; // last run's plain-text report (Copy report)
const ispSleep = (ms) => new Promise((r) => setTimeout(r, ms));

// The firmware refuses ISP + SD-write commands (0xC8/0xC9/0xE0/0x8D/0x8A)
// The firmware refuses ISP + SD-write commands (0xC8/0xC9/0xE0/0x85/0x8A)
// unless the display is stopped (state ALL_OFF → CE_DISPLAY_ACTIVE with
// "Stop display before flashing a panel" otherwise). STOP_DISPLAY also
// BLANKS the panels — they hold their last frame, so the firmware pushes
Expand Down Expand Up @@ -3602,7 +3602,7 @@
<script type="module">
import PatParser from './js/pat-parser.js';
import { getConfig } from './js/arena-configs.js'; // also sets window.PANEL_SPECS
import { generatePatternIcon } from './js/icon-generator.js';
import { generatePatternIcon } from './js/icon-generator.js?v=1006-partial-cols';

const PS = window.PatternSet;
const PP = window.PatPreview;
Expand Down
4 changes: 2 additions & 2 deletions arena_replay_viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -505,8 +505,8 @@
<span class="orbit-hint">Drag to orbit · scroll to zoom · “Fly” for a close-up</span>
</footer>

<script src="js/arena-replay-viewer-protocol.js?v=0713-solid-ball"></script>
<script src="js/arena-replay-viewer-protocol.js?v=1006-partial-cols"></script>
<script src="js/fly-gait.js?v=20260926-gait"></script>
<script type="module" src="js/arena-replay-viewer.js?v=0713-solid-ball"></script>
<script type="module" src="js/arena-replay-viewer.js?v=1006-partial-cols"></script>
</body>
</html>
Loading
Loading