# 5S/5T/5F → v8 ForkScore threshold-calibration handoff

**Date:** 2026-05-11T12:23:57Z
**Ticket:** #000025 §10.14 (closure deliverable) — handoff to #000012.
**Method:** ran the canonical 5S/5T/5F sub-batteries (the packs `fork_score._BATTERY_RATE_KEYS` reads) plus the 5F live packs; computed baseline rates, observability granularity (1/n), and ran `fork_score` on the parent vs three synthetic child perturbations. Pure measurement.

## 1. Baseline rates + granularity

Each row's *rate* is the single metric `fork_score` consumes for that sub-battery (`_BATTERY_RATE_KEYS`). *Granularity* = 1/n — the smallest rate change a single fixture flip can produce, i.e. the finest Δ the scorer could ever observe on that pack.

| battery | sub-battery | fixtures (n) | embedded rate | live rate | granularity (1/n) |
|---|---|---|---|---|---|
| 5s | syntax | 10 | 1.0000 | — | 0.1000 (10.0pp) |
| 5s | semantics | 8 | 1.0000 | — | 0.1250 (12.5pp) |
| 5s | syllogism | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5s | synthesis | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5s | semiotics | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5t | transfer-learning | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5t | triangulation | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5t | truthtables | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5t | transitivity | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5t | time | 30 | 1.0000 | — | 0.0333 (3.3pp) |
| 5f | function | 50 | 1.0000 | 1.0000 | 0.0200 (2.0pp) |
| 5f | finetuning | 50 | 1.0000 | 1.0000 | 0.0200 (2.0pp) |
| 5f | falsification | 62 | 1.0000 | 1.0000 | 0.0161 (1.6pp) |
| 5f | formulate | 50 | 1.0000 | 1.0000 | 0.0200 (2.0pp) |
| 5f | feedback-loop | 50 | 1.0000 | 1.0000 | 0.0200 (2.0pp) |

- **5S mean baseline rate:** 1.0000
- **5T mean baseline rate:** 1.0000
- **5F mean baseline rate:** 1.0000
- **Coarsest pack granularity:** 0.1250 (12.5pp)
- **Finest pack granularity:** 0.0161 (1.6pp)

## 2. Identity-fork verdict (no change)

`fork_score(parent, parent)` → **MARGINAL (score +0.0000)**

Every 5S/5T/5F pack is at ceiling (rate 1.0) at HEAD, so every Δ-rate term is exactly 0 and the identity fork lands in the `[0, SIGNAL_FLOOR)` band → **MARGINAL**, not ACCEPT. Takeaway for #000012: an unchanged child is *not* auto-accepted; positive score has to come from a Δ-rate gain (needs harder fixtures, see §4), an efficiency-bonus increase (`adaptation_efficiency` / `feedback_efficiency`), or one of the non-bench terms (`selfmodel_calibration_gain`, `audit_completeness`, …).

## 3. Floor-constant sanity checks

`SIGNAL_FLOOR = 0.05` · `HARD_REGRESSION_FLOOR = 0.05` (current `arborist/substrate/fork_score.py`). The production parent is at ceiling, so these are exercised against a synthetic *degraded parent* — every 5F sub-battery rate knocked to 0.90, giving a child headroom to improve.

| scenario | verdict |
|---|---|
| identity — `fork_score(HEAD, HEAD)` | MARGINAL (score +0.0000) |
| degraded parent → child recovers **all 5** 5F subs by +0.06 | ACCEPT (score +0.0600) |
| degraded parent → child recovers **only 1** 5F sub by +0.06 | MARGINAL (score +0.0120) |
| degraded parent → child drops one 5F sub by −0.05 | REJECT (score -0.0300) — flags: REGRESSION_5F:5f/falsification: -0.050 |

Row 2 vs row 3 is the **5× averaging dilution**: `_delta_5f` means over all 5 sub-batteries, so the same +0.06 gain scores `1.0·0.06 = 0.06` (ACCEPT) when applied to all five subs but only `1.0·(0.06/5) = 0.012` (MARGINAL) when applied to one. Each battery contributes a separate Δ-term at weight 1.0, so a uniform +`SIGNAL_FLOOR` across *every* sub of *all three* batteries would score `~0.15`.

## 4. Recommendation for #000012

- **Keep `SIGNAL_FLOOR = 0.05` and `HARD_REGRESSION_FLOOR = 0.05`.** They match `docs/bench-maxing.md`'s 5-pp signal floor and the sanity checks in §3 behave as documented.
- **Granularity caveat (load-bearing):** the foundational 5S packs are *coarser* than the floors — `syntax` (n=10 → 10.0pp) and `semantics` (n=8 → 12.5pp). On those packs a single fixture flip is ≥ 5pp, so *any* regression there trips the hard-reject. That is the intended zero-tolerance behaviour on the syntax/semantics base — not a bug — but #000012 should document it: HARD_REGRESSION_FLOOR is not a tunable knob on the small packs, it's effectively 'one fixture'. The n=30 / n=50 / n=62 packs have 3.3pp / 2.0pp / 1.6pp granularity, so there 5pp ≈ 1.5–3 fixture flips.
- **5× averaging dilution (document this in #000012):** a single sub-battery's rate gain is worth a fifth of its face value because `_delta_5{s,t,f}` means over 5 subs. So 'a fork must improve by `SIGNAL_FLOOR`' really means *one of*: ~`0.25` on a single sub, ~`0.05` uniform across one battery's 5 subs, or ~`0.017` uniform across the whole 15-sub suite. If #000012 wants single-sub improvements to weigh equally it should switch `_delta_*` from mean to max-or-sum — but that's a #000012-owned design call, not a calibration finding.
- **Ceiling saturation:** until harder fixtures drop a pack's baseline below 1.0, the `α·Δ5s + β·Δ5t + γ·Δ5f` terms can only be ≤ 0. ForkScore acceptance at the current pack difficulty is driven by the efficiency bonuses + non-bench terms. If #000012 wants the bench Δ-rate terms to carry real positive signal, the 5S/5T/5F packs need a harder tier (or a deliberately-degraded parent baseline) — track that as a #000025 follow-up, not a #000012 blocker.
- **No constant change shipped by this calibration.** This is a handoff document; if #000012 decides to move a floor it owns that edit (and the resulting `governance_policy_hash` is unaffected — ForkScore constants don't fold into it; they're estimator parameters pinned by `ESTIMATOR_VERSION`).

## 5. `delta_aggregator` comparison (#000047, on the #000046 below-ceiling pack)

Below-ceiling baseline: `5f/falsification` at the `bench/fixtures/5f/falsification-hard-v1.jsonl` rate **0.3333** (4/12 — `verify_quotes` over-grounds the rest); a child fork that tightens `verify_quotes` lifts it toward 1.0 — a *single-sub* gain of 0.6667. How that single-sub gain scores under each aggregator:

| aggregator | `γ·Δ5f` | verdict | note |
|---|---|---|---|
| `mean` | +0.1333 | ACCEPT | single-sub gain diluted 1/5 (still clears the floor here — the gain is large; a smaller fix would land MARGINAL, see §3) |
| `max` | +0.6667 | ACCEPT | single-sub gain at face value |
| `sum` | +0.6667 | ACCEPT | = max here (one sub); diverges from max only on a broad multi-sub gain |

Reading: `mean` (default) makes a single-sub verifier fix worth ≈ a fifth of its face value — so closing #000046 (lifting one 5F sub) is rewarded modestly, while a broad cross-sub improvement is rewarded fully; `max`/`sum` flip that. The default stays `mean` (the conservative, noise-robust, regression-symmetric choice — `docs/bench-maxing.md`'s per-rate floor framing); #000012 can pick `max`/`sum` per-deployment via `WeightSet(delta_aggregator=...)` (recorded in `ScoredFork.weights`). #000047 ships the knob, not a default change.
