# CONTROL SURFACE BUILD SPEC — Exposing the Engine as a Deductive Search Instrument **Written 2026-07-15. Spec only — no code until approved.** This spec covers three parts: 1. **Expose the knobs** — catalog every hardcoded physics constant in `khra_gixx_1024_v5_observer.cu` and design a single generic `set_param` command to make each live-settable. 2. **The deductive search method** — how the full knob space is searched, per `.clinerules` §6. 3. **The observer's role** — how the observer LLM runs the search, chases anomalies, and interacts with the human. --- ## PART 1 — EXPOSE THE KNOBS ### 1.1 Already-Exposed Knobs (command surface as of 2026-07-14) | Knob | Default | Clamp Range | Command | |------|---------|-------------|---------| | `omega` | 1.97 | [0.5, 1.99] | `set_omega` | | `khra_amp` | 0.03 | [0.0, 0.2] | `set_khra_amp` | | `gixx_amp` | 0.008 | [0.0, 0.1] | `set_gixx_amp` | ### 1.2 Currently Hardcoded Physics Constants #### A. Wave Function (`khra_gixx_wave_1024`, line 74–81) The wave function in full: ```c float khra = sinf(2.0f * M_PI * x / 128.0f + cycle * 0.025f) * cosf(2.0f * M_PI * y / 128.0f + cycle * 0.015f) * d_khra_amp; float gixx = sinf(2.0f * M_PI * x / 8.0f + cycle * 0.4f) * cosf(2.0f * M_PI * y / 8.0f + cycle * 0.35f) * d_gixx_amp; float asymmetry_factor = 1.0f + sinf(cycle * 0.05f) * 0.5f; return khra + gixx * asymmetry_factor; ``` **Constants to expose:** | # | Constant | Line(s) | Location | Default | Proposed Clamp | Rationale | |---|----------|---------|----------|---------|----------------|-----------| | W1 | `128.0f` (khra wavelength λ_khra) | 75, 76 | `x / 128.0f`, `y / 128.0f` | 128.0 | [16.0, 512.0] | Spatial scale of the slow carrier. Doubling it halves khra's spatial frequency. | | W2 | `0.025f` (khra temporal freq ω_khra) | 75 | `cycle * 0.025f` | 0.025 | [0.001, 0.5] | Temporal frequency of khra carrier. Controls khra period (~251 cyc at default). | | W3 | `0.015f` (khra y-phase rate) | 76 | `cycle * 0.015f` | 0.015 | [0.0, 0.5] | Phase offset rate for the y-component cosine. Governs khra's xy phase relation. | | W4 | `8.0f` (gixx wavelength λ_gixx) | 77, 78 | `x / 8.0f`, `y / 8.0f` | 8.0 | [2.0, 128.0] | Spatial scale of the fast carrier. | | W5 | `0.4f` (gixx temporal freq ω_gixx) | 77 | `cycle * 0.4f` | 0.4 | [0.01, 2.0] | Temporal frequency of gixx carrier. Controls gixx period (~15.7 cyc at default). | | W6 | `0.35f` (gixx y-phase rate) | 78 | `cycle * 0.35f` | 0.35 | [0.0, 2.0] | Phase offset rate for gixx y-component. | | W7 | `0.05f` (breathing frequency) | 79 | `cycle * 0.05f` | 0.05 | [0.0, 1.0] | Frequency of the asymmetry/breathing modulation. 0.0 = no breathing. | | W8 | `0.5f` (breathing amplitude) | 79 | `sinf(…) * 0.5f` | 0.5 | [0.0, 0.95] | Amplitude of breathing modulation. At 1.0, asymmetry_factor ranges [0.0, 2.0] so gixx can vanish entirely at the trough. | #### B. Collision Kernel (`collide_kernel_khragixx`, lines 100–169) | # | Constant | Line(s) | Default | Proposed Clamp | Rationale | |---|----------|---------|---------|----------------|-----------| | C1 | `0.1f` (density floor) | 124 | 0.1 | [0.001, 1.0] | Minimum rho after clamp. Lower bound on density. Source of mass-leak asymmetry (floor ≠ ceil). | | C2 | `10.0f` (density ceiling) | 125 | 10.0 | [1.0, 100.0] | Maximum rho clamp. The asymmetry between C1 and C2 pumps mass in over long runs. | | C3 | `0.25f` (velocity clamp) | 129, 130, 131, 139, 140, 141 | 0.25 | [0.05, 1.0] | Maximum velocity magnitude. Appears in two places: pre-forcing clamp (lines 129–133) and post-forcing clamp (lines 138–143). Both use the same constant. | | C4 | `0.5f` (ky cross-coupling) | 135 | `ky = kx * 0.5f` | 0.5 | [-2.0, 2.0] | Ratio of y-forcing to x-forcing from the wave. At 0.5, the y-axis gets half the push. At 0.0, forcing is x-only. At 1.0, symmetric. Negative = counter-phase. | #### C. LBGK Equilibrium Coefficients (collide kernel line 147, velocity_stress line 214) | # | Constant | Default | Proposed Clamp | Rationale | |---|----------|---------|----------------|-----------| | E1 | `3.0f` (cs⁻², the eu coefficient) | 3.0 | [2.1, 3.9] | Governs how strongly velocity couples to the equilibrium distribution. Standard D2Q9 value = 3.0. **NUMERICAL STABILITY: clamp narrowed to ±30% of default.** | | E2 | `4.5f` (cs⁻⁴/2, the eu² coefficient) | 4.5 | [3.15, 5.85] | Second-order velocity coupling. Standard = 4.5. **NUMERICAL STABILITY: clamp narrowed to ±30% of default.** | | E3 | `1.5f` (cs⁻²/2, the u² coefficient) | 1.5 | [1.05, 1.95] | Velocity-squared damping term. Standard = 1.5. **NUMERICAL STABILITY: clamp narrowed to ±30% of default.** | **CRITICAL — E1–E3 numerical stability note:** These equilibrium coefficients define the local Maxwellian. Varying them far from the standard D2Q9 values (cs²=1/3 → 3.0, 4.5, 1.5) can destabilize the LBGK collision operator, producing NaN values in the distribution function `f` and contaminating the entire field. The clamp ranges above are narrowed to ±30% of default to stay within observed stable bounds. **The sweep engine MUST detect divergence/NaN at any grid point and abort+flag that point before the averaging window completes.** The daemon's existing NaN/Inf reset counter (lines 149–152 in the collide kernel) will catch NaN events — the sweep engine must monitor `reset_count` in telemetry (port 5556) and abort any point where `reset_count > 0` during the settle or measurement window. Such points are flagged in `sweep_results.jsonl` as `"status": "diverged"` and the daemon is restored to baseline params before continuing to the next point. These are **lower priority** than the wave function and collision clamp constants — the wave parameters directly control the two-carrier forcing regime, which is the physics we're searching. The equilibrium coefficients control the fluid response to that forcing. Expose them but treat them as a secondary sweep dimension. Do NOT sweep E1–E3 in Round 1; wait until the active knob set has been narrowed to a manageable size. ### 1.3 Design: Single Generic `set_param` Command **Channel:** ZMQ SUB on port 5557 (existing). **Message format (JSON):** ```json {"cmd": "set_param", "param": "", "value": } ``` **Param names and their clamp ranges:** | param name | Default | Min | Max | Maps to | |------------|---------|-----|-----|---------| | `omega` | 1.97 | 0.5 | 1.99 | `h_omega` (already exposed, just add alias) | | `khra_amp` | 0.03 | 0.0 | 0.2 | `d_khra_amp` (already exposed) | | `gixx_amp` | 0.008 | 0.0 | 0.1 | `d_gixx_amp` (already exposed) | | `lam_khra` | 128.0 | 16.0 | 512.0 | W1 — khra wavelength | | `w_khra` | 0.025 | 0.001 | 0.5 | W2 — khra temporal frequency | | `khra_y_phase` | 0.015 | 0.0 | 0.5 | W3 — khra y-component phase rate | | `lam_gixx` | 8.0 | 2.0 | 128.0 | W4 — gixx wavelength | | `w_gixx` | 0.4 | 0.01 | 2.0 | W5 — gixx temporal frequency | | `gixx_y_phase` | 0.35 | 0.0 | 2.0 | W6 — gixx y-component phase rate | | `breath_freq` | 0.05 | 0.0 | 1.0 | W7 — breathing modulation frequency | | `breath_amp` | 0.5 | 0.0 | 0.95 | W8 — breathing modulation amplitude | | `rho_floor` | 0.1 | 0.001 | 1.0 | C1 — density floor clamp | | `rho_ceil` | 10.0 | 1.0 | 100.0 | C2 — density ceiling clamp | | `vel_clamp` | 0.25 | 0.05 | 1.0 | C3 — velocity magnitude clamp | | `ky_ratio` | 0.5 | -2.0 | 2.0 | C4 — y/x forcing ratio | | `eq_cs2_inv` | 3.0 | 0.1 | 10.0 | E1 — equilibrium cs⁻² coefficient | | `eq_cs4_half` | 4.5 | 0.1 | 20.0 | E2 — equilibrium cs⁻⁴/2 coefficient | | `eq_usq_half` | 1.5 | 0.1 | 20.0 | E3 — equilibrium u²/2 coefficient | **Total: 18 params** (3 already exposed, 15 newly exposed). **Implementation in `handle_command`:** Add a single `set_param` branch that looks up the param name in a static table, validates the value against its clamp range, sets the corresponding host variable, and pushes to device where needed (via `cudaMemcpyToSymbol` for device-side wave-function params; host-side for clamp and equilibrium constants that are currently fed through kernel arguments or are baked as literals). **Response (ack on 5559):** ```json {"ack": "set_param", "cycle": , "status": "ok", "param": "", "value": } ``` or `"status": "rejected"` with reason if out of range or unknown param. ### 1.4 Constants That Must Move from Compile-Time Literal to Runtime Variable Most of these constants (W1–W8, C1–C4, E1–E3) are currently C numeric literals baked into the kernel at compile time. To make them live-settable: - **Wave function constants (W1–W8):** Currently `__device__` scalars or kernel literals. Must become `__device__` variables (like `d_khra_amp` already is) so they can be updated via `cudaMemcpyToSymbol` without recompilation. The wave function kernel already reads from device memory for amplitudes — extend the same pattern to the other eight constants. - **Collision clamp constants (C1–C4):** Currently inline literals in `collide_kernel_khragixx`. Must become either `__device__` variables or kernel parameters. Kernel parameters are simpler (no symbol-copy overhead) but require the host to pass them every call. Recommendation: `__device__` variables — consistent with the wave-function pattern, single point of update, no kernel signature change. - **Equilibrium coefficients (E1–E3):** Currently inline literals in two kernels (collide line 147, velocity_stress line 214). Must become `__device__` variables shared by both kernels — updated once, both kernels see the change. ### 1.5 Alpha Coupling Ratio — Automatic Computation The coupling ratio is: ``` alpha = (A_khra * w_gixx * lam_gixx) / (A_gixx * w_khra * lam_khra) ``` With the full knob set exposed: - `A_khra` = `khra_amp` - `A_gixx` = `gixx_amp` - `w_khra` = `w_khra` (temporal frequency param, khra cycle multiplier) - `w_gixx` = `w_gixx` (temporal frequency param, gixx cycle multiplier) - `lam_khra` = `lam_khra` - `lam_gixx` = `lam_gixx` The daemon should compute and include `alpha` in every telemetry frame (port 5556) so the sweep engine and observer always have it. Add it to the JSON payload alongside coherence, asymmetry, etc. **Default alpha at canonical params:** ``` alpha = (0.03 * 0.4 * 8.0) / (0.008 * 0.025 * 128.0) = (0.096) / (0.0256) = 3.75 ``` **IMPORTANT — Alpha comparability note:** The α computed here uses the daemon's phase-increment parameters (`w_khra`=0.025, `w_gixx`=0.4) as defined in the `khra_gixx_wave_1024` device function. These are NOT physical angular frequencies (which would be 2π× these values). This α is **internally consistent for this daemon** — all α values logged by this instrument are comparable to each other. They are **NOT comparable** to any historical α values that were computed using physical angular frequencies. Treat this daemon's α as a self-consistent relative scale, not an absolute physical quantity. ### 1.6 Future — NOT Part of This Build These are genuinely new degrees of freedom, not exposing existing constants. Listed for later consideration, do NOT implement now: - **Per-node omega** — spatially varying relaxation rate. Would require a new kernel or a texture lookup. Major change, not "exposing existing constants." - **Multi-frequency forcing** — adding a third or Nth carrier wave. New wave function architecture. - **Non-periodic boundary conditions** — walls, inflow/outflow. Changes the streaming kernel. - **Anisotropic wavelengths** — different λ_x and λ_y for khra and gixx. Currently they share one λ. Would add 2 params. - **Non-sinusoidal wave shapes** — sawtooth, square, arbitrary waveform. Changes the wave function fundamentally. --- ## PART 2 — THE DEDUCTIVE SEARCH METHOD ### 2.1 The Dependent Variable **Perturbation hold-time** — how long a perturbation (injected into the stress/non-equilibrium channel, NOT density — fixing Mistake 1) persists in the field before the system relaxes back to its unperturbed attractor. Longer hold-time = candidate "memory regime." Operational definition: inject a standardized stress perturbation, then measure the number of cycles until the spatially-resolved field (5561 coarse stream) becomes indistinguishable from an unperturbed control run, by a pre-committed statistical threshold. ### 2.2 The Full Knob Space 18 knobs. Even with coarse discretization (e.g. 5 values each), full grid = 5^18 ≈ 3.8 × 10^12 points. Brute force is impossible. **Deductive elimination** is the only viable strategy. ### 2.3 The Method: Hypothesis → Vary → Observe → Eliminate → Narrow Per `.clinerules` §6. Each round: 1. **State the current hypothesis** — which knob(s) are predicted to move hold-time. 2. **Design the sweep** — vary those knobs across their ranges while holding all others at baseline (canonical defaults). Each knob gets a sparse sweep (e.g. 3–5 values across its range) to detect whether it moves the dependent variable at all. 3. **Run the sweep** — automated, resumable, detached. Every measurement uses: - ≥10-forcing-period averaging (≥2510 cycles at canonical khra ω=0.025; adjust dynamically if `w_khra` is varied, since khra period = 2π/w_khra). - Pre-committed threshold set in code before looking at results. - Surrogate/permutation null (temporal shuffle of perturbation-to-response mapping). - Bootstrap distribution of hold-time estimates (never a single sample). 4. **Eliminate** — knobs that produce NO statistically significant variation in hold-time (flat response within bootstrap CI) are FIXED at their baseline value for all future rounds. They are eliminated from the search space. 5. **Combine** — knobs that DO move hold-time are combined in the next round (small factorial design over the active subset). 6. **Narrow** — repeat until only a small active set remains. Then refine: finer sweeps, interaction terms, regime mapping. ### 2.4 Round 1: The Two Known Hypotheses Per task instructions, the first two hypotheses to test: **Hypothesis 1: Relaxation rate (omega) controls hold-time.** - Vary `omega` across [0.5, 1.0, 1.5, 1.97, 1.99] with all other knobs at baseline. - Prediction: hold-time ∝ 1/(1−ω) near the inviscid limit, or some monotonic function of ω. - Null: hold-time is flat across omega (within bootstrap CI). - This is the obvious first guess — slower relaxation = longer persistence. **Hypothesis 2: Perturbation carrier/rhythm phase relative to the forcing carriers controls hold-time.** - The perturbation's timing relative to the khra and gixx wave phase determines whether the perturbation constructively or destructively interferes with the forcing. - Vary `w_khra`, `w_gixx`, `breath_freq` (the temporal knobs) in a small combinatorial sweep. - Prediction: hold-time peaks at specific phase alignments (resonances). - Null: hold-time is insensitive to temporal phase. **Round 1 should also sweep the amplitudes** (`khra_amp`, `gixx_amp`) to establish the alpha-dependence baseline, since amplitude ratio α is the governing quantity (§3 of `.clinerules`). Sweep `khra_amp` × `gixx_amp` at a few omega values to get the first α-vs-hold-time map. ### 2.5 Round Structure Template ``` ROUND N Active knobs: [list] Fixed knobs (eliminated): [list with reason] Hypothesis: [stated before running] Sweep design: [which knobs at which values, how many points] Settle protocol: >=10 periods of the slowest active carrier after each knob change Measurement protocol: >=10 khra periods of 5561 coarse stream, averaged Rigor gates: [threshold, null type, bootstrap samples] Output: per-point JSONL + summary of which knobs moved hold-time and which didn't Decision: [which knobs eliminated, which combined for next round] ``` ### 2.6 Safety and Operational Rules - Never `reset_equilibrium` casually — a fresh daemon start is the only clean reset. - Restore baseline params (omega 1.97, khra 0.03, gixx 0.008, all new params at defaults) at end of each sweep session. - Clamp all params to valid ranges before sending. - The mass leak (§2.5 of Source of Truth) contaminates long windows — subtract the leak slope from hold-time measurements, or keep perturbation windows short enough that leak drift is negligible relative to the perturbation signal. - Launch daemon detached. Only one daemon on ports 5556–5561. --- ## PART 3 — THE OBSERVER'S ROLE ### 3.1 Architecture The observer is the LLM driving the sweep engine. It sits behind the existing `navigator/lattice_observer.py` (extended) and the sweep engine (`analysis/sweep_engine.py`, per `OBSERVER_SWEEP_BUILD_SPEC.md` Stage 1–2). It receives: - Telemetry stream (port 5556) — 9 scalars + alpha + all 18 knob values. - Coarse field stream (port 5561) — 32×32×6 spatially-resolved field at ~100 Hz. - Per-point sweep metrics from the engine. It sends: - `set_param` commands (port 5557) to turn knobs. - Sweep instructions to the engine (which points to run next). - Refinement requests (finer grid around an anomaly). ### 3.2 Observer Instructions (the Prompt) The observer must be explicitly instructed. This is its core prompt, to be written to `navigator/sweep_observer_prompt.json` or embedded in the observer code: ``` You are mapping an alien system across its parameter space. You do NOT know what it is. Your job is to find where its behaviour CHANGES CHARACTER — transitions, new dynamical regimes, parameter regions where perturbations persist longer than expected. Your method is DEDUCTIVE ELIMINATION, not brute force. RULES: 1. Your dependent variable is PERTURBATION HOLD-TIME. Everything else is a predictor. 2. Every measurement uses >=10 khra-period averaging (>=~2510 cycles at default w_khra; scale this by 2π/w_khra if w_khra is varied). 3. Every claim must survive: pre-committed threshold, surrogate null, bootstrap distribution. A clean null is a real result. 4. The governing quantity is alpha = (A_khra * w_gixx * lam_gixx) / (A_gixx * w_khra * lam_khra). Always log alpha at every point. 5. The lattice may host MULTIPLE attractor basins. Do not assume "one attractor" from averaged data. Probe for multi-basin behaviour. 6. Time is a variable: how long things persist is the point. Do not reduce dynamics to static snapshots. BANNED: - Do NOT invoke Golden Weave. - Do NOT query another LLM for a second opinion. - Do NOT reach for neuroscience analogies as conclusions. - Do NOT re-run injection/readback fidelity tests. - Do NOT write to the density channel — perturbations go to the STRESS channel. METHOD: Each round: state hypothesis → vary active knobs → measure hold-time → eliminate flat knobs → combine active knobs → narrow. Report what each round eliminated and what remains active. ``` ### 3.3 Anomaly Chasing The observer watches per-point metrics as they compute. An anomaly is: - A hold-time that jumps by >2σ above the running bootstrap distribution. - A new orbit character (coherence behaviour changes — fixed point → limit cycle → chaotic). - A multi-basin signal (different settling points from different initial phases). - A predictability spike/drop in the coarse field. When an anomaly is flagged, the observer: 1. Requests the sweep engine to REFINE around that point — a finer grid in the active-knob space within ±one step of the anomalous point. 2. Requests a longer capture window and a multi-basin probe (vary the perturbation phase and check for distinct settling outcomes). 3. Reports the refined finding: anomaly confirmed (with rigor gates) or resolved as noise. ### 3.4 Interactive Human-in-Loop Mode The observer exposes an interactive mode (CLI or lightweight web UI — agent's choice at build time, justified). In this mode: - **Live view:** current knob values, the α-vs-hold-time map so far (which points tested, what they showed), anomaly flags. - **Human can:** - Jump the daemon to any (param set) by issuing `set_param` commands. - Ask the observer to characterize a specific point (dwell, capture, report). - Request a custom sweep along a line or small grid. - Mark a point as "interesting" for the observer to refine later. - Override the observer's next-round proposal. - **Observer explains:** what it sees at the current point, what it thinks the active knobs are, where it would look next, and why. - **Two-way:** the human can accept, redirect, or override. The observer adapts its internal hypothesis state. ### 3.5 The Observer's Knowledge State The observer maintains a running model of the search: - **Active knobs:** which params have been shown to move hold-time (not yet eliminated). - **Eliminated knobs:** which params were swept and found flat (with statistical confidence). - **Untested knobs:** which params haven't been swept yet. - **Anomaly log:** points that showed unexpected behaviour, with refinement status. - **Alpha map:** the hold-time surface over α for the current fixed-knob configuration. This state persists to disk so the observer can resume after a crash or restart — it reads the `sweep_results.jsonl` and reconstructs its model. --- ## DELIVERABLE CHECKLIST - [x] Catalog all hardcoded physics constants in `khra_gixx_1024_v5_observer.cu` (18 total: 3 exposed, 15 to expose) - [x] Design single `set_param` command with clamp ranges for all 15 new params - [x] Specify deductive elimination search method with Round 1 design - [x] Specify observer instructions, anomaly-chasing rules, interactive mode - [x] List future additions that are NOT part of this build **Write no code until this spec is approved.**