Glow 2027 Project Knowledge Base — Cary, NC Post Office plaza
Radiant Core is a temporary interactive art installation for Glow 2027 in Cary, NC. It transforms passive observers into active participants through motion, voice, and presence detection driving synchronized LED displays and generative animations.
| Detail | Value |
|---|---|
| Festival | Glow 2027, Cary, NC — Post Office plaza |
| Artists | Wes Swain / Geeksmithing, LLC + Thure / When Geeks Craft |
| Background | Pac-Man LED Matrix Lantern (Jan 2025) — WLED + ESP32 experience |
| Artistic intent | Prompt questions about the relevance of the humble pixel in modern life and our relationship with it |
Overall dimensions
- Total height: 7.5 ft
- Base: trapezoidal, 7 ft wide, 2" steel tubing, ~1,562 lbs — Config B, wind load verified
- Outer cube: 5 ft cuboid framework atop the base
- Inner cube: 3 ft solid cube, suspended diagonally inside outer cube
LED display surfaces
- 4 outward-facing sides of inner 3 ft cube: P10 LED matrix panels
- Outer 5 ft cube: LED strips tracing perimeter of each of 4 outward-facing sides — 16 lengths total, picture-frame style on each cardinal face
- No strips on frame edges or diagonal struts
Panel layout — per face
| Spec | Value |
|---|---|
| Grid per face | 3 wide × 6 tall = 18 panels |
| Resolution per face | 96 × 96 pixels |
| Face dimension | 960mm × 960mm — designed to 960mm (~45mm over 3 ft / 915mm) |
3D printed pixel isolation grids
- Material: matte black PETG or ASA — UV stable, no PLA outdoors
- 1:1 true pixel: 10mm cell per LED — ~80+ hrs print time for 4 faces
- 2×2 chunky pixel: 20mm cell — retro look, bold at distance, best crowd appeal
- Plan: mix styles across faces — each face different character per artistic intent
Matrix panels — P10 (confirmed, all faces)
P10 (10mm pitch) is the only panel choice. Finer pitches (P6, P4, P3) are not viable — 3D-printed pixel isolation grids become impractical below 10mm cell size.
| Spec | Value |
|---|---|
| Pixel pitch | 10mm |
| Panel size | 320mm × 160mm (32×16 px) |
| Resolution per face | 96×96 pixels |
| Panels required | 72 panels (4 faces × 18) |
| Panels ordered | 80 panels (72 + 8 spares) |
| Panel cost | $13/panel incl. shipping — $1,040 total for 80 |
| Weather rating | IP65 outdoor |
| Interface | HUB75 — driven by Colorlight 5A-75E cards |
Matrix driver cards — Colorlight 5A-75E (confirmed)
| Spec | Value |
|---|---|
| Quantity | 4 cards (one per face) |
| HUB75 ports | 16× onboard — no adapter boards needed |
| Max pixels per card | 256×1024 — vastly exceeds 96×96 |
| RGB data groups | 32 (vs. 16 on older 75B) |
| Refresh rate | 240Hz |
| Input | Gigabit Ethernet — receives DDP/E1.31 from Jetson |
| Cost estimate | ~$15–25/card — ~$60–100 for 4 |
LED strips — outer frame faces
- 16 lengths total — 4 per outward-facing side of the 5 ft outer cube, picture-frame style
- Total run: ~20m (16 × ~1.25m per side)
- SK6812 RGBW 5V — recommended. WLED native, white channel for better glow.
- WS2812B 5V — familiar fallback from Pac-Man build
- Controllers: ESP32 nodes running WLED — permanent display layer
Storage — NVMe per Jetson
- 500GB M.2 2280 M-key NVMe per Jetson unit — confirmed slot: M.2 Key M with x4 PCIe Gen3
- Do not use microSD for production — slow random I/O, corruption risk at festivals
- Recommended: WD Black SN770 500GB or Kingston NV2 500GB — ~$35–40/unit
- 500GB provides headroom for raw camera logging for offline analysis or model retraining
Confirmed components
- Central compute: 1 or 2× Jetson Orin Nano modules (~$249 each) — real CUDA, 1024-core Ampere GPU, 40 TOPS
- Storage: 500GB M.2 NVMe per unit (~$35–40 each)
- Matrix drivers: Colorlight 5A-75E (one per face, four total) — receive DDP/E1.31 UDP from Jetson(s)
- Strip controllers: ESP32 + WLED — permanent display layer, receive E1.31
- Cameras: 4× Logitech C920/C922 USB webcams — one per face, Sony IMX sensor, native MJPEG hardware compression
- Camera upgrade path: OAK-D offloads 100% of CV compute from Jetson GPU if needed — strong contingency
1 Jetson vs. 2 Jetsons — open question (pending benchmark)
The 2-Jetson split is increasingly appealing. Full 4-face synchronized animation is not a hard requirement — face-independent interaction may serve the artistic intent better anyway. Decision deferred until benchmark results on first unit.
| 1 Jetson ($249) | 2 Jetsons ($498) | |
|---|---|---|
| Camera load | 4 cameras, 4 async threads | 2 cameras each — more headroom |
| Inference headroom | Tight if all models active | Comfortable — half the load each |
| Cross-face sync | Trivial — shared memory | Needs MQTT/UDP coordination |
| Full sync animation | Easy | Harder — inter-unit latency |
| Whisper / LLM | One instance | One unit, results shared via MQTT |
| Failure mode | Single point of failure | One unit fails, others continue |
| Complexity | Simpler | More config + MQTT glue |
Compute tier reference (historical)
| Tier | Hardware | Cost | Status |
|---|---|---|---|
| 1 — ESP32 | ESP32 + WLED | $25–50/node | Retained — display layer |
| 2 — Pi 5 | Raspberry Pi 5 8GB | $200–240 | SKIPPED |
| 3 — Jetson | Jetson Orin Nano | $249/unit | CONFIRMED — 1 or 2 units |
| 4 — Full PC | Mini PC + GPU | $900–1,400 | Deferred — post-Glow if TD needed |
Display / LED
- Colorlight 5A-75E: receives DDP/E1.31 UDP from Jetson, drives P10 HUB75 panels directly
- WLED on ESP32: drives LED strips, receives E1.31 from Jetson
- Python sacn / python-e131: Jetson sends frame data via UDP to all display nodes
AI / Vision (phase 2 — deferred until visual pipeline validated)
- TensorRT: YOLOv8n-pose (nano variant), FP16, exported on-device — primary inference engine
- MediaPipe: fallback / supplement for face and hand landmarks
- Whisper.cpp: local STT — model size TBD pending Orin Nano benchmark (Large may be too heavy)
- Ollama (Phi-3 Mini): local LLM, ~1s response, structured JSON output — deferred
- Piper TTS: local text-to-speech for compliment engine — deferred
Orchestration / Glue
- Python 3.11+ asyncio: non-blocking camera + audio + LED pipelines
- MQTT: pub/sub — "Presence on north face" → all nodes react
- FastAPI: REST + WebSocket on Jetson — control panel and sensor routing (on isolated CPU core)
- librosa: beat detection, FFT, RMS for crowd noise / music reactivity
Remote monitoring — Particle Boron LTE-M
Cellular health monitoring independent of festival Wi-Fi. Jetson publishes status JSON to Boron over USB serial every 60s; Boron forwards to Particle Cloud via LTE-M. Webhook to Discord/Slack for alerts.
| Field | Source | Notes |
|---|---|---|
| uptime_s | Jetson system | Seconds since last boot |
| cpu_temp_c / gpu_temp_c | Jetson thermal | Alert threshold ~85°C |
| cpu_load_pct | /proc/stat | |
| visitor_count | Orchestrator | Running total since boot |
| current_mode | Orchestrator | Active interaction mode name |
| cameras_ok | Camera health check | Bitmask: 1 bit per face camera |
| error_flags | Orchestrator | Bitmask for fault conditions |
Browser control panel — intended architecture
FastAPI on Jetson serves a live control UI — like WLED or LedFX. Any device on the same local network opens it in a browser. No SSH, no config files, no festival fumbling in the dark.
| Control | Description |
|---|---|
| Mode switching | Select active interaction mode from full inventory |
| Parameter sliders | Speed, intensity, palette, particle count, decay rate per mode |
| Palette picker | Live color palette swap across all faces |
| Face override | Force a specific mode on one face independently |
| Trigger effects | Fire one-shot effects (fireworks, ignite, crowd burst) manually |
| Visitor stats | Live visitor count, dwell times, current crowd level |
| Boron health | Jetson temps, uptime, camera status, error flags |
| LLM mood override | Manually set palette/speed/mode bias (phase 2) |
Body / skeleton driven
Voice / audio driven
Crowd / multi-person
Games
Layered stack, not discrete mode switching:
{"mode":"crowd_noise","palette":"fire","speed":0.8,"intensity":0.9} — tiny output, fast on Jetson.Model selection ▸ plain english
YOLOv8 is the AI model that looks at a camera frame and finds people's bodies and joint positions — shoulders, elbows, wrists, hips etc. It comes in different sizes: nano, small, medium, large, extra-large. Bigger = more accurate but slower.
For your use case the nano variant (YOLOv8n) is plenty accurate and fast enough to run on 4 cameras at once. Using anything larger just burns compute you don't have for accuracy you don't need.
TensorRT is NVIDIA's way of taking that AI model and compiling it specifically for your exact GPU so it runs as fast as physically possible. FP16 means it uses half-precision math — the Ampere GPU has dedicated hardware for it, so it runs roughly twice as fast with no meaningful quality loss for pose detection.
The imgsz flag just tells TensorRT what resolution the camera frames will be. If you don't match it to your actual capture resolution, the model wastes time padding or rescaling every frame before it can even look at it.
- Model: YOLOv8n-pose — "n" = nano size tier in Ultralytics naming (not Jetson Nano specific). ~3.2M parameters, highly efficient for multi-stream.
- Do not use YOLOv8s/m/l/x-pose — too heavy for 4-stream inference on a single Orin Nano
- Precision: TensorRT FP16 — leverages Ampere tensor cores, sub-10ms latency per frame
- CRITICAL: export the .engine file on the Jetson itself — TensorRT engines are hardware-specific, a desktop-built engine will not run on the Orin Nano
| Step | Command / Notes |
|---|---|
| Install | pip install ultralytics on Jetson |
| Export | yolo export model=yolov8n-pose.pt format=engine half=True imgsz=[480,848] device=0 |
| Why imgsz matters | Matching export to capture resolution (848×480) avoids wasted compute on padding/upscaling |
| Output | yolov8n-pose.engine — use this in production, not the .pt |
| Hardware-specific | Always export on the Jetson itself — engine will not transfer to other hardware |
| Expected latency | ~2–4ms per frame FP16 at 848×480 — 4 sequential frames ~16ms, inside 33ms budget for 30fps |
| Verify | Run inference benchmark on Jetson before integrating into pipeline |
Camera ingest — MJPEG hardware decode ▸ plain english
By default if you just open a webcam in Python it soft-decodes the video on the CPU. Four cameras doing that simultaneously would eat all 6 ARM cores just decoding video frames — leaving nothing for the AI or the LED output.
The C920/C922 can compress video internally as JPEG frames before it even leaves the camera. GStreamer with nvjpegdec lets the Jetson's GPU decompress those frames in hardware instead of the CPU — so by the time Python even sees a frame, it cost almost zero CPU to get there.
The memory:NVMM part means the decoded frame stays in GPU memory rather than bouncing out to CPU RAM and then back into GPU for the AI — saves an unnecessary round trip every single frame.
The appsink drop=true line means if the pipeline falls behind, it throws away old frames and always hands you the latest one. You never want a queue of stale frames building up — you want to know where the person is right now.
Do not capture at 1080p and resize in Python. Do not use raw H.264 soft decode. Use C920/C922 native MJPEG at 848×480, decoded on the Jetson GPU via nvjpegdec. Frames stay in unified VRAM.
- Capture format: MJPEG 848×480 30fps — C920/C922 handles MJPEG compression natively in hardware
- nvjpegdec: hardware JPEG decode on Jetson GPU — near-zero CPU utilization for video ingest
memory:NVMMflag: keeps decoded frames in Jetson unified VRAM, avoids CPU/GPU memory transferappsink drop=true max-buffers=1: always processes the latest frame, never builds a backlog- One isolated thread or multiprocessing worker per camera writing latest frame to shared memory
| GStreamer element | Purpose |
|---|---|
v4l2src device=/dev/videoN | Capture from USB webcam by device index |
image/jpeg, width=848, height=480, framerate=30/1 | Force MJPEG at 848×480 30fps at hardware layer |
nvjpegdec | Hardware-accelerated JPEG decode on Jetson GPU |
video/x-raw(memory:NVMM) | Frame stays in unified VRAM — no CPU bounce |
nvvidconv | Hardware memory format converter |
video/x-raw, format=BGRx | Convert to OpenCV/TensorRT compatible format |
appsink drop=true max-buffers=1 | Drop stale frames, guarantee zero latency |
Threading & CPU affinity ▸ plain english
The Jetson has 6 CPU cores. If you let the OS freely schedule everything, your LED output loop might get interrupted mid-packet by a web request hitting the control panel. That causes a visible flicker on the panels.
The fix is to pin each workload to a specific core — camera ingest here, AI inference here, LED output here, web server there — and they never step on each other. Think of it like dedicating lanes on a highway instead of letting all traffic merge freely.
The LED output core is the most important one to isolate. It needs to fire a UDP packet every 40ms like clockwork. Anything else sharing that core is a risk.
Pin each workload to specific cores. The Orin Nano has 6 ARM Cortex-A78AE cores.
| Core(s) | Assigned workload |
|---|---|
| Core 0 | OS, housekeeping, watchdog |
| Core 1–2 | GStreamer NVDEC camera ingest threads |
| Core 3 | TensorRT inference dispatch + result collection |
| Core 4 | sacn / python-e131 UDP output loop — isolated, never interrupted |
| Core 5 | FastAPI + MQTT + Boron serial — never on same core as UDP output |
- Use
tasksetoros.sched_setaffinity()to pin threads to cores - Isolating the E1.31/sacn UDP output loop is non-negotiable — HTTP request on same core causes LED frame drops
Batched vs. sequential inference ▸ plain english
Imagine you have 4 photos to show someone. You could hand them one at a time and get each answer back before handing the next. Or you could stack all 4 and hand them over at once — potentially faster since they can look at them all in one go.
Batched inference is the "stack all 4" approach. It's theoretically faster because the GPU handles them in one pass. The catch: you have to wait until all 4 cameras have a fresh frame before you can fire the batch. If one camera is a few milliseconds behind, everything waits.
Sequential async means you process each camera's frame as soon as it arrives — no waiting. A bit more overhead per frame but no one camera can hold everyone else up. Start here and only switch to batched if you need the extra speed.
Stacking 4 frames into a single TensorRT batch is theoretically faster than 4 sequential calls. In practice frames rarely arrive simultaneously — waiting for the slowest camera to fill the batch adds latency. Benchmark both.
| Approach | Pro | Con |
|---|---|---|
| Sequential async (4 separate calls) | Process each frame as it arrives, no wait | 4× inference call overhead |
| Batched (stack 4 frames, 1 call) | GPU processes all 4 in one pass | Must wait for all 4 cameras — slowest gates the batch |
- Start with sequential async (simpler), test batched if headroom is needed
Frame buffer architecture ▸ plain english
When the AI finishes processing a camera frame it needs to hand the result to the LED output thread. Python has built-in queues for this but they go through Python's global lock — meaning threads have to take turns accessing them, adding latency on every single frame.
A shared memory ring buffer bypasses that entirely. It's just a chunk of memory that both threads can read and write directly — like a whiteboard between two people in the same room vs. sending messages through a receptionist. Faster, no waiting.
The "ring" part means it wraps around — it holds the last N frames and the output thread always grabs the newest one. Old frames get overwritten automatically. No cleanup needed.
- Use shared memory ring buffers between inference threads and LED output thread — do not use Python queues
- Python queues add latency and GIL contention — unacceptable in a real-time LED pipeline
- Ring buffer holds last N rendered frames per face; LED output thread always reads latest available frame
- Use
multiprocessing.shared_memoryor numpy shared arrays for zero-copy frame passing
Camera fault tolerance ▸ plain english
If a cable gets kicked at the festival and a camera drops, without fault handling the whole Python process would likely crash or hang waiting for a frame that never comes — taking all 4 faces down with it.
The watchdog is just a simple check running every second: "did I get a new frame from camera 2?" If not, it tells the orchestrator to put that face into ambient animation mode and keep everything else running normally. When the camera comes back, it reattaches automatically.
The key principle: a hardware failure on one face must never be allowed to affect the other three faces or the LED output loop. Failures should be contained and degraded gracefully, not cascaded.
A camera dropping mid-festival must not crash the orchestrator or blank the LED panels.
| Failure scenario | Required behavior |
|---|---|
| Camera feed drops / corrupt frame | Per-camera watchdog detects loss within 1s |
| Face with dead camera | That face falls back to ambient/idle animation automatically |
| Camera recovers | Pipeline reattaches, resumes interactive mode without restart |
| All cameras dead | All faces fall back to ambient — Boron alert fires immediately |
| Orchestrator crash | Watchdog restarts orchestrator; ESP32 WLED nodes continue running last animation |
- Per-camera watchdog thread: no new frame within 1s → flag camera dead, notify orchestrator
- Orchestrator checks camera health flags each frame cycle, routes dead-camera faces to ambient mode
- Never let a camera failure propagate to the UDP output loop — LED panels must keep outputting regardless
UDP output — packet compilation ▸ plain english
Every 40ms the Jetson needs to send a UDP packet to each Colorlight card containing the full frame of pixel colors. That's a lot of packets flying out at a steady rhythm.
If you build those packets from scratch each time — allocating memory, building Python objects, converting them — that overhead adds up and can cause the rhythm to stutter. The fix is to pre-allocate the packet buffers once at startup and just overwrite the color values each frame. No memory allocation in the hot loop, no garbage collection surprises mid-festival.
And if the AI is running behind and doesn't have a fresh frame ready in time, the output loop should just re-send the last valid frame rather than going blank. The panels should never go dark just because inference is a frame late.
- Compile DDP / E1.31 packets as low-overhead byte arrays, not Python dicts or objects
- Pre-allocate packet buffers at startup — no runtime memory allocation in the output loop
- Target: LED output loop runs at locked 25fps cadence regardless of inference load
- If inference falls behind, output loop sends last valid frame — never a blank/dropped frame
Benchmark plan — before committing to 1 vs. 2 Jetsons ▸ plain english
Before deciding whether to buy a second Jetson, you need real numbers from the actual hardware. The plan is to start simple — one camera, does it hit 30fps? Yes: add a second camera. Still good? Add a third and fourth. If 4 cameras runs comfortably, one Jetson is enough.
If 4 cameras is struggling — say you're only getting 18fps or the CPU is pegged — that's your answer: 2 Jetsons with 2 cameras each. At $249 the second unit is affordable insurance, but run the test first before spending the money.
The last two tests check real-world conditions: not just "can it do pose inference" but "can it do pose inference AND blast UDP packets to the LED panels AND respond to a web request from the control panel, all at the same time, without the panels flickering." That's the actual festival scenario.
| Test | Target | Decision trigger |
|---|---|---|
| YOLOv8n-pose TRT FP16, 1 camera, 848×480 | 30fps+ | Baseline — must pass |
| YOLOv8n-pose TRT FP16, 2 cameras async | 30fps+ each | Confirms 2-Jetson split viability |
| YOLOv8n-pose TRT FP16, 4 cameras async | 25fps+ each | If passes: 1 Jetson sufficient |
| 4 cameras + sacn UDP simultaneously | No frame drops | Real-world combined load test |
| 4 cameras + sacn + FastAPI on separate core | No LED stutter on HTTP request | CPU affinity validation |
The browser tool radiant_core_96x96.html is the canonical animation sandbox. The 96×96 canvas matches physical panel resolution exactly — what you see in the browser is a faithful preview of the physical panels.
| Stage | Tool | Role |
|---|---|---|
| Design | HTML5 Canvas + vanilla JS | Build and tune at 96×96. Fast iteration, no hardware needed. |
| Port | Python + NumPy | Rewrite as function(frame_buffer, input_state, params) → frame_buffer |
| Test | Jetson + Colorlight | Drop Python function into pipeline. Verify on real P10 hardware. |
| Iterate | HTML → Python sync | Tweak in HTML for fast feedback, sync to Python version. |
Active effect categories
| Category | Modes |
|---|---|
| Physics | Cloth, rope, falling sand, ball collisions |
| Nature | Flowers, crystal growth, slime mould, mycelium |
| Space | Warp speed, meteor, jellyfish, deep sea |
| Fluid | Reaction-diffusion, viral sim, aurora |
| Art | Starry Night curl noise, lightsaber/whip |
| Pets | Assorted critter modes |
Python libraries for production
- numpy — frame buffer operations, vectorised math
- python-e131 / sacn — E1.31 sACN UDP output to Colorlight cards
- asyncio — non-blocking camera + audio + LED pipelines
- opencv-python — camera capture and image processing
- mediapipe — pose, face, and hand landmark detection
| Parameter | Value |
|---|---|
| Design wind speed | 115 mph — Exposure Category C, Risk Category II |
| Structure weight | 1,562 lbs |
| Total lateral wind force | ~1,067 lbs at 115 mph |
| Overturning moment | 3,599 ft·lbs |
| Restoring moment | 5,467 ft·lbs (1,562 lbs × 3.5 ft arm) |
| Safety factor unballasted | 1.52× ✓ — exceeds 1.5× target |
| Sand ballast required | 0 bags at design wind speed |
| No-tip limit | ~165 mph |
Keep two 50 lb sand bags on hand at each deployment as emergency reserve regardless.
Trailer solution
The 7ft base width (84") exceeds the 82" between-wheel deck width of standard rental equipment trailers. Solution: detachable trapezoidal base side panels that bolt on at the venue, reducing transport width to the rectangular core only.
- Trailer: 12ft Tilt Deck Flatbed Trailer, Single Axle — United Rentals Cary NC, $64/day
- Transport config: rectangular core only — narrow enough for trailer deck, fully stable on flatbed
- Deployed config: trapezoidal side panels bolted back on — full 7ft footprint restored, wind load calc holds as designed
- Safety factor concern only applies when panels are detached during transport — irrelevant since structure is not standing in wind on the trailer
Detachable trapezoidal base panels
The trapezoidal side panels are fabricated as separate bolt-on sections. They attach to the rectangular core via welded receiver flanges or bolt plates built into the core during fabrication. Wes and Thure are welding the entire structure in-house.
- Weld receiver flanges or bolt plates onto rectangular core during fabrication — before final welding of core
- Trapezoidal panels bolt on at venue with through-bolts or quick-release pins
- When attached: panels restore full 7ft footprint and 3.5ft restoring arm — 1.52× wind load safety factor intact
- Joint must genuinely transfer lateral load — adequate bolt pattern and flange sizing required, not just cosmetic attachment
- Structural engineer must review and stamp the bolt joint design as part of final sign-off
Base split concept
Red lines show the detachment point — trapezoidal side panels separate from the rectangular core for transport. Blue outlines show the removable panel sections. When bolted on at the venue the full footprint and wind load safety factor are restored.
Transport checklist
| Item | Detail |
|---|---|
| Trailer rental | United Rentals Cary — 12ft Tilt Deck Single Axle — $64/day |
| Confirm deck width | Call United Rentals Cary branch — verify usable deck width accommodates rectangular core |
| Tow vehicle | Confirm tow vehicle capacity vs. trailer weight + structure weight before booking |
| Load config | Rectangular core on trailer, trapezoidal panels loaded separately |
| Strapping | 4-point tie-down minimum on rectangular core |
| On-site assembly | Bolt trapezoidal panels onto core before raising structure |
| Sand bags | Two 50 lb bags on hand at each deployment as emergency ballast reserve |
| # | Question / action item |
|---|---|
| ★ F1 | FABRICATION: design and weld bolt flanges for detachable trapezoidal base panels into rectangular core before final welding |
| ★ F2 | FABRICATION: confirm rectangular core width fits Sunbelt 12ft tilt deck — call 919.851.0890 |
| ★ F3 | Confirm tow vehicle towing capacity before trailer booking |
| ★ 1 | IMMEDIATE: order one Jetson Orin Nano + 500GB NVMe, run visual pipeline benchmark suite before ordering second unit |
| ★ 2 | IMMEDIATE: benchmark YOLOv8n-pose TensorRT FP16 at 1/2/4 cameras 848×480 — results determine 1 vs. 2 Jetson decision |
| 3 | 1 vs. 2 Jetson Nanos — decision pending benchmark results |
| 4 | LLM / Whisper stack: DEFERRED — validate visual pipeline first |
| 5 | Decide inner cube face dimension: 960mm or trim to 915mm |
| 6 | 3D printed grid design: finalize cell sizes and which face gets which style |
| 7 | Steel fabrication timeline — wind load engineer review and stamp |
| 8 | Power budget: total draw for panels + strips + Jetson(s) + audio at full load |
| 9 | Weatherproofing strategy for Jetson enclosure in base |
| 10 | Confirm Glow 2027 application / submission requirements and deadlines |
| 11 | Thure's role: which parts of the build / which software layers |
| 12 | Festival Wi-Fi availability at Post Office plaza for weather API |
| 13 | Camera placement: FOV and mounting angle per face for reliable pose detection |
| 14 | Particle Boron: implement health monitoring script, set up webhook to Discord/Slack |