Repository navigation
Release 2026-10-06: Arena Console v11, Pattern Icon Generator v1.5, Pattern Designer v0.13, Arena Studio v0.95 - #247
Conversation
The Program set confirm said ~8 min for a 20-panel arena; the batch on rig5-mr (2026-09-30) took ~2 min: ~4.4 s program + reboot and ~1 s verify + STOP per panel. Use 5 s + 1 s for the estimate and fix the "~20 s" in the dialog and the Flash / Program set tooltips. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ial arenas
getDegreesPerPixel() divided 360 deg by the INSTALLED pixel columns instead of the
full circumference. On G4 3x12of18 (the IAS talk arena) that gave 1.875 deg/px
instead of 1.25, so the px<->deg helper labels were wrong and a spatial period typed
in pixels generated the wrong grating (24 px came out as 45 deg, 36 frames). A step
size typed in degrees was off the same way. G6 3x12of18 (1.5 -> 1.0 deg/px) and
G6 2x8of10 (2.25 -> 1.8) had the same bug; full arenas were already right.
The generator already used the full circle (arenaCoordinates numCircle = num_cols).
The pitch now lives in one shared place, PatternGenerator.getDegreesPerPixel(), and
getArenaDimensions() reports circleCols / azimuthPixels, which the three spherical
generators also read, so the editor and generator cannot drift apart again.
Also fixes the Animate tab's frame-shift hint on partial arenas ("360 deg = 192 px"
-> "240 deg = 192 px"), and cache-busts generator.js because the editor's init now
calls the new helper.
tests/test-pattern-degrees-per-pixel.js runs the editor's own conversion functions
(extracted from pattern_editor.html) for every standard config, plus end-to-end
gratings typed in px and deg; it failed 15 checks before the fix.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…olumn A partial arena's PAT stores only the installed columns, in columns_installed order (block k = physical column columns_installed[k], the icon generator's convention). ThreeViewer looked pixels up by the PHYSICAL column instead. Harmless when the installed columns start at 0 (G4/G6 3x12of18), but on G6 2x8of10 (columns 1-8) the whole pattern showed one panel to the side, pattern panel 1 never appeared, and physical column 8 read block 0 of the next pixel row (the lookup wrapped at the full circle, past the 160-px pattern width). _getPatternColumnMap() maps physical column -> block from the CONFIGURED columns (never the preview-visible set, so the CSHL 8/18 gap still doesn't shift the address map); each LED ref carries patternCol; the lookup wraps inside the pattern width. Shared by the Pattern Designer and the replay viewer, so the cache token moves to 1006-partial-cols in both (and the tests that pin it). tests/test-three-viewer-partial-columns.js loads the viewer with Three.js stubbed and checks every LED of every standard config reads its own block and row; against main's viewer it fails only on G6_2x8of10 (column 1 showed block 2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
renderCylindricalIconToCanvas drew math angles (y up) straight onto the canvas (y down), so every icon was the 3D "Top Down" view mirrored: the gap (behind the fly) at the TOP and the columns running counter-clockwise. TESTING.md always expected the gap at the bottom. Angles are now negated at the drawing step (toCanvas), so icons match the 3D viewer and MATLAB design_arena: south at the bottom, column 0 at the south boundary, columns clockwise from above. The misleading "row 0 = top of arena" comment is corrected (row 0 is the bottom; it maps to the inner ring, as in a perspective view from above). Shared by the Studio, Console and Pattern Designer thumbnails. No export changes. tests/test-icon-orientation.js records the canvas arcs and checks column 1 and 2 of every standard config against the 3D viewer's placement; the old code failed 21 of 27 checks (G6 2x10 column 1 drawn at -18 deg instead of -162 deg). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…zimuth axis Panel numbers: the 2D overlay labelled pattern blocks as if they were physical columns and looped over every num_cols (drawing labels past the right edge on partial arenas). It now labels each block with its physical panel through physicalPanelNumber() (block k = columns_installed[k], G6 row-major / older generations column-major), the same numbers as the 3D overlay: on G6 2x8of10 the first pattern panel reads 2/12, not 1/11. Azimuth axis: a degree scale under the 2D grid (its own canvas, so edit-mode hit-testing and captures are unchanged), in pattern coordinates: 0 deg = straight ahead at the middle, negative to the fly's left; same values as the Mollweide view and the generator. Ticks pick 15/30/45/60/90 deg to keep labels apart (G4 3x12of18: -120..+120 every 30 deg). CLAUDE.md gains the three partial-arena rules (full-circle pitch, block k = columns_installed[k], 0 deg = middle of the pattern) and points at LAB-296 for hardware. tests/test-pattern-2d-view.js runs the extracted helpers against every standard config, the 3D overlay's numbering and the Mollweide longitudes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…F color code Color variants of one grating used to share a name (the filename carried only the layout tag), and re-saves stacked tags (_4c_rir). Names now: - record the ON/OFF choice used to GENERATE the frames, stamped at generate time: PanelColor.filenameColorCode -> '_4c-B' (ON blue), '_4c-B-G' (ON blue, OFF green), '_4c-VB', '_4c-F80A' (custom levels, one hex digit per channel); nothing extra for the default (ON all / OFF dark), so default names are unchanged apart from the words below; - normalize the layout tag on save (PanelColor.stripColorTag): replaced, never stacked; - use short words: grat/star/edge/sine/offon/anim, rot/exp/trans, Ndot, _img, comb, hcw/hccw/vup/vdown. The controller keeps pattern names in a 64-byte buffer: <= 63 characters, plain ASCII, and <= 58 so a same-name re-upload (stored as X001_<name>) keeps its .pat. Nothing in the Designer checked this. js/pattern-set.js gains the shared rule (checkSdFilename, sanitizeSdFilename, fitSdFilename; both tools load it), and the Designer fits every saved and renamed name (fitPatternFilename: cleans characters, shortens only the descriptive middle, keeps the arena prefix and the color tag; the rename prompt says what it changed). The longest name Generate can produce is now 54 characters with the worst-case tag. Helpers are typeof-guarded, so a stale cached panel-color.js / pattern-set.js only means the old naming until a refresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ads, 0x85 label
- sdUploadOne runs PatternSet.checkSdFilename BEFORE sending the bytes: a name the
controller can't store (non-ASCII, > 63 chars, no .pat) fails at once instead of after
the whole upload (which left the data on the card as pattern.temp), and is not retried.
59-63 characters log a warning (a re-upload would lose .pat).
- An upload never overwrites: the firmware stores a same-name re-upload as X001_<name>.
sdLogicalName now strips that prefix, so the copy shows as a DUPLICATE of the original
(onSdListing warns; protocols refuse to guess) instead of a separate pattern while the
picker kept playing the old file. The upload also warns up front when the name is
already on the card.
- The upload opcode is SET_PATTERN_FILE 0x85; the Studio's error text ("write rejected"),
tooltips and comments said 0x8D, which the firmware doesn't have (also fixed in the
Console comment and CLAUDE.md's quiesce list).
CLAUDE.md records the filename rule. tests/test-sd-filenames.js covers the shared rule,
the Designer's names (every arena prefix + worst-case tag <= 58), withColorTag /
fitPatternFilename, sdLogicalName and the upload order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l-G6 warning From the adversarial review of candidate 2026-10-06 rc1 (Claude + Codex): - The shared SD name rule (PatternSet.checkSdFilename) refused anything outside [A-Za-z0-9._-], so the Studio blocked uploads that worked before: spaces and parentheses are legal FAT long-name characters (e.g. a browser's "… (1).pat"). It now errors only on what the card can't store (non-printable/non-ASCII, \ / : * ? " < > |, > 63 chars, no .pat, leading "."); the Designer still writes tidy names (SD_TIDY_CHARS) and says so when it changes one. - Cache tokens (?v=1006-partial-cols) on every consumer of the changed shared modules: icon-generator.js (Designer, Studio, Console, v3 designer, icon tool), pattern-set.js (Designer, Studio, Console, v3 designer), panel-color.js (Designer) — so the fixes reach browsers holding an old copy. Pinned in tests. - Partial-G6 warning (never blocks): current firmware ignores the panel mask and rejects any grid other than its own at load. The Designer says so once per session when a partial-G6 pattern is saved; the Studio upload warns when the session rig is a partial arena, when a file carries a partial panel mask, or when its grid differs from the rig's (PatternSet.g6HeaderGrid / g6PlaybackWarning; Studio.rigArena()). - 2D panel numbers: a block past the installed list labels its own number, never NaN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0e4c49b to
123a1d2
Compare
|
Adversarial review of candidate 2026-10-06 rc1. Reviewers: Claude (independent), Codex GPT-5.5 standard and Codex GPT-5.5 adversarial. No blockers. No reviewer found a correctness problem in the partial-arena geometry or a regression on full arenas. Fixed in rc2 (#246 at 84daa99, candidate
Deliberately not changed: the upload name check stays fail-open if Follow-ups (not in this candidate):
Bench checks still to do:
|
…he thumbnail download (LAB-298)
Bench 2026-10-07 (rig cshl_g6_2x10_ball, 24 patterns on the card): the first
TRIAL_PARAMS after Connect was acknowledged 57 s late. The SD listing auto-picks
pattern 1 and the picker thumbnail downloaded the whole file (0x84). With
Chrome's default 255-byte Web Serial read buffer the body arrived 901 bytes
short, and the one-at-a-time link waited out the read's 60 s timeout with the
GET_PATTERN_INFO sweep and the user's command queued behind it. Firmware ruled
out from Python (listing + 24x 0x88 in 48 ms, 81 KB download in 6 ms).
- arena-link.js: open the port with a 1 MiB bufferSize; a background lane
(`{ background: true }`) that waits while any foreground send is queued; a
bulk body fails after idleTimeoutMs (5 s) without a byte instead of waiting
out timeoutMs.
- Studio: the GET_PATTERN_INFO sweep (cardPatternInfo, also caches file size)
and the picker thumbnail fetch use the background lane; a card-only
thumbnail downloads only when PatPreview.liveFetchVerdict allows it (size
known, <= 1 MiB, no run). Frame counts for the closed-loop resolver (#201)
are still filled for every pattern. Tooltip explains a thumbnail refused
while the display runs (status 10).
- Tests: link buffer/stall/lanes, liveFetchVerdict, Studio call sites.
Verified on hardware: first TRIAL_PARAMS ack 2 ms (was 57,013 ms); 81 KB
thumbnail download 7 ms; 24 frame counts done 197 ms after Connect.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
123a1d2 to
b4f9ebf
Compare
…ipherals (LAB-214, Studio v0.96) - js/arena-wire-g6.js: GET_I2C_SCAN 0xB0 / I2C_TRANSFER 0xB1 encoders + decoders (status text, "No Qwiic jack" detection) with golden tests. - js/qwiic-drivers.js (new): host register maps ported from the firmware repo's tests/qwiic_sensors.py + qwiic_peripherals.py — SHT4x (two-leg read, CRC), LSM6DSOX (tilt, latched bump: LIR + INT_CLR_ON_READ + INT1 routing), TSL2591, VEML7700, AS7343 (releases the bus while integrating), PCA9685 (ALLCALL off, servo pulse), PCA9548; an async bus mutex; all-call shadow decided from MODE1; the KINDS registry is the one place to add or drop a part. - js/studio-qwiic.js (new): rig `peripherals:` parser (tolerant, warnings), the scan-vs-YAML check in both directions (declared-but-missing = error, attached-but-undeclared = warning), setup, chip, "Copy as rig YAML". - arena_studio.html: one Console "Qwiic" window (Scan → device dropdown → one device's readout in display units; bump check; servo go-to-position), a connect-time setup scan and a top-bar chip. Every I2C command is gated on the 0xC2 qwiic_i2c feature bit: controllers without the jack are never sent any. Test only — nothing is written to run logs (that is PR 2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nection columns, no sticky "live" status (LAB-214) Bench feedback (all six boards working on the 12-18): - The scan table is the picker: one radio row per identified device (click the row or its button); the dropdown is gone. - Each row names its category (Servo / PWM, Temp / humidity, Accel / tilt / bump, Light (broadband / lux / spectral), I2C mux) — KINDS.category, one place. - "where" is now "connection": direct (on the controller's own chain) or "mux 0x70 · ch N" (behind a PCA9548 channel). - Stopping Live restores the scan summary; a tick in flight at Stop no longer re-writes "live · N Hz". - Table text left-aligned (it inherited the analog table's right alignment). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-214) - js/arena-configs.js regenerated (scripts/generate-arena-configs.js) from the maDisplayTools branch feat/arenas-1x12of18-3x10of14: G6 arena IDs 7 (G6_1x12of18) and 8 (G6_3x10of14). angle_offset_deg centers the gap at the back so the installed panels sit centered at the front in the 3D view and icons (Mercator/Mollweide center the installed arc by construction). - configs/rigs/g6_1x12of18_bench.yaml: the arena_12-18 bench, one row, with the six-board Qwiic chain declared under peripherals:; g6_3x10of14.yaml with peripherals: [] until its chain is known. Both added to configs/rigs/index.json. - tests: geometry, IDs, centering, index entries, clean peripherals parse. The maDisplayTools change must merge first: the sync-arena-configs workflow regenerates arena-configs.js from maDisplayTools main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s, color-bank figure; Qwiic read-on-select (LAB-214)
- Qwiic: clicking a device reads it immediately; Live keeps running across device
changes (each tick reads the selected device); the servo follows its slider while
dragging.
- Console sliders act while dragging — Test display level/duty re-send the last
figure, LED power sets the LED — through liveSender(): single-flight, latest
value wins, nothing sent while a run owns the link, one log line on release.
- Test display streams at the CONTROLLER's frame geometry (0xCB rows×cols; fw
panel_count_per_frame) instead of refusing every non-20-panel rig: the rig figure
is placed in the firmware frame (installed columns from the left, rig rows
repeated up), so the 1×12 bench drives a 4×12 build. Wire.streamFrameBytes +
encodeStreamFrame(payload, {panels}) with golden tests; .bin/.pat file streaming
stays 20-panel.
- Test display → color A–D: light one LED bank (T0–T3) at a time via the shared
PanelColor.bankAt (100 of 400 LEDs per panel).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…commit bumps; notes live in the PR body Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b4f9ebf to
af9ffef
Compare
Adversarial review of candidate 2026-10-06 rc4 (
|
…n), live gates, deferred Qwiic setup (LAB-214) From the adversarial review of candidate 2026-10-06 rc4 (PR #247 comment): - BLOCKING: test figures are drawn on the rig's FULL arena grid again (activeArenaGeom = num_rows × num_cols); grid column c → firmware column c and only the installed columns must fit the firmware frame. rc4 collapsed partial arenas to installed width and left-aligned them, which shifted every figure one panel on G6_2x8of10 (columns 1–8) with 0xCB firmware and refused it outright on pre-0xCB firmware; it also mis-numbered the ISP progress map and changed the paste dimensions. Mapping now lives in js/studio-stream-geometry.js with Node tests (2×10 / 2×8of10 identity on both firmware kinds, 1×12of18 → 4×12). - Live slider re-streams and Qwiic reads pause during an ISP batch and a post-mortem (a stream re-activates the display; 0xC8/0xC9 need it stopped). - Connect-time Qwiic setup starts after the controller reads and is not awaited, so its sensor configure waits never delay the SD listing. - Orientation glyphs sit inside the installed span (1×12of18's "R"). - Best-effort servo release (PCA9685 all off) on an intentional disconnect. - Docs: drop the stale "v0.96" (the release commit owns versions). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…apping, unmirrored icons, short SD-safe filenames, upload name checks (LAB-295)
… behind the thumbnail download (LAB-298)
…ve Console sliders, firmware-sized test display + color banks (LAB-214)
af9ffef to
a4a2a9c
Compare
|
✅ Validated Validated a4a2a9c by proxy. The #254 branch head c2a1c5e (which contains this rc5 tree through its merge of origin/next) ran real experiments on ww18 (2026-10-15: closed loop + BIAS, 10-min soak, idx mismatch 0.00 %) and on the Bergamo (2026-10-16: SD upload of four GS16 patterns, Connect / first-command latency, a 26/26-step 2P run). Not exercised on hardware: the Qwiic Console panel beyond Michael's 12-18 bench. rc5 itself was not run as its own SHA on a rig. |
Release candidate
2026-10-06rc5 · candidate commita4a2a9c829· built on main7c68dfcee8Live on Next: https://reiserlab.github.io/webDisplayTools/next/
5b0b30dcce84daa996a1546d9c62566bbd8d1b36Promotion checklist
pixi run releasechecks)Validated a4a2a9c …(pixi run tiers -- validate --rig … --by …). Any change to a constituent PR = rebuild (rc+1) = validate again.releaselists recent rig runs — advisory)fictrac-bridge/changed, rig PCsgit pull+ restart the bridgePromote with
pixi run release(merges with a merge commit — never squash — and tagsrelease-2026-10-06). Manifest:releases/2026-10-06.json.🤖 Generated with Claude Code release tooling (
scripts/tiers/candidate.py)