Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 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
5cc7783
ros2-poc: ROS 2 bridge proof of concept (LAB-303) — Zenoh graph, adap…
cshl-2026 Oct 14, 2026
890e247
ros2-poc: review fixes + further automated checks (soak, zero-loss, r…
cshl-2026 Oct 14, 2026
6462415
ros2-poc: portable process-group control (Windows: CTRL_BREAK_EVENT /…
claude Oct 14, 2026
48aee7f
ros2-poc README: 60-min soak result (78/78, flat latency, RSS creep a…
claude Oct 14, 2026
213adfd
ros2-poc: Mac test results (LAB-303) + warn on a relative BIAS filename
mbreiser Oct 14, 2026
6245ba7
ros2-poc: Windows rig test plan + portable SIGKILL constant
mbreiser Oct 14, 2026
4dd129f
Studio v0.95: BIAS camera commands run from the browser (LAB-303 conn…
mbreiser Oct 14, 2026
c6a4019
ros2-poc README: BIAS-from-the-protocol end-to-end result (Studio v0.…
mbreiser Oct 14, 2026
f505cf9
ros2-poc: Plan B spike — MCAP record of truth, typed interfaces, conv…
mbreiser Oct 14, 2026
50ca2b5
ros2-poc README: Plan B spike — Mac bench end-to-end results (G6 cont…
mbreiser Oct 14, 2026
d547f3d
ros2-poc: ready for the Windows test — opt-in typed/viz envs, export …
mbreiser Oct 15, 2026
0991e8f
ros2-poc: Foxglove layout for arena bags (plots from the typed frames…
mbreiser Oct 15, 2026
35a40b0
ros2-poc: crash-safe while open, indexed when closed — re-index + zst…
mbreiser Oct 15, 2026
1996a71
ros2-poc docs: Foxglove capture of the re-indexed bench bag in the ex…
mbreiser Oct 15, 2026
0dc96ba
protocols: G6 color optomotor test (one LED color at a time, top row,…
mbreiser Oct 15, 2026
81259e3
ros2-poc: Foxglove as a one-command live view — `rig --foxglove` + de…
mbreiser Oct 15, 2026
d8a2a36
protocols: 2-minute SMOKE variant of the color optomotor test (10 s p…
mbreiser Oct 15, 2026
077f7b5
Merge origin/next (release 2026-10-06 rc5: Studio v0.95 with the SD/f…
mbreiser Oct 15, 2026
18ff241
ros2-poc harness: judge the typed ArenaFrame by VALUE, not schema name
mbreiser Oct 15, 2026
d222432
ros2-poc docs: color optomotor smoke run on the Mac bench (Next Studi…
mbreiser Oct 15, 2026
d5a9d1b
ros2-poc docs: explainer v4 — color optomotor test + smoke result, li…
mbreiser Oct 15, 2026
110c1dd
ros2-poc docs: standalone single-file explainer (images inlined) + th…
mbreiser Oct 15, 2026
15bc39e
ros2-poc WINDOWS-TEST: paste-ready kickoff prompt + PC prerequisites …
mbreiser Oct 15, 2026
ad485c4
ros2-poc WINDOWS-TEST: bare-machine bootstrap prompt (git/pixi/MSVC v…
mbreiser Oct 15, 2026
06bafb8
ros2-poc WINDOWS-TEST: probe a custom BIAS build's HTTP interface bef…
mbreiser Oct 15, 2026
bd2e349
ros2-poc WINDOWS-TEST step 1a: expand the Zenoh config paths on Windows
mbreiser Oct 15, 2026
aeccdce
ros2-poc WINDOWS-TEST step 3: LF run logs on Windows + find the root …
mbreiser Oct 15, 2026
6250cdb
ros2-poc WINDOWS-TEST step 3: converter emits nothing after logging_s…
mbreiser Oct 15, 2026
4935732
ros2-poc WINDOWS-TEST step 6: graceful Ctrl-C stop + router pre-fligh…
mbreiser Oct 15, 2026
6e556fc
ros2-poc WINDOWS-TEST: ww18 results, steps 0-3, 6, 5b + BIAS probe
mbreiser Oct 15, 2026
667e538
ros2-poc X2: record device messages on the loop, under the shadow wri…
mbreiser Oct 15, 2026
cbd0d31
ros2-poc W8: never let bias_sim shadow the real BIAS on port 5010
mbreiser Oct 15, 2026
cc603f6
ros2-poc W9: measure durations with perf_counter (15.6 ms monotonic o…
mbreiser Oct 15, 2026
1d78deb
ros2-poc WINDOWS-TEST: ww18 results — X2, W8, W9 fixed; 5 runs 45/45
mbreiser Oct 15, 2026
e33316c
ros2-poc W9b: precise Windows clocks for every stamp + a 1 ms system …
mbreiser Oct 15, 2026
63adb30
ros2-poc WINDOWS-TEST: ww18 results — Windows clocks section (trade-o…
mbreiser Oct 15, 2026
5b0fcfb
ros2-poc WINDOWS-TEST step 4: a slow-command timeout for the camera
mbreiser Oct 15, 2026
a0634f1
ros2-poc WINDOWS-TEST step 5: keep backslashes in rig.py's --x-args o…
mbreiser Oct 15, 2026
7f4fd5a
ros2-poc WINDOWS-TEST step 5: ww18 protocol variants on the site 2x10…
mbreiser Oct 15, 2026
2584e03
ros2-poc BIAS (B2): the video BIAS really wrote + every frame's times…
mbreiser Oct 15, 2026
a8ea76d
ros2-poc WINDOWS-TEST: ww18 hardware results (steps 4, 5, 5.7, soak, …
mbreiser Oct 15, 2026
538cc85
ros2-poc W2 step 2: arena_interfaces packages on Windows (normalized …
mbreiser Oct 15, 2026
8102a85
ros2-poc W2 step 2: short build path via detached-environments (rig.p…
mbreiser Oct 15, 2026
ad5c7cd
ros2-poc W2 steps 1-4: typed messages on Windows — cause, workaround,…
mbreiser Oct 15, 2026
930126b
Studio: stop the run when the FicTrac bridge is lost mid-run (BRIDGE_…
mbreiser Oct 15, 2026
e7e233c
ros2-poc W2 docs: where the ww18 logs and repro recipes are kept
mbreiser Oct 15, 2026
0ed962a
Studio: a ?p= link no longer waits on a GitHub listing (LAB-303 S2)
mbreiser Oct 15, 2026
9bb44b0
ros2-poc: ScanImage-day brief — real 2P LED-intensity experiment thro…
mbreiser Oct 16, 2026
4632674
ros2-poc W2 step 3: upstream reports filed (rattler-build#2875, comme…
mbreiser Oct 16, 2026
952e9eb
ros2-poc W2 docs: correct the .pyc claim (rattler-build compiles and …
mbreiser Oct 16, 2026
711d246
ros2-poc W2 docs: §4a quotes the corrected pixi#7226 comment (edited …
mbreiser Oct 16, 2026
3051258
ros2-poc W2 step 4: a prebuilt arena_interfaces channel (win-64) for …
mbreiser Oct 16, 2026
43a6227
ros2-poc W2 step 4: typed-prebuilt env — typed messages on Windows wi…
mbreiser Oct 16, 2026
87f4723
ros2-poc W2 step 4: note the one-time backend start (metadata, no bui…
mbreiser Oct 16, 2026
93fc80d
ros2-poc step 1: scanimage_node.py — ScanImage's JSON server as a con…
mbreiser Oct 16, 2026
dc6124e
ros2-poc step 2: bridge node relays the scanimage connector (CONNECTO…
mbreiser Oct 16, 2026
405fc57
studio v0.96 step 3: scanimage plugin (ScanImagePlugin: setFileStem, …
mbreiser Oct 16, 2026
343ea11
protocols step 5: g6_2p_color_led_sweep — one ScanImage grab per LED …
mbreiser Oct 16, 2026
7211b2c
tools step 6: led_sweep_si.py — fallback driver for ScanImage's JSON …
mbreiser Oct 16, 2026
fa69aab
step 7: GS16 color gratings for the 2P sweep (make-color-optomotor-pa…
mbreiser Oct 16, 2026
7d137c5
SCANIMAGE-DAY: the two-hour version (install overlapped with hardware…
mbreiser Oct 16, 2026
cff5835
ros2-poc: auto-recover an unclosed bag on log_export (LAB-303 adoptio…
mbreiser Oct 16, 2026
ab207c0
ros2-poc: typed interfaces for rigs come from a CI-built channel (LAB…
mbreiser Oct 16, 2026
484b187
Studio v0.97: a lost bridge ends the run (BRIDGE_LOST); ?p= loads ahe…
mbreiser Oct 16, 2026
51ad95a
SCANIMAGE-DAY §6 prep: led_sweep_si.py prints UTF-8 on a cp1252 Windo…
hanmarsan Oct 16, 2026
ad20480
SCANIMAGE-DAY 0:20: status snapshot tolerates lastFrameNumber errorin…
hanmarsan Oct 16, 2026
90e64ca
SCANIMAGE-DAY 0:35: waitIdle tolerates lastFrameNumber erroring (thro…
hanmarsan Oct 16, 2026
6e6d0aa
SCANIMAGE-DAY 0:10: dev server threaded + listens on ::1 (single-thre…
hanmarsan Oct 16, 2026
1e1b6c2
SCANIMAGE-DAY 0:40: frames_per_grab 640 -> 330 (ScanImage runs 15.0 H…
hanmarsan Oct 16, 2026
2ae113d
SCANIMAGE-DAY 0:40 GO: dry run passed; pattern fallback IDs = the Ber…
hanmarsan Oct 16, 2026
39c3d52
SCANIMAGE-DAY 5: frames_per_grab 640 for 256x256 at 29.1 Hz (Michael …
hanmarsan Oct 16, 2026
87d98f4
SCANIMAGE-DAY 5: frames_per_grab 800 (host-timed steps ran ~3.6 s for…
hanmarsan Oct 16, 2026
0ac99e4
SCANIMAGE-DAY 5: run 2 passes (800 frames, every color fully imaged, …
hanmarsan Oct 16, 2026
7cefe51
SCANIMAGE-DAY 5: host-timed steps 4.0 s = Chrome background-timer thr…
hanmarsan Oct 16, 2026
1ef14c4
SCANIMAGE-DAY 5: quick look at run 2 (tools/led_sweep_quicklook.py: d…
hanmarsan Oct 16, 2026
c2a1c5e
SCANIMAGE-DAY: MATLAB messages (JSON client disconnects, PLL unlocked…
hanmarsan Oct 16, 2026
0b1d212
Studio/bridge client: review fixes for the bridge-loss policy (LAB-30…
mbreiser Oct 18, 2026
0b2b2a3
Merge #257 (BRIDGE_LOST + ?p= hardening) into the Plan B branch; drop…
mbreiser Oct 18, 2026
f7ae2eb
#254 prep for candidate 2026-10-20 (LAB-303): review fixes, hygiene, …
mbreiser Oct 18, 2026
6903f8a
ros2-poc: after-the-fact Foxglove MCAP for a 2P color sweep (tiff2mca…
mbreiser Oct 18, 2026
4c42563
ros2-poc: shared-memory and MCAP recording benchmarks + how to run th…
mbreiser Oct 22, 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
18 changes: 16 additions & 2 deletions .claude/nocache-server.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"""
import http.server
import os
import socket
import sys
import threading

# Serve from the repo root (two levels up from .claude/).
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
Expand All @@ -31,8 +33,20 @@ def log_message(self, fmt, *args):
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else int(os.environ.get("PORT", "8091"))
os.chdir(ROOT)
httpd = http.server.HTTPServer(("127.0.0.1", port), NoCacheHandler)
sys.stderr.write("Arena Studio dev server: http://127.0.0.1:%d/ (root: %s)\n" % (port, ROOT))
# Threaded: with the single-threaded HTTPServer, Chrome's idle pre-connected sockets
# block every other request, so the Studio's module graph (≈ 60 imports) stalls and the
# whole <script type="module"> block never runs (Bergamo PC, 2026-10-16).
httpd = http.server.ThreadingHTTPServer(("127.0.0.1", port), NoCacheHandler)
# Also listen on ::1: on Windows `localhost` tries IPv6 first, and a refused ::1 costs
# ~2 s per connection before the IPv4 fallback (measured: 2025 ms vs 23 ms).
try:
class V6(http.server.ThreadingHTTPServer):
address_family = socket.AF_INET6
httpd6 = V6(("::1", port), NoCacheHandler)
threading.Thread(target=httpd6.serve_forever, daemon=True).start()
except OSError:
pass
sys.stderr.write("Arena Studio dev server: http://localhost:%d/ (root: %s)\n" % (port, ROOT))
try:
httpd.serve_forever()
except KeyboardInterrupt:
Expand Down
140 changes: 140 additions & 0 deletions .github/workflows/arena-interfaces.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Build ros2-poc/interfaces/arena_interfaces (the typed ROS 2 messages of the arena bridge graph,
# LAB-303) ONCE per platform in CI and publish the result as a conda channel, so a rig PC installs
# typed messages as a download — no MSVC, no build backend, no MAX_PATH workaround. Decision and
# the consumer-side manifest edits: ros2-poc/docs/typed-distribution-decision.md; the measurements
# behind it: ros2-poc/docs/typed-windows.md.
#
# Runs: build-only on a PR that touches the package or this file (proves the three platforms still
# build); the `arena-interfaces-channel` artifact on every run; the hosted-channel upload only on an
# `arena-interfaces-v*` tag AND only when the repository variable ARENA_INTERFACES_CHANNEL names a
# prefix.dev channel (with the PREFIX_API_KEY secret). Until the lab owns one, the channel artifact
# is committed into ros2-poc/channel/ by hand (the decision note has the recipe).
name: arena-interfaces

on:
pull_request:
paths:
- 'ros2-poc/interfaces/**'
- '.github/workflows/arena-interfaces.yml'
push:
tags: ['arena-interfaces-v*']
workflow_dispatch:

permissions:
contents: read

concurrency:
group: arena-interfaces-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

env:
# The pixi + pixi-build-ros pair the package was last built and tested with (ww18 2026-10-16,
# docs/typed-windows.md). Bump deliberately: the backend is NOT in pixi.lock.
PIXI_VERSION: v0.80.0

jobs:
build:
name: build (${{ matrix.subdir }})
strategy:
fail-fast: false
matrix:
include:
# windows-2022 carries VS 2022 → conda-forge's vs2022 activation finds the v143
# toolset without the Build-Tools-2026 detour of docs/typed-windows.md W2.
- { os: windows-2022, subdir: win-64 }
- { os: macos-14, subdir: osx-arm64 }
- { os: ubuntu-24.04, subdir: linux-64 }
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
working-directory: ros2-poc
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: prefix-dev/setup-pixi@v0.10.2
with:
pixi-version: ${{ env.PIXI_VERSION }}
run-install: false
# MAX_PATH: the runner's checkout (D:\a\webDisplayTools\webDisplayTools\ros2-poc, 45
# chars) plus the ~211 chars the rosidl build adds crosses 260. detached-environments
# moves .pixi (build workspace included) to a 4-char root — the ww18 recipe.
- name: Windows build root
if: runner.os == 'Windows'
run: pixi config set --local detached-environments 'C:\px'
- name: pixi build arena_interfaces
run: |
rm -rf ../dist
pixi build --path interfaces/arena_interfaces/pixi.toml --output-dir ../dist
mkdir -p ../dist/${{ matrix.subdir }}
mv ../dist/*.conda ../dist/${{ matrix.subdir }}/
ls -la ../dist/${{ matrix.subdir }}
# The archive must be a well-formed .conda for the subdir it claims; `..` in a packaged
# path (the Windows bug the CMakeLists workaround covers) would already have failed above.
- name: inspect the package
run: |
f=$(ls ../dist/${{ matrix.subdir }}/*.conda)
python3 - "$f" <<'EOF'
import sys, zipfile
path = sys.argv[1]
names = zipfile.ZipFile(path).namelist() # a .conda = zip{pkg-*.tar.zst, info-*.tar.zst, metadata.json}
assert any(n.startswith("pkg-") for n in names) and any(n.startswith("info-") for n in names), names
print(path, names)
EOF
- uses: actions/upload-artifact@v5
with:
name: arena_interfaces-${{ matrix.subdir }}
path: dist/${{ matrix.subdir }}/*.conda
if-no-files-found: error
retention-days: 30

channel:
name: channel (index + artifact)
needs: build
runs-on: ubuntu-24.04
steps:
- uses: prefix-dev/setup-pixi@v0.10.2
with:
pixi-version: ${{ env.PIXI_VERSION }}
run-install: false
# One artifact per subdir → channel/<subdir>/<pkg>.conda (upload paths are relative to
# dist/<subdir>, so the subdir is restored here from the artifact name).
- uses: actions/download-artifact@v5
with:
pattern: arena_interfaces-*
path: artifacts
- name: assemble + index the channel
run: |
mkdir -p channel
for d in artifacts/arena_interfaces-*; do
subdir=${d#artifacts/arena_interfaces-}
mkdir -p "channel/$subdir"
cp "$d"/*.conda "channel/$subdir/"
done
pixi exec rattler-index fs channel
find channel -type f | sort
python3 - <<'EOF'
import json, pathlib
for rd in sorted(pathlib.Path("channel").glob("*/repodata.json")):
pk = json.load(open(rd))["packages.conda"]
for name, rec in pk.items():
print(rd.parent.name, name, rec["sha256"], [d for d in rec["depends"] if d.startswith("ros2-distro-mutex")])
EOF
# The deliverable: a complete three-platform channel. Commit it into ros2-poc/channel/
# (decision note §"Releasing a new build") or let the step below upload it.
- uses: actions/upload-artifact@v5
with:
name: arena-interfaces-channel
path: channel
if-no-files-found: error
retention-days: 90
# Hosted channel (prefix.dev), only when the lab has one: set the repository variable
# ARENA_INTERFACES_CHANNEL to its name and the secret PREFIX_API_KEY. Tags only, so a
# PR build can never publish.
- name: upload to the hosted channel
if: startsWith(github.ref, 'refs/tags/arena-interfaces-v') && vars.ARENA_INTERFACES_CHANNEL != ''
env:
PREFIX_API_KEY: ${{ secrets.PREFIX_API_KEY }}
ARENA_INTERFACES_CHANNEL: ${{ vars.ARENA_INTERFACES_CHANNEL }}
run: pixi exec rattler-build upload prefix --channel "$ARENA_INTERFACES_CHANNEL" channel/*/*.conda
11 changes: 9 additions & 2 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ on:
description: 'EMERGENCY ROLLBACK ONLY: a release-… tag or main commit to serve at the site root'
required: false
default: ''
ros:
description: 'ROS Studio side tier at /ros/ (LAB-303 Plan B spike): "none" or the spike branch commit SHA'
required: false
default: 'none'

# Sets permissions for GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
Expand Down Expand Up @@ -58,17 +62,20 @@ jobs:
# self-checks that Production is byte-identical to its commit apart from the
# stamp. A Next problem never blocks Production (placeholder instead).
# Inputs go through env, never interpolated into the script.
- name: Assemble Production + Next
# The optional /ros/ side tier (ROS Studio, LAB-303) comes from a dispatched SHA; a
# problem there is skipped, never a failed deploy. Off ("none") on every push.
- name: Assemble Production + Next (+ ROS Studio when dispatched)
env:
NEXT_INPUT: ${{ inputs.next || 'auto' }}
ROOT_REF: ${{ inputs.root_ref }}
ROS_INPUT: ${{ inputs.ros || 'none' }}
run: |
if [ -n "$ROOT_REF" ]; then
git merge-base --is-ancestor "$ROOT_REF" origin/main \
|| { echo "root_ref $ROOT_REF is not on main's history"; exit 1; }
fi
python3 scripts/tiers/stamp.py build --out _site \
--root-ref "${ROOT_REF:-$GITHUB_SHA}" --next "$NEXT_INPUT"
--root-ref "${ROOT_REF:-$GITHUB_SHA}" --next "$NEXT_INPUT" --ros "$ROS_INPUT"

- name: Upload artifact
uses: actions/upload-pages-artifact@v5
Expand Down
Loading
Loading