Files
resonance-engine/CONTROL_SURFACE_BUILD_SPEC.md
T
2026-07-16 11:57:36 +07:00

329 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 7481)
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 100169)
| # | 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 129133) and post-forcing clamp (lines 138143). 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 — E1E3 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 149152 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 E1E3 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": "<name>", "value": <float>}
```
**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": <n>, "status": "ok", "param": "<name>", "value": <float>}
```
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 (W1W8, C1C4, E1E3) are currently C numeric literals baked into the kernel at compile time. To make them live-settable:
- **Wave function constants (W1W8):** 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 (C1C4):** 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 (E1E3):** 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. 35 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 55565561.
---
## 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 12). 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.**