Grand Diomande Research · Full HTML Reader

CODEX WAVE 8 EXECUTION PLAN — Computational Choreography

> **Audience:** Codex CLI agent picking up Wave 8 mid-flight on Mac1. > **Date:** 2026-05-02 > **Author:** Claude (handing off coordinated work) > **Track:** Wave 8 — Computational Choreography (NOT Wave 9, NOT Duncan presets, NOT K11 deploy) > **Approved plan:** `[home-path]`

Embodied Trajectory Systems research note experiment writeup candidate score 40 .md

Full Public Reader

CODEX WAVE 8 EXECUTION PLAN — Computational Choreography

> Audience: Codex CLI agent picking up Wave 8 mid-flight on Mac1.
> Date: 2026-05-02
> Author: Claude (handing off coordinated work)
> Track: Wave 8 — Computational Choreography (NOT Wave 9, NOT Duncan presets, NOT K11 deploy)
> Approved plan: `[home-path]`

---

0. Mission

Wave 8 reframes LUME from "audio drives visuals, motion drives visuals" into "body and audio are two reactive surfaces of one performance." The body listens to the music AND plays it back. Visuals reward beat-coherence between body and audio. Beat moments freeze into decaying ghost poses (Duncan E598 "Dissolving Clones"). Synthetic mocopi data unblocks all of this without hardware.

There are six implementation steps. Steps 1 and 2 shipped. Steps 3-6 remain. Step 3 is owned by Claude (in flight). Codex executes Steps 4, 5, and 6.

---

1. Current State (verified 2026-05-02 ~20:10 PT)

git log -4 --oneline (Mac1 main, NOT pushed)
  95387a02 feat(lume): Wave 8 Step 3 — choreography scorer
  8139a931 feat(lume): Wave 8 step 2 — LumePoseSnapshotter + SAMPLE_SNAPSHOTS keyword
  8fb6fe75 feat(lume): Wave 8 step 1 — mocopi_synth.py procedural skeleton publisher
  30e87fa6 fix(lume): OSC packed vs per-bone address collision + 4 OSC decoder tests

Working-tree note: `Assets/Scripts/LumeMotionToAudio.cs` + `.cs.meta` exist on disk but are untracked (Step 4 was written by a prior session that didn't commit). Codex must inspect these before either committing as-is or rewriting.

Shipped artifacts

Step 3 (commit 95387a02):
- `Assets/Scripts/LumeChoreographyScorer.cs` `[DefaultExecutionOrder(226)]`
- 32 audio onsets (rising-edge of `LumeAudioFftReceiver.TryGetLatest` transient flag) + 32 bone-velocity peaks (default boneIndex=12, parent-chain world-pose resolution)
- Sliding window = `windowBars 4 60 / bpm` (windowBars=4, bpm=120 → 8s)
- Score: `1 - clamp(meanAbsDelta_ms / gracePeriodMs, 0, 1)`, EMA α=0.1
- Globals: `_ChoreographyScore`, `_ChoreographyScoreSmoothed`, `_ChoreographyHistory` (StructuredBuffer<float>)
- Static helpers `NearestDelta`, `EmaStep` are public + testable
- `Assets/Scripts/Editor/Tests/LumeChoreographyScorerTests.cs` (NEW Editor test)
- `Assets/Scripts/LumeCalibrationPanel.cs` EDITED — Wave 8 tunables registered
- `Assets/Scripts/LumeFluidSim.cs` EDITED — `_ChoreographyScoreSmoothed` pre-multiplier on audio turbulence
- `Assets/Shaders/LumePointInstanced.shader` EDITED — `APPLY_CHOREOGRAPHY_SCORE` keyword + brightness/saturation lerp

Step 1 (commit 8fb6fe75):
- `software/demo/mocopi_synth.py` — procedural LUMM publisher (idle/dance/chaos modes, free-running BPM clock OR `--sync-to-lumf` SO_REUSEPORT sniffer on :9701, reuses `mocopi_bridge.pack_lumm`)
- `software/demo/tests/test_mocopi_synth.py` — 10 fast + 1 slow CLI smoke
- pytest baseline: 73 fast / 6 slow (was 62 / 5)

Step 2 (commit 8139a931):
- `Assets/Scripts/LumePoseSnapshotter.cs` `[DefaultExecutionOrder(225)]`
- Ring buffer N=8, hold=0.25s, decay=2.25s quadratic ease-out
- Drift fixed at artistic 0.2 m/s² (NOT real g)
- Static helpers `SnapshotAlpha` / `SnapshotGravityDrift` for future tests
- Globals: `_SnapshotPositions` (StructuredBuffer<float4>), `_SnapshotCount`, `_SnapshotMaxBones`, `_SnapshotActive`
- Subscribes to `LumeTransientForcePusher.OnTransientFired`
- `Assets/Scripts/LumeTransientForcePusher.cs` EDITED — added `public event Action OnTransientFired` (try/catch around handler invocation)
- `Assets/Shaders/LumePointInstanced.shader` EDITED — `#pragma multi_compile_local _ SAMPLE_SNAPSHOTS` keyword + snapshot influence loop in `vert()` (4 new material props: `_SnapshotRadius`, `_SnapshotFrozenColor`, `_SnapshotLiveColor`, `_SnapshotMixGain`)

Hardware reality (does NOT block Wave 8)

  • Sony mocopi-pro activation code is in the mail. Skeleton source for the demo is the iPad path (existing, working). Wave 8 code only needs LUMM datagrams on :9702 — synthetic, iPad, or pro all satisfy that contract.
  • K11 is offline 4 days. Separate agent owns K11 bring-up. Codex MUST NOT touch K11 work.

---

2. Co-Execution Protocol (Claude ↔ Codex)

To avoid stepping on each other's edits while we both ship to Mac1 main:

Ownership split (current as of 2026-05-02 ~20:10 PT)

StepFile(s) createdOwnerStatus
3`LumeChoreographyScorer.cs` + shader edits + `LumeFluidSim.cs` editprior sessionDONE (commit 95387a02)
4`LumeMotionToAudio.cs` (+ .meta)prior sessionWRITTEN, UNCOMMITTED — needs review + commit
5`LumeChoreographyPanel.cs`openNOT STARTED
6`ARCHITECTURE.md` + `LumeWaveAutoWire.cs`openNOT STARTED

Coordination rules

1. Codex starts at Step 4 only after Claude's Step 3 commit appears. Check before starting:

bash
   git -C Desktop/lume-commerce log --oneline | grep "Wave 8 step 3"

If no Step 3 commit, STOP and wait. Do NOT start Step 4 against an inconsistent baseline (Step 4 reads `_ChoreographyScoreSmoothed` which Step 3 publishes).

2. Codex never edits files Claude is touching in Step 3. Specifically off-limits until Step 3 ships:
- `Assets/Shaders/LumePointInstanced.shader` (Claude adds `APPLY_CHOREOGRAPHY_SCORE` keyword here)
- `Assets/Scripts/LumeFluidSim.cs` (Claude adds turbulence pre-multiplier here)
- `Assets/Scripts/LumeCalibrationPanel.cs` (Claude registers Step 3 tunables here)

3. One commit per step. No batching. Each commit must be independently revertable. Commit message format:

   feat(lume): Wave 8 step N — <one-line summary>

4. Codex commits to Mac1 main directly. No branches. No pushes. Mohamed pushes manually after Wave 8 verification on Mac4.

5. If Codex hits a blocker, stop and write a memory file:

   [home-path]

Do NOT silently downgrade scope or skip features.

---

3. Hard Rails (preserved from Wave 1-7)

  • Wire format magic bytes (LUME / LUMD / LUMF / LUMM / LUMC) — pinned by `software/demo/tests/test_wire_format_golden.py`. Touch nothing in `mocopi_bridge.py`, `audio_pub.py` packing, or `pointcloud_pub.py` packing.
  • `LumeUdpReceiver.cs` dispatch — magic-byte routing is load-bearing.
  • `LumeAudioReactor.cs` — Wave 1 fallback / parallel session contract. Don't refactor.
  • `pointcloud_pub.py` CLI surface — public, used by health-check.sh.
  • Existing 17 components' public APIs — additive only. New components subscribe to events, read globals, or call existing public methods. Never modify a public method signature.
  • Wave 1 cloud rendering rescue floor — must keep working when SAMPLE_SNAPSHOTS / APPLY_CHOREOGRAPHY_SCORE keywords are off.
  • Execution-order ladder — pick exec orders that fit cleanly between existing components. Current ladder:
  -100  LumeDisplayController
     0  LumeOpticalFlow
   100  LumeFluidSim
   150  LumeMotionGate
   200  LumeAudioFftReceiver
   205  LumeMocopiReceiver
   210  LumeVfxRuntimeBridge
   215  LumeMotionToAudio          (NEW — Step 4)
   220  LumeTransientForcePusher
   225  LumePoseSnapshotter
   226  LumeChoreographyScorer     (NEW — Step 3, Claude)

---

4. Step 4 — `LumeMotionToAudio.cs` (Codex)

### Goal
Reverse the data flow: bones → OSC → external audio software (SuperCollider, Max, TouchDesigner, Pure Data). Unity is mediocre for synthesis; push out to where audio tooling lives.

### File
`software/demo/unity/lume_pcloud/Assets/Scripts/LumeMotionToAudio.cs` + `.cs.meta`

Spec

csharp
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

namespace Lume
{
    [DefaultExecutionOrder(215)]   // between AudioFftReceiver(200) and TransientForcePusher(220)
    [DisallowMultipleComponent]
    public sealed class LumeMotionToAudio : MonoBehaviour
    {
        [Header("Source")]
        [SerializeField] LumeMocopiReceiver mocopiReceiver;

        [Header("Output")]
        [SerializeField] string oscHost = "[ip]";
        [Range(1, 65535)] [SerializeField] int oscPort = 9050;
        [Range(30, 120)] [SerializeField] int updateRateHz = 60;
        [SerializeField] bool oscEnabled = true;

        [Header("Bone indices (Sony mocopi 27-bone)")]
        // Defaults match LumeMocopiReceiver bone naming. Right hand = 18, left hand = 22, etc.
        [SerializeField] int rightHandBone = 18;
        [SerializeField] int leftHandBone  = 22;
        [SerializeField] int hipBone       = 0;
        [SerializeField] int rightFootBone = 6;
        [SerializeField] int leftFootBone  = 10;
        [SerializeField] int headBone      = 14;

        [Header("Mappings (per-channel enable)")]
        [SerializeField] bool sendRightHandVel    = true;
        [SerializeField] bool sendLeftHandVel     = true;
        [SerializeField] bool sendHipRotation     = true;
        [SerializeField] bool sendFootDifferential = true;
        [SerializeField] bool sendBodyExtension   = true;
        [SerializeField] bool sendSpread          = true;
        [SerializeField] bool sendCoherence       = true;

        [Header("Smoothing")]
        [Range(0f, 1f)] [SerializeField] float velocityEma = 0.3f;
        [Range(0f, 10f)] [SerializeField] float velocityNormalizeMax = 5f;  // 5 m/s mapped to 1.0

        // Internal
        UdpClient _udp;
        IPEndPoint _endpoint;
        Vector3[] _prevPos;
        float[]   _smoothedVel;        // per-bone scalar
        float     _prevHipYaw;
        float     _emitInterval;
        float     _emitAccumulator;
        static readonly int IdChoreographyScoreSmoothed = Shader.PropertyToID("_ChoreographyScoreSmoothed");

        void OnEnable()
        {
            if (mocopiReceiver == null) mocopiReceiver = FindObjectOfType<LumeMocopiReceiver>();
            _udp = new UdpClient();
            _endpoint = new IPEndPoint(IPAddress.Parse(oscHost), oscPort);
            _prevPos = new Vector3[27];
            _smoothedVel = new float[27];
            _emitInterval = 1f / Mathf.Max(1, updateRateHz);
            _emitAccumulator = 0f;
        }

        void OnDisable()
        {
            try { _udp?.Close(); } catch { }
            _udp = null;
        }

        void LateUpdate()
        {
            if (!oscEnabled || _udp == null || mocopiReceiver == null) return;

            _emitAccumulator += Time.deltaTime;
            if (_emitAccumulator < _emitInterval) return;
            float dt = _emitAccumulator;
            _emitAccumulator = 0f;

            if (!mocopiReceiver.TryGetSmoothed(out var pos, out var rot)) return;

            // Update per-bone velocities (EMA scalar magnitude).
            for (int i = 0; i < 27 && i < pos.Length; i++)
            {
                Vector3 v = (pos[i] - _prevPos[i]) / Mathf.Max(1e-4f, dt);
                float mag = v.magnitude;
                _smoothedVel[i] = Mathf.Lerp(_smoothedVel[i], mag, 1f - velocityEma);
                _prevPos[i] = pos[i];
            }

            // Build OSC bundle.
            var bundle = OscBuilder.NewBundle();

            if (sendRightHandVel)
                bundle.AddFloat("/lume/right-hand-vel", Mathf.Clamp01(_smoothedVel[rightHandBone] / velocityNormalizeMax));
            if (sendLeftHandVel)
                bundle.AddFloat("/lume/left-hand-vel", Mathf.Clamp01(_smoothedVel[leftHandBone] / velocityNormalizeMax));
            if (sendHipRotation)
            {
                float yaw = rot[hipBone].eulerAngles.y;
                float yawRate = Mathf.DeltaAngle(_prevHipYaw, yaw) / Mathf.Max(1e-4f, dt);
                _prevHipYaw = yaw;
                bundle.AddFloat("/lume/hip-rotation", yawRate / 360f);
            }
            if (sendFootDifferential)
            {
                float diff = Mathf.Abs(_smoothedVel[leftFootBone] - _smoothedVel[rightFootBone]);
                bundle.AddFloat("/lume/foot-differential", Mathf.Clamp01(diff / velocityNormalizeMax));
            }
            if (sendBodyExtension)
            {
                float maxHand = Mathf.Max(pos[rightHandBone].y, pos[leftHandBone].y);
                float ext = maxHand - pos[hipBone].y;
                bundle.AddFloat("/lume/body-extension", Mathf.Clamp(ext / 1.5f, -1f, 2f));  // ~hip→over-head range
            }
            if (sendSpread)
            {
                float s = Vector3.Distance(pos[rightHandBone], pos[leftHandBone]);
                bundle.AddFloat("/lume/spread", Mathf.Clamp01(s / 2.0f));
            }
            if (sendCoherence)
            {
                bundle.AddFloat("/lume/coherence", Shader.GetGlobalFloat(IdChoreographyScoreSmoothed));
            }

            byte[] bytes = bundle.Build();
            try { _udp.Send(bytes, bytes.Length, _endpoint); }
            catch (Exception ex) { Debug.LogWarning($"[LumeMotionToAudio] OSC send failed: {ex.Message}"); }
        }
    }
}

OSC builder helper (private, in same file under namespace `Lume`)

csharp
internal sealed class OscBuilder
{
    // Minimal hand-rolled OSC 1.0 bundle packer. NO new package dependency.
    // Bundle wire format:
    //   "#bundle\0"              (8 bytes)
    //   <int64 timetag big-endian> (8 bytes, immediate = 0x0000000000000001)
    //   per element:
    //       <int32 size big-endian>
    //       <element bytes — message>
    //
    // Message wire format:
    //   <address pattern, null-terminated, padded to 4-byte boundary>
    //   <type tag string, starts with ',', null-terminated, padded to 4>
    //   <arguments>
    //
    // Float32 args are big-endian IEEE 754.

    readonly System.Collections.Generic.List<byte[]> _messages = new();

    public static OscBuilder NewBundle() => new OscBuilder();

    public void AddFloat(string address, float value)
    {
        var ms = new System.IO.MemoryStream();
        WritePadded(ms, Encoding.ASCII.GetBytes(address));
        WritePadded(ms, Encoding.ASCII.GetBytes(",f"));
        var fb = BitConverter.GetBytes(value);
        if (BitConverter.IsLittleEndian) Array.Reverse(fb);
        ms.Write(fb, 0, 4);
        _messages.Add(ms.ToArray());
    }

    public byte[] Build()
    {
        var ms = new System.IO.MemoryStream();
        ms.Write(Encoding.ASCII.GetBytes("#bundle\0"), 0, 8);
        var timetag = new byte[] { 0, 0, 0, 0, 0, 0, 0, 1 };
        ms.Write(timetag, 0, 8);
        foreach (var msg in _messages)
        {
            var sz = BitConverter.GetBytes(msg.Length);
            if (BitConverter.IsLittleEndian) Array.Reverse(sz);
            ms.Write(sz, 0, 4);
            ms.Write(msg, 0, msg.Length);
        }
        return ms.ToArray();
    }

    static void WritePadded(System.IO.MemoryStream ms, byte[] data)
    {
        ms.Write(data, 0, data.Length);
        ms.WriteByte(0);                   // null terminator
        int total = data.Length + 1;
        int pad = (4 - (total % 4)) % 4;
        for (int i = 0; i < pad; i++) ms.WriteByte(0);
    }
}

Calibration tunable registration

Open `Assets/Scripts/LumeCalibrationPanel.cs`. Find the `BuildTunables()` block where Wave 5 components register (`LumeTransientForcePusher`, `LumeOpticalFlow`, etc.). Add a Wave 8 section after whatever Claude added in Step 3:

csharp
// Wave 8 — Step 4: LumeMotionToAudio
RegisterReflectedFloat<LumeMotionToAudio>(nameof(LumeMotionToAudio.updateRateHz), 30f, 120f);
RegisterReflectedBool<LumeMotionToAudio>(nameof(LumeMotionToAudio.oscEnabled));
// ... 7 per-mapping bool toggles

Mirror the existing reflection helpers — do NOT invent new infrastructure.

### .meta GUID
Pick fresh GUID for `.cs.meta`. Use pattern from existing Wave 8 files:

yaml
fileFormatVersion: 2
guid: aaaa9999bbbb2222cccc3333dddd4444   # NEW — must be unique
MonoImporter:
  externalObjects: {}
  serializedVersion: 2
  defaultReferences: []
  executionOrder: 215
  icon: {instanceID: 0}
  userData:
  assetBundleName:
  assetBundleVariant:

### Tests
Add `software/demo/tests/test_motion_to_audio_osc.py`:
- Spawn UDP listener on [ip]:9050
- Hand-build expected OSC bundle bytes for known float values
- Decode bundle from socket, assert addresses + values
- Verify big-endian floats, 4-byte padding, "#bundle\0" prefix

OR (if Python-side test is awkward without Unity), add a Unity edit-mode test — but the project has no `.asmdef` runner wired, so Python loopback is the cheaper path.

### Don't
- Don't add a heavy OSC package (e.g., `extOSC`, `UnityOSC`). Hand-rolled is ~30 LOC.
- Don't reuse `:9701` or `:9702` for OSC out — those are inbound LUMF/LUMM ports.
- Don't make `oscHost` an IPEndPoint resolution at every send — resolve once in OnEnable.

### Done when
- File compiles in Unity (check Mac4 if needed; Codex on Mac1 can validate via `dotnet build` against a stub or skip and document deferral).
- New Python test passes: `pytest software/demo/tests/test_motion_to_audio_osc.py -v`
- Total fast test count: 73 → ~80.
- Commit on Mac1 main: `feat(lume): Wave 8 step 4 — motion-to-OSC bridge`

---

5. Step 5 — `LumeChoreographyPanel.cs` (Codex)

### Goal
F2 IMGUI debug surface tying the whole choreography stack together. Mirror `LumeCalibrationPanel.cs` structure verbatim — copy the file, swap content, do NOT generalize into a base class.

### File
`Assets/Scripts/LumeChoreographyPanel.cs` + `.cs.meta`

Spec

  • Toggle key: `KeyCode.F2` (F1 reserved for Duncan picker; F12 for calibration)
  • Window ID: `8122` (8120 = calibration, 8121 = picker)
  • JSON persistence: `Application.persistentDataPath/lume-choreography.json` — separate from calibration JSON
  • Render order: `OnGUI()` only when toggled visible

Sections (top-to-bottom)

┌─────────────────────────────────────────┐
│ [F2] Choreography Diagnostics    ✕ Close │
├─────────────────────────────────────────┤
│ Score: 0.74  ━━━━━━━━━━━━━━━━━━━━━━━━━━ │  ← live + sparkline
│   sparkline of _ChoreographyHistory[64]  │
├─────────────────────────────────────────┤
│ Onset/Peak Timeline (last 8 of each)     │
│   Audio  : ▮ ▮  ▮   ▮ ▮▮  ▮       ▮    │
│   Body   :  ▮  ▮ ▮  ▮ ▮  ▮  ▮     ▮    │  ← time axis right→left
├─────────────────────────────────────────┤
│ Snapshot Ring (8 slots)                  │
│   [██▓░░░░░] 0.34  age=1.20s            │
│   [██████▓░] 0.78  age=0.45s            │
│   ...                                    │
├─────────────────────────────────────────┤
│ OSC Out (60 Hz → [ip]:9050)        │
│   /right-hand-vel   ▮▮▮▮▮▮▯▯▯▯  0.62    │
│   /left-hand-vel    ▮▮▯▯▯▯▯▯▯▯  0.21    │
│   /coherence        ▮▮▮▮▮▮▮▮▯▯  0.74    │
│   ... (7 channels)                       │
├─────────────────────────────────────────┤
│ Toggles                                  │
│   [✓] scoringEnabled                     │
│   [✓] snapshotEnabled                    │
│   [✓] oscEnabled                         │
└─────────────────────────────────────────┘

Reading from existing components

- Sparkline: `Shader.GetGlobalFloat("_ChoreographyScoreSmoothed")` — instant value. For history, you need access to the StructuredBuffer<float> `_ChoreographyHistory`. Read it via a new public accessor on `LumeChoreographyScorer`:

csharp
  public float[] GetHistorySnapshot()  // copies from GPU buffer once per OnGUI tick

Coordinate with Claude: Step 3 should expose this. If it doesn't, add a `LumeChoreographyScorer.Latest` static array Claude can populate, OR read the smoothed score over time and build the sparkline locally in the panel (simpler, no GPU readback).
- Snapshot ring: `LumePoseSnapshotter` exposes its ring buffer via an accessor — add one if missing. Each slot needs `(alpha, age)`.
- OSC gauges: `LumeMotionToAudio` exposes its last-sent values per channel via a `public IReadOnlyDictionary<string, float> LastValues` property. Add this in Step 4.

Implementation notes

  • Use `GUI.Window` with the matching ID. Drag-handle the title bar.
  • Sparkline: `GUI.DrawTexture` on a 1x64 texture with grayscale per-frame value, refreshed once per OnGUI tick. Or draw with `GL.LINES` inside `OnPostRender` (overkill — stick with texture).
  • Toggle keys: `Input.GetKeyDown(KeyCode.F2)` flips `_visible`.
  • JSON persistence: serialize a `[Serializable]` `PanelState` struct on `OnDisable`; load on `OnEnable`. Don't crash if the JSON is missing or malformed.

### Tunable registration
Hide-behind-flag toggles (`scoringEnabled`, `snapshotEnabled`, `oscEnabled`) flip the matching component's `enabled` field directly:

csharp
if (GUILayout.Toggle(scorer.enabled, "scoringEnabled") != scorer.enabled)
    scorer.enabled = !scorer.enabled;

### .meta GUID
Pick fresh: e.g., `aaaaAAAAbbbb3333cccc4444dddd5555`.

### Don't
- Don't replace `LumeCalibrationPanel.cs`. The two panels coexist — F2 vs F12.
- Don't use `EditorGUILayout` (that's editor-only). Runtime IMGUI uses `GUILayout`.
- Don't pull in any UI Toolkit / UGUI dependency. IMGUI only, mirrors calibration.

### Done when
- F2 in Play mode opens the panel; F2 again closes it.
- Sparkline animates within 1s of motion changes.
- JSON saved/restored across Play sessions.
- Commit on Mac1 main: `feat(lume): Wave 8 step 5 — F2 choreography panel`

---

6. Step 6 — Architecture wrap-up (Codex)

6a — `software/demo/unity/lume_pcloud/ARCHITECTURE.md`

Update the doc to match reality.

Changes:

1. Component count in the header summary: 17 → 21 (LumePoseSnapshotter, LumeChoreographyScorer, LumeMotionToAudio, LumeChoreographyPanel are the +4).

2. Execution-order ladder table — add four rows in order:

markdown
   | 215 | LumeMotionToAudio       | bones → OSC out                   | Wave 8 |
   | 220 | LumeTransientForcePusher| (existing)                        | Wave 5 |
   | 225 | LumePoseSnapshotter     | beat-locked pose ghosts           | Wave 8 |
   | 226 | LumeChoreographyScorer  | body↔audio coherence score       | Wave 8 |

`LumeChoreographyPanel` is `OnGUI()`-only; doesn't need an exec-order row but mention it under the panels section.

3. Globals table — add rows:
| Name | Type | Producer | Consumer |
|------|------|----------|----------|
| `_ChoreographyScore` | float | LumeChoreographyScorer | LumeChoreographyPanel |
| `_ChoreographyScoreSmoothed` | float | LumeChoreographyScorer | shader brightness, LumeFluidSim turbulence, LumeMotionToAudio /lume/coherence |
| `_ChoreographyHistory` | StructuredBuffer<float> | LumeChoreographyScorer | LumeChoreographyPanel sparkline |
| `_SnapshotPositions` | StructuredBuffer<float4> | LumePoseSnapshotter | LumePointInstanced.shader (SAMPLE_SNAPSHOTS) |
| `_SnapshotCount` | float | LumePoseSnapshotter | shader |
| `_SnapshotMaxBones` | float | LumePoseSnapshotter | shader |
| `_SnapshotActive` | float | LumePoseSnapshotter | shader |

4. Wave ladder section — add Wave 8 entry with the 4 commits:

   Wave 8 — Computational Choreography
     8fb6fe75  step 1  mocopi_synth.py + 11 tests
     8139a931  step 2  LumePoseSnapshotter + SAMPLE_SNAPSHOTS keyword
     <SHA>     step 3  LumeChoreographyScorer + APPLY_CHOREOGRAPHY_SCORE
     <SHA>     step 4  LumeMotionToAudio (OSC :9050)
     <SHA>     step 5  LumeChoreographyPanel (F2)
     <SHA>     step 6  Architecture + Auto-Wire

5. F-key panel table — add F2:

   F1   Duncan preset picker  window 8121
   F2   Choreography panel    window 8122   (Wave 8)
   F12  Calibration panel     window 8120

6. Wire formats table — already lists LUME/LUMD/LUMF/LUMM/LUMC. Confirm LUMM is described as Wave 8-active (synthetic + iPad + future Sony pro).

7. Don't-touch list — append:
- Wave 8 wire format magic 0x4C554D4D (LUMM) — pinned by mocopi_synth golden test
- LumeTransientForcePusher.OnTransientFired event — load-bearing for snapshotter

6b — `Assets/Editor/LumeWaveAutoWire.cs`

Add Wave 8 component attach + serialized-slot fill. Pattern: read existing Wave 1+2+3+5 entries, add four parallel cases.

csharp
// Wave 8 — Computational Choreography
EnsureComponent<LumeMotionToAudio>(target, c => {
    SetSerializedRef(c, "mocopiReceiver", FindOrAttach<LumeMocopiReceiver>(target));
});
EnsureComponent<LumePoseSnapshotter>(target, c => {
    SetSerializedRef(c, "transientForcePusher", FindOrAttach<LumeTransientForcePusher>(target));
    SetSerializedRef(c, "mocopiReceiver", FindOrAttach<LumeMocopiReceiver>(target));
});
EnsureComponent<LumeChoreographyScorer>(target, c => {
    SetSerializedRef(c, "audioFftReceiver", FindOrAttach<LumeAudioFftReceiver>(target));
    SetSerializedRef(c, "mocopiReceiver", FindOrAttach<LumeMocopiReceiver>(target));
});
EnsureComponent<LumeChoreographyPanel>(target);

Idempotent: re-running the menu must not duplicate components. Use `target.GetComponent<T>() ?? target.AddComponent<T>()` pattern.

Add a verify entry:

csharp
LogTickbox("Wave 8: choreography stack",
    target.GetComponent<LumeChoreographyScorer>() != null &&
    target.GetComponent<LumePoseSnapshotter>() != null &&
    target.GetComponent<LumeMotionToAudio>() != null);

### .meta files
ARCHITECTURE.md doesn't need a .meta change (Unity ignores .md outside Assets/). LumeWaveAutoWire.cs already has a .meta — leave it alone.

### Done when
- ARCHITECTURE.md component count, ladder, globals, Wave 8 section all match the four shipped commits.
- Auto-Wire menu attaches all 21 components without errors on a fresh GameObject.
- Verify menu prints `[x]` for the new Wave 8 tickbox.
- Commit on Mac1 main: `docs(lume): Wave 8 architecture + auto-wire`

---

7. Verification Checklist (Codex runs after each step)

bash
# 1. Test suite still green (after Steps 4 only — Steps 5/6 don't add Python tests)
cd Desktop/lume-commerce/software/demo
pytest tests/ -q
# Expect: 73 fast green at start. After step 4: ~80 fast green.

# 2. Git state clean
git -C Desktop/lume-commerce status
# Expect: only the new files for the current step.

# 3. Commit landed
git -C Desktop/lume-commerce log -1 --oneline
# Expect: matches "Wave 8 step N" format.

# 4. No accidental edits to don't-touch files
git -C Desktop/lume-commerce diff HEAD~1 HEAD --stat | grep -E "(audio_pub|mocopi_bridge|pointcloud_pub|LumeUdpReceiver|LumeAudioReactor)"
# Expect: empty output (none of these files changed).

If any of these fail, DO NOT commit. Investigate and either fix or write a blocker memory file.

---

8. Reading Order for Codex (paste this into your bootstrap)

1. This file: `software/demo/unity/lume_pcloud/CODEX-WAVE8-PLAN.md`
2. Plan source: `[home-path]`
3. Memory state: `[home-path]`
4. Architecture mental model: `software/demo/unity/lume_pcloud/ARCHITECTURE.md`
5. API contracts (read-only):
- `Assets/Scripts/LumeMocopiReceiver.cs` → `TryGetSmoothed`
- `Assets/Scripts/LumeAudioFftReceiver.cs` → `TryGetLatest`
- `Assets/Scripts/LumeTransientForcePusher.cs` → `OnTransientFired` event
- `Assets/Scripts/LumeCalibrationPanel.cs` → IMGUI + reflection-tunable + JSON pattern
6. Step 3 baseline (when Claude commits it): `Assets/Scripts/LumeChoreographyScorer.cs`

---

9. Out of Scope (don't pick up)

  • K11 deploy / NSSM / pyorbbecsdk install — separate K11 agent owns this.
  • Mac4 verification — separate Mac4 agent owns this after all four commits ship.
  • Sony mocopi-pro pairing — blocked on activation code in mail.
  • iPad skeleton publisher — already exists and works; Mohamed will use it as-is.
  • Wave 9 (marching cubes, Human Kaleidoscope, frozen velocity buffers, pinscreen mesh).
  • Pushing to remote — Mohamed pushes manually after Mac4 verification.
  • Authoring `Assets/VFX/LumeFlowParticles.vfx` — GUI-only, Mohamed's job.

---

10. Stop / Recovery Conditions

Stop and write a blocker memory if:
- Step 3 commit isn't on main when you start (Codex must wait).
- A test that was previously green starts failing — bisect first, don't paper over.
- A `.meta` GUID collision (Unity will complain on reimport).
- `LumeCalibrationPanel.RegisterReflectedFloat<T>` doesn't exist (Claude may have used a different helper name in Step 3 — read the file before assuming).
- Any Wave 1-7 don't-touch file shows up in your diff.

Blocker memory format:

markdown
---
name: LUME Wave 8 Codex Blocker <DATE>
description: Codex stopped on Wave 8 step N — <one-line reason>
type: project
---
# What I tried
...
# What broke
... (file:line, exact error)
# What I need
... (decision Mohamed must make, or unblock from Claude)
# State of the working tree
... (git status output)

---

End of plan. Codex: read top to bottom, then start at Section 4 only after Section 1's "Step 3 commit" appears in `git log`. If you must wait, run `pytest software/demo/tests/ -q` once to confirm baseline, then idle.

Promotion Decision

Attach run IDs, datasets, metrics, and reproduction commands.

Source Anchor

lume-commerce/viz/lume-pcloud/CODEX-WAVE8-PLAN.md

Detected Structure

Method · Evaluation · References · Code Anchors · Architecture