# Fieldtest — cycle-0007 (signal-quality loop) — 2026-06-04

**Status:** Draft — awaiting orchestrator triage
**Author:** fieldtester (dispatched by fieldtest skill)

## Scope

Cycle 0007 realizes the C10 reframe (the signal-quality half) as two new
`aura-std` nodes on the unchanged, domain-free engine:

- `Exposure { scale }` — the decision/sizing node: `clamp(signal / scale, -1, +1)`,
  one `f64` exposure per fired cycle, `None` until its input is present.
- `SimBroker { pip_size }` — the sim-optimal broker: a two-input node (exposure,
  price) that accumulates `prev_exposure · (price − prev_price) / pip_size` and
  emits cumulative pip equity. `pip_size` is held reference metadata, never
  streamed.

Composed with the existing `Sma` / `Sub` and the cycle-0006 recording-node
pattern (a node with `output: vec![]` pushing `(ctx.now(), row)` out of graph),
these wire an end-to-end signal-quality backtest whose pip-equity curve is
recorded via a sink. The fieldtest exercised this from a standalone downstream
consumer crate (`fieldtests/cycle-0007-signal-quality/`) that `path`-depends on
the engine crates and uses only the public surface (rustdoc + design ledger +
glossary + project-layout, never `crates/*/src`).

**Build exercised:** all four binaries built and run from the current working
tree via `cargo run --manifest-path fieldtests/cycle-0007-signal-quality/Cargo.toml
--bin <name>` (debug profile, HEAD source recompiled each invocation — confirmed
by the `Compiling aura-core/std/engine` lines).

## Examples

### fieldtests/cycle-0007-signal-quality/c0007_1_single_signal_quality.rs — single-signal quality backtest, end to end
- Wires the full primary research loop: `price → {SMA(2), SMA(4)} → Sub →
  Exposure(4.0) → SimBroker(1.0) → Recorder`, price also fanning into the broker's
  price slot. Records the cumulative pip-equity curve over a rising price.
- Fits the headline axis: composition into an end-to-end signal-quality harness +
  recording the equity curve (project-layout "A day in the life" step 3).
- Outcome: built, ran. The pip VALUES matched the hand model exactly in the warm
  region; the recorded curve had three *extra* leading `0.0` rows (t=1,2,3) my
  first hand model did not predict — see the firing-shape spec_gap. Final
  assertion adjusted to the observed (principled) shape; passes.

### fieldtests/cycle-0007-signal-quality/c0007_2_exposure_clamp.rs — Exposure shaping (clamp + sizing)
- Feeds raw scores spanning below / at / above the saturation band, both signs,
  straight into `Exposure(10.0) → Recorder`. Asserts each output ∈ [-1,+1] and the
  exact `clamp(score/scale, -1, +1)` mapping (incl. hard saturation at ±1 and sign
  preservation).
- Fits the "intent/exposure shaping via Exposure" axis.
- Outcome: built, ran, matched expected `[0.0, 0.5, 1.0, 1.0, -0.7, -1.0, 0.3]`
  on the first try. Clean.

### fieldtests/cycle-0007-signal-quality/c0007_3_pip_size_scale.rs — sim-optimal integration, short signal, pip_size divisor
- Runs the same falling-price stream through two harnesses (`pip_size` 1.0 vs 2.0).
  Asserts (1) a short signal on a falling market makes positive pips
  (`neg exposure · neg return > 0`); (2) the `pip_size=2.0` curve is exactly half
  the `pip_size=1.0` curve, timestamp-for-timestamp.
- Fits the "sim-optimal pip-equity integration via SimBroker" + "different pip_size"
  axes.
- Outcome: built, ran. Final pips +3.0 (short pays); pip_size=2.0 curve exactly
  halved. Clean.

### fieldtests/cycle-0007-signal-quality/c0007_4_combine_two_signals.rs — combine two signals into one exposure (north-star move)
- Two MA-cross spreads (`SMA2-SMA4` and `SMA3-SMA6`) summed by a **hand-authored
  `Add2` node** into one combined score, shaped to exposure, fed to the broker.
  Asserts a warm equity region exists, equity is positive, and steady-state +1
  (saturated) exposure earns +2 pips/step.
- Fits the "combine two signals into one exposure and backtest the combination
  (the stated north-star research move)" axis (design INDEX C10).
- Outcome: built, ran, final pips +4.0, steady-state slope +2/step. Passes — but
  the combine step required hand-writing a summing node aura-std does not ship
  (see friction).

## Findings

### [working] Exposure mapping is exactly as documented, bound is hard
- Example: c0007_2.
- What happened: `clamp(score/scale, -1, +1)` reproduced verbatim from the rustdoc
  one-line description — 0→0, 5/10→0.5, 25/10→saturates to 1.0 (not 2.5),
  −40/10→−1.0, sign preserved throughout. Every recorded value lay in [-1,+1].
- Why working: the new surface was reached for, used as the rustdoc described, and
  correct on the first run with no surprises; the hard saturation (a downstream
  author's main safety question — "can exposure exceed ±1?") is empirically firm.
- Recommended action: carry-on.

### [working] Sim-optimal pip integration: sign, causality, and pip_size divisor all correct
- Example: c0007_3 (and the warm region of c0007_1).
- What happened: a short exposure on a falling price integrated to **positive**
  pips (+3.0), confirming the `prev_exposure · Δprice` sign convention; doubling
  `pip_size` exactly halved every pip value (the divisor relationship); the t-1
  exposure earning t's return (causality, C2) showed up as the steady +1.0-pip/step
  slope under constant exposure and constant +2 price steps in c0007_1.
- Why working: the broker's documented integration model held bit-for-bit against
  three independent hand computations (rising-long, falling-short, pip_size scaling).
- Recommended action: carry-on.

### [working] End-to-end signal-quality harness composes and records cleanly
- Examples: c0007_1, c0007_4.
- What happened: the full `SMA-cross → Exposure → SimBroker → Recorder` chain
  bootstrapped and ran with the cycle-0006 recording pattern unchanged; one source
  fanning into both the indicator legs and the broker's price slot wired without
  fuss; the recorded pip-equity curve drained from the channel exactly as in 0006.
- Why working: the headline cycle deliverable — a runnable signal-quality backtest
  whose equity curve you can record — works from the public surface, including the
  north-star "combine two signals" composition.
- Recommended action: carry-on.

### [spec_gap] SimBroker's firing policy / warm-up emission shape is not on the public surface
- Examples: c0007_1 (surfaced), c0007_3, c0007_4 (same shape).
- What happened: I predicted the broker would emit its first equity row only once
  its exposure leg warmed (from t=4 in c0007_1). It actually emits on **every
  price-fresh cycle** from t=1, treating a still-cold exposure as 0.0 and recording
  leading `0.0` rows (t=1,2,3). Verbatim, c0007_1:
  `recorded = [(1,0.0),(2,0.0),(3,0.0),(4,0.0),(5,1.0),(6,2.0),(7,3.0)]` vs my first
  `predicted = [(4,0.0),(5,1.0),(6,2.0),(7,3.0)]`. The pip *values* matched once
  warm; only the emission *shape* differed.
- Why spec_gap: the `SimBroker` rustdoc states the integration formula but **not**
  (a) its per-input firing policy (does it fire on price-fresh, exposure-fresh, or
  a barrier?), nor (b) that it emits `0.0`-equity rows before the exposure leg
  warms. Both readings — "emit from first price tick, cold-exposure = flat" (what
  ships) and "emit only once exposure is defined" (what I guessed) — are plausible
  from the prose, and they change the recorded-curve length a downstream consumer
  reasons about. The feat commit body documents the cold-exposure→0.0 detail
  (`Window::get(0)` "or 0.0 when the exposure leg is cold"), but a commit body is
  not the public reference surface; the rustdoc / ledger C10 Realization is silent
  on it. I did not probe the engine internals to confirm *why* (that would end the
  test); the behaviour is the finding.
- Recommended action: tighten the design ledger / rustdoc — state `SimBroker`'s
  firing policy and the cold-exposure-emits-`0.0` warm-up contract on the rustdoc
  (and, if it is a contract worth pinning, in C10's Realization note), so a
  consumer can predict the recorded curve's length and leading values.

### [spec_gap] SimBroker's input slot order (exposure vs price) is only in the commit body / C10 prose, not the rustdoc
- Examples: c0007_1, c0007_3, c0007_4 (all wire `Target { node: broker, slot: 1 }`
  for price and `Edge { to: broker, slot: 0 }` for exposure).
- What happened: to wire the broker I had to know which input slot is exposure and
  which is price. The `SimBroker` rustdoc (`struct.SimBroker.html`, including its
  `Node` impl) does **not** render `schema()`'s body, so it does not state the slot
  order. I recovered it from design-ledger C10's Realization note ("a two-input
  node (exposure, price)") plus the feat commit body ("exposure (slot 0) + price
  (slot 1)"). I picked slot 0 = exposure, slot 1 = price; the pip signs came out
  correct, confirming the guess — but a wrong guess (swapping them) would also
  bootstrap successfully (both slots are f64, no kind mismatch to catch it) and
  silently produce a wrong-but-plausible equity curve.
- Why spec_gap: a two-input node whose slots are not symmetric and not type-
  distinguishable needs its slot contract on the consumer-facing surface; the
  rustdoc omits it, and a mis-wiring is *not* caught at bootstrap (both f64). The
  ordering "exposure first" is only inferable from prose order in C10 / a commit
  message.
- Recommended action: tighten the rustdoc — name the input slots on `SimBroker`'s
  doc comment (slot 0 = exposure ∈ [-1,+1], slot 1 = price). Same applies to any
  future multi-`f64`-input node where slots are role-distinct but kind-identical.

### [friction] The north-star "combine two signals" move needs a combinator aura-std does not ship
- Example: c0007_4.
- What happened: aura-std ships `Sma`, `Sub`, `Exposure`, `SimBroker`. To combine
  two signals into one exposure — the explicitly stated north-star research move
  ("backtest one signal, combine it with another", C10) — I needed to *add* (or
  weight-sum) two spread streams, and there is no `Add` / weighted-sum / mean
  combinator in aura-std. I hand-authored a project-local `Add2` node (a `schema`
  with two f64 inputs + one f64 output, an `eval` summing the newest values),
  which is legitimate per C16 but is boilerplate every consumer attempting the
  headline move must rewrite. `Sub` exists (difference) but not its companion sum.
- Why friction: the task completed, but the cycle's own stated primary research
  loop ("combine it with another") cannot be expressed with the shipped nodes
  alone; the most basic combinator for it is absent, so the consumer writes it by
  hand. This is the natural next-tidy candidate (cf. the cycle-0006 "recorder
  boilerplate" friction).
- Recommended action: plan — add an `Add` / weighted-sum (or a small `LinComb`)
  combinator to aura-std so the stated north-star combination is expressible from
  shipped blocks. Pairs naturally with the existing `Sub`.

### [friction] Standalone consumer crate still needs the empty `[workspace]` table (carried from cycle-0006)
- Example: all (the fixture crate's `Cargo.toml`).
- What happened: the same nested-consumer-crate resolver fight the cycle-0006
  fieldtest recorded. I pre-emptively added the empty `[workspace]` table (Cargo's
  own keep-it-out-of-the-workspace fix) so the crate builds; without it the build
  fails with "current package believes it's in a workspace when it's not".
- Why friction: it recurs for every downstream consumer crate created beside/under
  the engine repo (C16: "a project is always a Rust crate" depending on aura). Not
  new this cycle, but it bit again, confirming it is a standing onboarding wall, not
  a one-off.
- Recommended action: plan — already noted in the cycle-0006 fieldtest; fold into
  the future `aura new` scaffolder / onboarding docs (open ledger thread). Carry-on
  is acceptable if 0006's item is already queued.

## Recommendation summary

| Finding | Action |
|---|---|
| [working] Exposure clamp/sizing exact, hard bound | carry-on |
| [working] SimBroker integration: sign / causality / pip_size divisor | carry-on |
| [working] end-to-end harness composes + records (incl. combine) | carry-on |
| [spec_gap] SimBroker firing policy / warm-up `0.0` emission undocumented | tighten the design ledger / rustdoc |
| [spec_gap] SimBroker input slot order only in commit body / C10 prose | tighten the rustdoc |
| [friction] no sum/weighted combinator in aura-std for the north-star move | plan |
| [friction] consumer crate needs empty `[workspace]` (carried from 0006) | plan (or carry-on if 0006 item queued) |
