# Pong RAM Agent Progress

## Round 1 - 2026-05-09

### Current Best

- Best retained policy: `ram_intercept_v3_3` in `pong_policy.py`.
- Best 10-seed score: mean `0.5`, min `-1`, max `9` over seeds `0..9` with `max_steps=30000`.
- Worst seeds in the retained baseline: seeds `0,1,3,4,5,6,8,9` end at `-1`; seed `2` reaches `9`, seed `7` reaches `4`.
- Known failed hypothesis before this round: late intercept lock after `x > SELF_PADDLE_X_RAM` regressed to mean `-5.8` and collapsed seed `1` to `-21`.
- Existing diagnostics: `scripts/trace_points.py`, `scripts/trace_episode.py`, calibrated RGB overlays in `scripts/run_eval.py`, point windows in `diagnostics/point_traces_seeds_0_1_3_4_5_8_9.jsonl`, and failure notes in `failures.jsonl`.

### Candidate Hypotheses

1. Low/mid downward late-return target is too high.
   - Evidence: point traces show repeated losses at `x≈202,y≈86,dy=4` in seeds `1,5,8`, often with the paddle above the target by about 9 RAM units.
   - Explains: repeated low/mid downward misses where the paddle appears to need to move lower at the right edge.
   - Minimal diagnostic: compare a temporary `+8` target bias for `dx>0`, `dy=3..5`, late-right low/mid y windows against baseline over seeds `0..9`.
   - Minimal policy change: add a narrow late-low-down family rule that increases `target_y` only near `x=196..204,y=78..96,dy=3..5`.
   - Valid if: 10-seed mean improves over `0.5` and min stays at least near `-1`; invalid if mean does not improve or min regresses.

2. Mid/upward near-target misses are action-phase misses, not target-coordinate misses.
   - Evidence: traces for seeds `0/9` repeat `x≈203,y≈164,dy=-8` where final paddle error is near target, yet the point is lost.
   - Explains: collisions can fail even when center error is small, suggesting paddle edge, noop phase, or one-frame timing matters.
   - Minimal diagnostic: compare a narrow `mid_up_minus8` target bias and inspect score distribution plus rule hit count.
   - Minimal policy change: if proven, add a narrow upward-ball edge bias only for late-right `y=145..180,dy<=-6`.
   - Valid if: fixes seed `0/9` without hurting seeds that already score positive; invalid if it creates early collapses.

3. High/downward misses need a trajectory-specific lower contact point.
   - Evidence: seed `1` high/downward losses occur near `x≈203,y≈185,dy=8` with the paddle above target; seed `0/9` also have high/downward losses, but with opposite final error sign.
   - Explains: the same high/downward family may be sensitive to collision edge or rebound phase, not a global offset.
   - Minimal diagnostic: compare a narrow `high_down_plus8` target bias over seeds `0..9` and check whether gains are localized or destructive.
   - Minimal policy change: only if diagnostic is clean, add a high/down late-right bias.
   - Valid if: improves high/down seeds without reducing min; invalid if the conflicting error signs cause broad regression.

### Diagnostics Run

- Added `scripts/probe_candidate_rules.py` as a non-policy diagnostic runner. It reuses the current RAM policy structure and applies temporary candidate target changes before action selection.
- Ran `diagnostics/candidate_rule_probe_round1.jsonl` over seeds `0..9`, `max_steps=30000`.
  - `baseline`: mean `0.50`, min `-1`, max `9`.
  - `low_mid_down_plus8`: mean `0.30`, min `-2`, max `9`; seed `5/8` improved from `-1` to `1`, but seed `7` regressed from `4` to `-2`.
  - `mid_up_minus8`: mean `-5.10`, min `-21`, max `19`; large regressions on seeds `0,1,7,9`.
  - `high_down_plus8`: mean `-8.20`, min `-21`, max `9`; seed `1` improved to `5`, but seeds `5/8` collapsed to `-21`.
- Ran narrower low-down probes in `diagnostics/candidate_rule_probe_round1_low_y86.jsonl`.
  - `low_down_y86_plus8`: mean `0.50`, min `-1`, max `9`, identical to baseline despite 76 rule hits.
  - `low_down_y86_plus6`: mean `0.50`, min `-1`, max `9`, identical to baseline despite 76 rule hits.

### Selected Hypothesis

- Selected hypothesis 1 because it was the only candidate with localized positive evidence: broad low/mid downward bias helped seeds `5/8`.
- Narrowed the actual policy test to the repeated `x≈202,y≈86,dy=4` family to reduce the seed `7` regression seen in the broad probe.

### Policy Change And Result

- Temporarily added a minimal `+8` target bias for `x=196..204,y=78..96,dy=3..5`.
- Formal 10-seed validation with `scripts/run_eval.py` as `ram_intercept_v3_4_late_low_down_y86_probe` scored mean `0.5`, min `-1`, max `9`, exactly matching the baseline.
- Because there was no mean improvement, the policy change was withdrawn. The retained strategy remains `ram_intercept_v3_3`.

### Rejected Hypotheses

- Rejected for now: late low/down target-y bias fixes the `x≈202,y≈86,dy=4` family. It can change actions but did not change 10-seed outcomes when narrowed safely.
- Rejected for now: mid/upward `-8` late bias. It caused multiple seed collapses.
- Rejected for now: high/downward `+8` late bias. It helped seed `1` but badly hurt seeds `5/8` and `0/7/9`.

### Next Automatic Exploration

- Inspect action rhythm instead of target position: count `noop/up/down` alternation, deadband stalls, and action reversals in the last 30-80 frames before losses.
- Focus on whether repeated near-target losses happen because the paddle enters deadband too early, oscillates across the target, or has a one-frame cooldown/timing mismatch.
- Keep the next policy change narrow: a conditional deadband or hysteresis adjustment is more plausible than another target offset.
- No-improvement streak: `1`.

## Round 2 - 2026-05-09

### Current Best

- Best retained policy remains `ram_intercept_v3_3`.
- Best retained 10-seed score remains mean `0.5`, min `-1`, max `9`.
- Worst retained seeds remain the `-1` seeds; no seed currently collapses below `-1` in the accepted policy.
- Failed hypotheses so far: late intercept lock, late low/down target-y bias, mid/up target bias, high/down target bias.

### Candidate Hypotheses

1. Low/down losses are caused by a late upward correction that kills downward momentum.
   - Evidence: in `x≈202,y≈86,dy=4` losses, the tail includes `x=190,pad=86,target=76,action=up`, then target jumps to `82/86` while the paddle is still above the final contact.
   - Explains: the paddle reverses up just before the target ramps downward.
   - Minimal diagnostic: force `DOWN` for the late low/down family and compare 10 seed scores.
   - Minimal policy change: override action to `DOWN` for `x=190..204,y=70..100,dy=3..5`.
   - Valid if: fixes repeated low/down losses without hurting seeds `5/8/7`.

2. Late downward balls should suppress `UP`, but not force `DOWN`.
   - Evidence: several downward losses show alternating `up/down` decisions near the right edge.
   - Explains: removing the wrong-direction correction might reduce oscillation while preserving normal control.
   - Minimal diagnostic: convert late downward `UP` actions to `NOOP`.
   - Minimal policy change: for `x=188..204,y=70..140,dy>0`, replace `UP` with `NOOP`.
   - Valid if: mean improves and no seed collapses.

3. Existing `DOWN` momentum should continue through deadband for one late step.
   - Evidence: some near-target losses enter `NOOP` with the paddle close to target, then miss at contact.
   - Explains: deadband may stop the paddle one control step too early.
   - Minimal diagnostic: if previous action was `DOWN` and current late downward action is not `DOWN`, keep `DOWN`.
   - Minimal policy change: add one-step down hysteresis in late-right downward windows.
   - Valid if: avoids final noops without broad action overrides and improves mean.

### Diagnostics Run

- Extended `scripts/probe_candidate_rules.py` with action-level variants.
- Ran `diagnostics/candidate_action_probe_round2.jsonl` over seeds `0..9`, `max_steps=30000`.
  - `baseline`: mean `0.50`, min `-1`, max `9`.
  - `low_down_force_down`: mean `0.40`, min `-2`, max `9`; seed `7` improved `4 -> 5`, but seeds `5/8` regressed `-1 -> -2`.
  - `late_down_no_up`: mean `-7.40`, min `-21`, max `9`; collapsed seeds `0,5,8,9`.
  - `late_down_keep_down`: mean `0.50`, min `-1`, max `9`; 8 rule hits, no score change.

### Selected Hypothesis

- Selected hypothesis 3 for the formal policy test because it was the only action-rhythm rule with no regression in diagnostic probing.

### Policy Change And Result

- Temporarily added late downward one-step keep-down hysteresis.
- Formal 10-seed validation as `ram_intercept_v3_4_late_down_keep_down_probe` scored mean `0.5`, min `-1`, max `9`.
- Because there was no mean improvement, the policy change was withdrawn.

### Rejected Hypotheses

- Rejected: forcing `DOWN` in the late low/down family. It has localized upside but hurts seeds `5/8`.
- Rejected: suppressing late `UP` for downward balls. It causes severe collapses.
- Rejected: one-step keep-down hysteresis as currently scoped. It changes timing but not score.

### Next Automatic Exploration

- Compare winning vs losing point setup earlier in the rally: serve type, first return, and first right-paddle contact after reset.
- Check whether the repeated late misses are consequences of the paddle being in a bad phase before the final approach, rather than final-window control.
- Consider diagnostics that bucket whole point trajectories by first-contact family and paddle phase, before any new policy rule.
- No-improvement streak: `2`.

## Round 3 - 2026-05-09

### Current Best

- Best retained policy remains `ram_intercept_v3_3`.
- Best retained 10-seed score remains mean `0.5`, min `-1`, max `9`.
- No code change from this round was retained.

### Candidate Hypotheses

1. Fixed `CENTER_Y=121` while the ball moves left leaves the paddle in a bad setup phase.
   - Evidence: residual losses may be determined before the final approach; current policy centers whenever `dx<=0`.
   - Explains: repeated late misses could be inherited from a poor standby location before the next incoming ball.
   - Minimal diagnostic: compare alternate left-moving standby targets over the same 10 seeds.
   - Minimal policy change: set the `dx<=0` target to a different fixed y.
   - Valid if: 10-seed mean improves without introducing collapses.

2. A lower standby target could help low/down incoming families.
   - Evidence: low/down losses around `y≈86` show the paddle too high at final contact.
   - Explains: starting lower before the ball returns could reduce late catch-up.
   - Minimal diagnostic: `left_center_100`.
   - Minimal policy change: use `100` instead of `121` for left-moving balls.
   - Valid if: low/down seeds improve and min remains near `-1`.

3. Tracking ball y while it moves left could preserve phase better than a fixed center.
   - Evidence: a fixed center ignores opponent-side trajectory, bounces, and upcoming vertical family.
   - Explains: tracking could preload the paddle toward the eventual return lane.
   - Minimal diagnostic: `left_track_ball_y`.
   - Minimal policy change: when `dx<=0`, target the clamped current `ball_y`.
   - Valid if: mean improves and no seed collapses.

### Diagnostics Run

- Extended `scripts/probe_candidate_rules.py` with left-moving setup variants.
- Ran `diagnostics/candidate_setup_probe_round3.jsonl` over seeds `0..9`, `max_steps=30000`.
  - `baseline`: mean `0.50`, min `-1`, max `9`.
  - `left_center_100`: mean `-20.80`, min `-21`, max `-19`.
  - `left_center_140`: mean `-18.80`, min `-21`, max `-17`.
  - `left_track_ball_y`: mean `-15.60`, min `-21`, max `-3`.

### Selected Hypothesis

- No hypothesis was safe enough to implement in the formal policy. All three setup variants were catastrophic compared with baseline.

### Policy Change And Result

- No formal policy change was made or retained.
- The diagnostic result directly rejects this family of setup changes.

### Rejected Hypotheses

- Rejected: changing the whole `dx<=0` standby target to `100`.
- Rejected: changing the whole `dx<=0` standby target to `140`.
- Rejected: tracking current ball y during `dx<=0`.
- Updated conclusion: current `CENTER_Y=121` is a stabilizing component of `ram_intercept_v3_3`.

### Stop Condition

- No-improvement streak: `3`.
- Stopping because the user-specified condition "连续 3 轮没有有效提升" is met.
- Current evidence suggests more small local target/action patches are unlikely to improve the retained policy. The next meaningful step would be richer structural diagnostics of collision timing and paddle phase across whole points.

## Round 4 - 2026-05-09

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_3` in `pong_policy.py`.
- Best retained 10-seed score remains mean `0.5`, min `-1`, max `9` over seeds `0..9` with `max_steps=30000`.
- Current accepted code includes state extraction for score RAM, low-serve handling, calibrated overlay helpers, point tracing, and candidate-rule probing.
- No policy change from this round was made or retained.

### Review Of Recent Failed Exploration

- The failed rules were not independent misses; they exposed the same blind spot from different angles.
- Late target-y offsets (`low_down_y86_plus6/+8`, `mid_up_minus8`, `high_down_plus8`) assumed the main error was a wrong target coordinate. They either did nothing when narrowly scoped or caused seed collapses when broad enough to affect behavior.
- Late action overrides (`force_down`, `no_up`, `keep_down`) assumed the main error was the final action sign. They either regressed seeds or produced no score movement.
- Left-moving setup changes assumed the main error was pre-positioning while the ball travels left. Global setup variants collapsed badly, showing that the current center target is stabilizing even if residual losses remain.
- Common blind spot: the current mental model treats `target_y`, `action`, and collision as nearly instantaneous and local. The data shows residual failures depend on a longer stateful paddle phase and on the real right-paddle interaction timing, not a single late target or action patch.

### Basic Assumption Map

#### State Model

- Hypothesis S1: `ram[49]` and `ram[54]` expose ball x/y reliably while active.
  - Current support: calibrated overlays and point traces align reward events and ball movement; no score regression after adding score bytes.
  - Possible error source: inactive/reset frames can produce huge fake `dx/dy` if compared directly across point boundaries.
  - Verification: existing traces show `ball_y=0` inactive windows and score RAM transitions; future diagnostics should reset velocity history on score/inactive transitions.
  - If true: keep using these bytes but protect all velocity-derived logic around point boundaries.
  - If false: revisit visual/RAM calibration before any policy rule.
- Hypothesis S2: `ram[51]` is right paddle y, but it is not an instantaneous controlled position.
  - Current support: action-effect diagnostic shows `UP`, `DOWN`, and `NOOP` each map to multiple next-step paddle deltas.
  - Possible error source: policy compares target to current `ram[51]` as if next action directly moves toward target.
  - Verification: `diagnostics/point_phase_summary.json` action-effect histograms.
  - If true: strategy needs a paddle phase/velocity state, not just current y.
  - If false: action probe or trace instrumentation is misaligned and should be fixed first.
- Hypothesis S3: score bytes `ram[14]` self and `ram[13]` opponent correctly define point boundaries.
  - Current support: point traces and failure notes align these bytes with reward events.
  - Possible error source: max-step truncation can hide point outcomes in long rallies.
  - Verification: `run_eval.py` records `done`, `steps`, and score distribution at `max_steps=30000`.
  - If true: keep evaluating whole episodes and point-level records.
  - If false: score-based point grouping is unreliable and trace windows need reward-only segmentation.

#### Physical Model

- Hypothesis P1: The current `SELF_PADDLE_X_RAM=192` is useful for prediction but is not the observed collision boundary.
  - Current evidence: `scripts/analyze_point_phase.py` found wins ending around RAM x `186..190` while most losses are still right-moving at x `202..204`.
  - Why it explains the bottleneck: late rules around x `196..204` act after the successful-contact region has already passed, or when the ball has already missed the paddle.
  - Verification: `diagnostics/point_phase_summary.json` final-x and contact-zone distributions.
  - If true: policy needs earlier contact/phase planning around x `184..195`, not more right-edge chasing.
  - If false: final-x analysis is confusing reward timing with collision timing; validate with RGB frame overlays around x `184..204`.
- Hypothesis P2: Wall reflection bounds `46..210` and target clamp `46..196` are locally adequate.
  - Current support: global offset/clamp sweeps and target-bias probes regressed when changed broadly.
  - Possible error source: reflection may be correct in RAM y but wrong for paddle contact geometry or paddle half-height.
  - Verification: compare wins/losses by contact-zone error and ball family.
  - If true: do not retune reflection constants.
  - If false: build a collision-envelope model using successful contacts, not a global y offset.
- Hypothesis P3: Winning contact tolerates large signed target error in some families.
  - Current evidence: contact-zone family summary includes wins with mean errors around `-21`, `17`, `8`, etc.
  - Why it explains the bottleneck: final target error alone is not a reliable classifier for hit/miss.
  - Verification: family-level rows in `diagnostics/point_phase_summary.json`.
  - If true: optimize a family/phase classifier rather than minimizing center error.
  - If false: current target/paddle coordinates are mixing incompatible coordinate systems.

#### Action Semantics

- Hypothesis A1: `UP=2`, `DOWN=3`, `NOOP=0` are directionally correct on average.
  - Current support: `scripts/probe_actions.py` and aggregate deltas show `UP` mean negative, `DOWN` mean positive.
  - Possible error source: same action can produce opposite-signed movement depending on internal phase.
  - Verification: `diagnostics/point_phase_summary.json` shows `DOWN` has frequent negative deltas and `UP` has frequent positive deltas.
  - If true: keep action labels but add phase awareness.
  - If false: action mapping itself must be rechecked in isolation.
- Hypothesis A2: `NOOP` does not mean no paddle movement.
  - Current evidence: `NOOP` most often moves by `-9` or `+9`, with near-zero mean only because directions cancel.
  - Why it explains the bottleneck: deadband/noop can let the paddle coast past the intended contact, so simple deadband tuning is under-specified.
  - Verification: action-effect diagnostic.
  - If true: a future policy should model latent paddle velocity or at least previous movement sign.
  - If false: trace sampling is too coarse and needs per-actual-frame instrumentation.
- Hypothesis A3: Last-action overrides are too late and too blunt.
  - Current support: Round 2 action variants did not improve mean and sometimes collapsed.
  - Possible error source: action changes need to occur several policy steps before contact.
  - Verification: compare contact-zone phase at x `184..195` rather than final x `202..204`.
  - If true: future changes should trigger earlier and be validated by phase metrics before score runs.
  - If false: a narrower final action rule might still work, but current evidence is weak.

#### Time Model

- Hypothesis T1: One policy step covers multiple Atari frames and interacts with internal paddle velocity.
  - Current evidence: next-step paddle deltas commonly jump by `8..24` RAM units.
  - Why it explains the bottleneck: one-step target decisions can overshoot; the policy is really controlling a phase oscillator.
  - Verification: action-effect histograms and action-phase grouping by `paddle_y % 4`.
  - If true: model prior action and current paddle trend explicitly.
  - If false: envpool step or trace timing needs deeper inspection.
- Hypothesis T2: Reward appears after the relevant contact/miss event, so final loss samples can be post-miss.
  - Current evidence: most losses have final right-moving x around `202..204`, while wins register earlier.
  - Why it explains the bottleneck: using final loss sample to tune target y can chase already-lost states.
  - Verification: inspect RGB overlays or trace rows around x `184..195` for losses.
  - If true: diagnostics and policy triggers should anchor on the last pre-contact zone, not the reward frame.
  - If false: the RAM x physical model is incomplete.
- Hypothesis T3: Left-moving center phase is part of a stable periodic control loop.
  - Current support: Round 3 global setup variants collapsed.
  - Possible error source: the loop is beneficial on average but may still lock some seeds into deterministic alternating losses.
  - Verification: compare repeating point families by seed and phase.
  - If true: avoid global setup changes; only consider structural phase controllers.
  - If false: more localized setup changes might be possible, but current evidence does not justify them.

#### Evaluation Model

- Hypothesis E1: 10 seeds with `max_steps=30000` is the current comparison gate.
  - Current support: baseline and formal probes use this gate, and 10k truncation was already shown weaker.
  - Possible error source: seeds `0..9` still include repeated deterministic families and may overfit to them.
  - Verification: after any structural policy, expand beyond seeds `0..9`.
  - If true: keep requiring mean, min, max, and seed-level scores against `ram_intercept_v3_3`.
  - If false: define a broader seed suite before claiming improvement.
- Hypothesis E2: Mean alone is insufficient.
  - Current support: broad probes sometimes improved selected seeds while worsening min or collapsing others.
  - Possible error source: optimizing one repeated family hides new failure families.
  - Verification: require seed-level table and point/failure pattern comparison.
  - If true: do not accept a patch unless it improves or preserves min and reduces repeated failures.
  - If false: single-seed improvements would be enough, but prior regressions reject that.
- Hypothesis E3: Passing unit tests only verifies extraction/helpers, not policy improvement.
  - Current support: tests cover RAM bytes, target bounds, low-serve rule, and overlay helpers, but not score.
  - Verification: pair tests with `run_eval.py` or diagnostic probes for behavior claims.
  - If true: use tests as regression guards only.
  - If false: test coverage must be expanded before relying on it.

### Diagnostic Experiment

- Added `scripts/analyze_point_phase.py`.
- Ran:
  - `python3 scripts/analyze_point_phase.py --jsonl diagnostics/point_traces_seeds_0_1_3_4_5_8_9.jsonl --output diagnostics/point_phase_summary.json`
- Key results:
  - Records analyzed: `207` point records (`100` wins, `107` losses).
  - Win final x: mean `187.64`, min `186`, max `190`.
  - Loss final x: mean `202.34`, min `187`, max `205`; most losses at x `202` or `203`.
  - Contact-zone error at x `184..195` is similar in aggregate for wins and losses (`-1.48` vs `-2.29`), so center error alone does not explain outcome.
  - `UP` next-step paddle delta mean `-9.09`, but includes frequent positive deltas such as `+11`.
  - `DOWN` next-step paddle delta mean `+8.68`, but includes frequent negative deltas such as `-11`.
  - `NOOP` has near-zero mean but most often continues motion by `-9` or `+9`.

### Evidence For Strategy Change

- Evidence is strong that the remaining bottleneck is not a narrow target-coordinate error.
- Evidence is also strong that the current policy's action model is too simple: action choice should not be treated as immediately setting paddle direction/position.
- This is a structural diagnosis, not yet a safe one-line strategy change. The next plausible improvement would be a phase-aware controller or learned/diagnosed paddle dynamics model, not another local rule patch.

### Policy Change

- No policy change was made.
- Reason: the evidence points to a foundational action/time/collision model issue. A minimal local change would likely repeat the last three rounds of brittle patches.

### Verification

- Re-ran unit tests with the project venv:
  - `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `12` tests passed.
- System `python3` cannot run `tests/test_video_overlay.py` because `envpool` is not installed outside `.venv`; this is an environment issue, not a test failure under the project environment.

### Added Or Excluded Possibilities

- Added: successful right-paddle interaction is observed around RAM x `186..190`; tuning at x `202..204` is likely post-miss for many losses.
- Added: `NOOP` is a coasting action in this sampled control loop, not a stationary action.
- Added: paddle phase/history is a necessary state variable for the next approach.
- Excluded: final-frame target error as the primary metric for policy improvement.
- Excluded: more local right-edge target/action patches as the next best use of time.

### Next Most Valuable Unknown

- Build or fit a small paddle dynamics diagnostic from traces: predict next `self_paddle_y` from `(current_y, previous_y, action, action history, phase)` and validate whether it explains the repeated loss families.
- If that model is accurate, use it to plan contact-zone paddle phase at x `186..190`, then compare against the current baseline over seeds `0..9` and expanded seeds.

### Stop Condition

- The loop has reached a structural stop condition: current evidence indicates the retained method needs a structural action/time model change before further policy improvement is likely.
- The accepted baseline remains `ram_intercept_v3_3`; this round's durable artifacts are the diagnostic script, JSON summary, tests rerun, and this hypothesis map.

## Round 5 - 2026-05-09

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_3` in `pong_policy.py`.
- Best retained 10-seed score remains mean `0.5`, min `-1`, max `9` over seeds `0..9` with `max_steps=30000`.
- Strong seeds remain seed `2` with `9` and seed `7` with `4`; seeds `0,1,3,4,5,6,8,9` remain `-1` under the accepted baseline.
- No strategy change from this round was retained.

### Hypothesis Map

1. A one-step paddle dynamics model needs action history/phase, not action alone.
   - Explains: `NOOP`, `UP`, and `DOWN` sometimes produce opposite-signed next paddle movement in existing traces.
   - Evidence source: `diagnostics/point_phase_summary.json` action-effect histograms.
   - Minimal diagnostic: cross-seed majority prediction of next paddle delta using feature sets from action only through action plus `paddle_y % 4`, previous delta sign, and error bin.
   - Minimal change if true: add only a phase-aware control correction after a probe proves a specific phase rule improves scores.
   - If false: phase/history can be deprioritized and focus should move back to collision geometry or state extraction.

2. Repeated contact-zone loss phase keys are actionable control states.
   - Explains: losses repeat at x `184..195` with keys such as `dy=4; action=noop/up; prev=1` and `dy=-8; action=noop; prev=-1`.
   - Evidence source: new contact-phase buckets in `diagnostics/paddle_dynamics_summary.json`.
   - Minimal diagnostic: run temporary action overrides for only these phase keys in `scripts/probe_candidate_rules.py`.
   - Minimal change if true: retain exactly one narrow phase rule that improves 10-seed mean/min.
   - If false: phase keys are mostly descriptive labels and counterfactual action effects need direct probing.

3. A phase-aware correction at x `184..195` can be safer than prior right-edge rules.
   - Explains: Round 1/2 rules acted at or after x `196..204`, often post-miss; a contact-zone rule might act before the miss.
   - Evidence source: Round 4 showed wins register around x `186..190`, losses at x `202..204`.
   - Minimal diagnostic: compare `phase_dy4_noop_to_down`, `phase_dy4_up_to_noop`, and `phase_dyneg8_noop_to_up` against baseline over seeds `0..9`.
   - Minimal change if true: implement the single winning phase action override in `RamPolicy`.
   - If false: simple deterministic one-action phase overrides are not enough.

### Diagnostics Run

- Added `scripts/analyze_paddle_dynamics.py`.
- Ran:
  - `.venv/bin/python scripts/analyze_paddle_dynamics.py --jsonl diagnostics/point_traces_seeds_0_1_3_4_5_8_9.jsonl --output diagnostics/paddle_dynamics_summary.json`
- Key results:
  - Transitions analyzed: `16036`.
  - Cross-seed action-only next-delta prediction: exact `0.210`, sign `0.703`, mean absolute error `9.765`.
  - `action + paddle_y % 4 + previous_delta_sign`: exact `0.435`, sign `0.867`, mean absolute error `4.662`.
  - Adding paddle error bins improved exact accuracy to `0.510` and mean absolute error to `4.154`, but sign accuracy stayed about `0.862`.
  - This supports the structural claim that current action semantics are phase/history dependent.
- Extended `scripts/probe_candidate_rules.py` with diagnostic-only phase variants using previous paddle delta.
- Ran:
  - `.venv/bin/python scripts/probe_candidate_rules.py --seeds 0,1,2,3,4,5,6,7,8,9 --max-steps 30000 --variants baseline,phase_dy4_noop_to_down,phase_dy4_up_to_noop,phase_dyneg8_noop_to_up --jsonl diagnostics/candidate_phase_probe_round5.jsonl`
- Results:
  - `baseline`: mean `0.50`, min `-1`, max `9`.
  - `phase_dy4_noop_to_down`: mean `-3.40`, min `-21`, max `9`; seed `7` improved `4 -> 5`, but seeds `5/8` collapsed to `-21`.
  - `phase_dy4_up_to_noop`: mean `-9.40`, min `-21`, max `-1`; severe regressions in seeds `1,2,5,7,8`.
  - `phase_dyneg8_noop_to_up`: mean `-7.40`, min `-21`, max `9`; regressions in seeds `1,5,7,8`.

### Supported And Rejected Hypotheses

- Supported: one-step paddle movement is meaningfully phase/history dependent. The prediction improvement is large and holds in leave-one-seed-out folds.
- Rejected: the tested contact-zone phase keys are directly fixable with single forced action substitutions.
- Rejected: acting earlier at x `184..195` is automatically safer than right-edge rules. Earlier rules still caused deterministic seed collapses.
- Updated interpretation: the phase model is real, but it is not enough to choose counterfactual actions from observational traces alone. The same phase key can appear in both wins and losses, and local overrides disrupt later deterministic rallies.

### Policy Change

- No policy change was made to `pong_policy.py`.
- The only code retained is diagnostic infrastructure in `scripts/analyze_paddle_dynamics.py` and additional diagnostic variants in `scripts/probe_candidate_rules.py`.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `12` tests passed.
- `.venv/bin/python -m py_compile scripts/analyze_paddle_dynamics.py scripts/probe_candidate_rules.py`
  - Result: passed.

### Durable Assets

- Added `diagnostics/paddle_dynamics_summary.json`.
- Added `diagnostics/candidate_phase_probe_round5.jsonl`.
- Added `scripts/analyze_paddle_dynamics.py`.
- Kept phase variants in `scripts/probe_candidate_rules.py` as reproducible negative probes.

### Next Most Valuable Unknown

- The next useful step is counterfactual action data, not another observational rule. For selected contact-zone states, run short controlled rollouts from matched seeds with alternate actions for 1-3 steps and measure whether the point outcome or next contact phase changes.
- If counterfactual branching is not feasible in EnvPool without emulator state cloning, build a direct action-probe dataset from reset/serve trajectories that intentionally drives the paddle through the suspicious phase keys and records alternate action effects.

### Stop Condition

- Stopping this loop because the current method now needs a larger structural experiment: counterfactual action-effect data or controlled state cloning. The structural change has been split into the two executable tasks above.
- No-improvement streak for accepted policy remains unbroken since `ram_intercept_v3_3`; latest round added an explanatory diagnostic but no score gain.

## Round 6 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_4_opening_low_dy8` in `pong_policy.py`.
- Previous accepted baseline: `ram_intercept_v3_3`, mean `0.5`, min `-1`, max `9` over seeds `0..9`, `max_steps=30000`.
- New formal 10-seed score from `scripts/run_eval.py`: mean `0.9`, min `-1`, max `9`.
- Seed-level change versus previous baseline: seeds `5` and `8` improved from `-1` to `1`; seeds `0,1,2,3,4,6,7,9` stayed unchanged.
- Strong seeds remain `2` with `9` and `7` with `4`; worst seeds remain `0,1,3,4,6,9` at `-1`.

### Hypothesis Map

1. Observational phase keys need one-shot counterfactual validation before policy changes.
   - Explains: Round 5 phase overrides collapsed because they applied repeatedly to both good and bad occurrences.
   - Evidence source: `diagnostics/candidate_phase_probe_round5.jsonl` and repeated mixed win/loss phase keys in `diagnostics/paddle_dynamics_summary.json`.
   - Minimal diagnostic: replay each seed from reset, intervene once at the first matching suspicious state for 1-3 policy steps, then resume baseline.
   - Minimal change if true: retain only a score/phase-gated one-shot correction whose 10-seed score improves and whose min does not regress.
   - If false: direct counterfactual replay would show no local score movement, forcing work toward state cloning or a richer dynamics model.

2. A low opening `dy=8` contact is a missed serve/setup family for seeds `5` and `8`.
   - Explains: seeds `5/8` repeatedly ended at `-1`; a single early contact-phase decision could shift the entire deterministic rally tree.
   - Evidence source: one-shot counterfactual hits at step `59`, `x=186,y=137,pad=135,target=149,prev_delta=-12` for both seeds.
   - Minimal diagnostic: compare `once_opening_low_dy8_down_to_up_h1` against baseline over seeds `0..9`.
   - Minimal change if true: add one score-gated correction at `0-0` that executes `UP` instead of baseline `DOWN` only for that opening family.
   - If false: opening low `dy=8` is descriptive but not causal; continue with other first-contact families.

3. Broader low `dy=8` correction may help but still risks later-rally regressions.
   - Explains: a broader low `dy=8` rule improved seed `5/8` but reduced seed `7`.
   - Evidence source: `diagnostics/counterfactual_low_dy8_probe_round6.jsonl`.
   - Minimal diagnostic: split opening `0-0` hits from later-rally hits.
   - Minimal change if true: keep only the opening `0-0` correction.
   - If false: a broader rule would be acceptable, but current data rejects it.

4. Mid/up `dy=-8` one-shot corrections are still unsafe.
   - Explains: prior near-target upward misses looked tempting, but changing `NOOP` to `UP` or `DOWN` can redirect the deterministic episode tree.
   - Evidence source: `diagnostics/counterfactual_action_probe_round6.jsonl`.
   - Minimal diagnostic: compare one-shot `dy=-8` variants over seeds `0..9`.
   - Minimal change if true: add a one-shot `dy=-8` correction.
   - If false: do not alter mid/up near-target behavior without deeper state cloning.

### Diagnostics Run

- Added `scripts/probe_counterfactual_actions.py`.
- First pass:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9 --max-steps 30000 --jsonl diagnostics/counterfactual_action_probe_round6.jsonl`
  - `once_dy8_down_to_up_h1`: mean `0.70`, min `-1`, max `9`; seeds `5/8` improved `-1 -> 1`, but seed `7` regressed `4 -> 2`.
  - `once_dy8_down_to_noop_h1`: mean `0.30`; seed `7` regressed and seeds `5/8` did not improve.
  - `once_dyneg8_noop_to_up_h1`: mean `-2.80`, min `-21`; rejected.
  - `once_dyneg8_noop_to_down_h1`: mean `0.20`; improved seeds `0/9` to `7` but regressed seed `2` to `-5`, so rejected.
- Narrow low `dy=8` pass:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9 --max-steps 30000 --variants baseline,once_low_dy8_down_to_up_h1,once_dy8_down_to_up_h1 --jsonl diagnostics/counterfactual_low_dy8_probe_round6.jsonl`
  - `once_low_dy8_down_to_up_h1`: mean `0.80`, min `-1`, max `9`; still regressed seed `7` from `4` to `3`.
- Opening-only pass:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9 --max-steps 30000 --variants baseline,once_opening_low_dy8_down_to_up_h1,once_low_dy8_down_to_up_h1 --jsonl diagnostics/counterfactual_opening_low_dy8_probe_round6.jsonl`
  - `once_opening_low_dy8_down_to_up_h1`: mean `0.90`, min `-1`, max `9`; only 2 interventions, both on seeds `5/8`, both improving `-1 -> 1`; no seed regression.

### Policy Change

- Retained one minimal policy change in `RamPolicy`:
  - track `previous_paddle_y`;
  - allow one opening correction per episode;
  - at score `0-0`, for `dx>0,dy=8`, `x=184..188`, `y=130..145`, paddle `128..142`, previous paddle delta `-15..-8`, and baseline action `DOWN`, execute `UP` once.
- Added `is_opening_low_dy8_correction_state()` to keep the rule testable and isolated.
- Added a unit test proving the rule is score/action gated and narrow.

### Formal Validation

- Ran:
  - `.venv/bin/python scripts/run_eval.py --episodes 10 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_4_opening_low_dy8 --notes "opening low dy=8 one-step correction: at 0-0 score, x=184..188 y=130..145 with paddle recovering upward, execute UP instead of baseline DOWN once; counterfactual probe improved 10-seed mean 0.5 to 0.9" --no-video`
- Result:
  - scores `[-1,-1,9,-1,-1,1,-1,4,1,-1]`
  - mean `0.9`, min `-1`, max `9`, env steps `138373`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `report.html`.

### Supported And Rejected Hypotheses

- Supported: one-shot counterfactual replay is useful enough to distinguish broad harmful phase rules from a safe opening-family correction.
- Supported: seeds `5/8` had a causal opening low `dy=8` action error that was not fixed by target offsets or repeated phase overrides.
- Rejected: broad low `dy=8` correction, because it still hit later seed `7` states and reduced score.
- Rejected: one-shot `dy=-8` corrections as currently scoped, because they caused seed collapses or traded off strong seeds.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `13` tests passed.
- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py`
  - Result: passed.

### Durable Assets

- Added `scripts/probe_counterfactual_actions.py`.
- Added:
  - `diagnostics/counterfactual_action_probe_round6.jsonl`
  - `diagnostics/counterfactual_low_dy8_probe_round6.jsonl`
  - `diagnostics/counterfactual_opening_low_dy8_probe_round6.jsonl`
  - `diagnostics/counterfactual_opening_low_dy8_20seed_round6.jsonl`
- Updated `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### New Structural Issue

- The 20-seed diagnostic run was executed after the policy change, so its `baseline` label reflects the new current policy rather than the old `ram_intercept_v3_3`. It still exposes a current-policy risk: seed `13` scores `-21` under the current policy over seeds `0..19`.
- Next diagnostics should either add an explicit legacy-baseline switch to `scripts/probe_counterfactual_actions.py` or compare against a saved policy variant before making claims beyond seeds `0..9`.

### Next Automatic Exploration

- Trace current-policy seed `13` with `scripts/trace_points.py` and classify whether the `-21` collapse is a new issue or existed under the previous policy.
- Add an explicit `--disable-opening-low-dy8-correction` or variant-local legacy policy path to the counterfactual diagnostic so expanded-seed comparisons remain clean after policy changes.
- If seed `13` is unrelated to the new correction, search for the earliest repeated loss family in seeds `10..19`; if it is related, narrow or gate the opening rule further.

## Round 7 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_5_low_serve_late_noop`.
- Previous retained best: `ram_intercept_v3_4_opening_low_dy8`, formal 10-seed mean `0.9`, min `-1`, max `9`.
- Clean 20-seed legacy comparison showed the Round 6 opening fix improved old `ram_intercept_v3_3` from mean `0.0` to `0.2` over seeds `0..19`, but both old and Round 6 policies still scored seed `13` at `-21`.
- New formal 20-seed score: mean `1.2`, min `-1`, max `9`; seed `13` improved from `-21` to `-1`; all other seeds `0..19` matched the Round 6 current-policy baseline.

### Hypothesis Map

1. Seed `13` collapse is not caused by the Round 6 opening correction.
   - Explains: the post-change 20-seed run showed seed `13=-21`, but the diagnostic baseline label was ambiguous.
   - Evidence source: `diagnostics/counterfactual_opening_low_dy8_20seed_legacy_compare_round7.jsonl`.
   - Minimal diagnostic: add a switch to disable the opening correction and rerun old baseline vs one-shot opening intervention.
   - Minimal change if true: keep Round 6 and search seed `13` as a separate failure family.
   - If false: narrow or remove the opening correction.

2. Seed `13` is a repeated low-serve late `NOOP` phase failure.
   - Explains: after the first point, seed `13` loses the same low-up point 20 times at `x=205,y=51,dy=-4,target=50,pad=58`.
   - Evidence source: `diagnostics/point_traces_seed13_round7.jsonl`.
   - Minimal diagnostic: change only late low-serve `NOOP` to `UP`, separately from `DOWN` and broad all-action variants.
   - Minimal change if true: add a low-serve late noop correction scoped to `target=50`, `x=189..201`, `y=55..67`, paddle `35..65`.
   - If false: seed `13` needs a different low-serve target or a reset/serve state machine.

3. Broad late low-serve force-up is too blunt.
   - Explains: changing every late low-serve action to `UP` did not fix seed `13`.
   - Evidence source: `diagnostics/counterfactual_low_serve_late_probe_round7.jsonl`.
   - Minimal diagnostic: split late low-serve action classes (`DOWN`, `NOOP`, any action).
   - Minimal change if true: only alter `NOOP`, not `DOWN`.
   - If false: a broader low-serve action override could be retained, but current data rejects it.

### Diagnostics Run

- Added `opening_low_dy8_correction_enabled` to `RamPolicy` and `--disable-opening-low-dy8-correction` to `scripts/probe_counterfactual_actions.py`.
- Ran clean legacy comparison:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --disable-opening-low-dy8-correction --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 --max-steps 30000 --variants baseline,once_opening_low_dy8_down_to_up_h1 --jsonl diagnostics/counterfactual_opening_low_dy8_20seed_legacy_compare_round7.jsonl`
  - old baseline: mean `0.00`, min `-21`, max `9`.
  - opening intervention: mean `0.20`, min `-21`, max `9`; only seeds `5/8` changed; seed `13` stayed `-21`.
- Traced seed `13`:
  - `.venv/bin/python scripts/trace_points.py --seeds 13 --max-steps 30000 --window 100 --tail 0 --jsonl diagnostics/point_traces_seed13_round7.jsonl`
  - result: 21 losses, score `-21`; 20 losses share `loss/low_up/near_target` with final `x=205,y=51,dy=-4,target=50,pad=58`.
- Ran low-serve late counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,13 --max-steps 30000 --variants baseline,repeat_low_serve_late_to_up_h1,repeat_low_serve_late_down_to_up_h1,repeat_low_serve_late_noop_to_up_h1 --jsonl diagnostics/counterfactual_low_serve_late_probe_round7.jsonl`
  - broad late-to-up: no score change.
  - late `DOWN -> UP`: no score change.
  - late `NOOP -> UP`: seed `13` improved `-21 -> -1`; seeds `0..9` unchanged.
- Expanded validation probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 --max-steps 30000 --variants baseline,repeat_low_serve_late_noop_to_up_h1 --jsonl diagnostics/counterfactual_low_serve_late_20seed_round7.jsonl`
  - current baseline: mean `0.20`, min `-21`, max `9`.
  - late low-serve noop correction: mean `1.20`, min `-1`, max `9`; only seed `13` changed.

### Policy Change

- Retained one minimal strategy change:
  - if the current low-serve rule is aiming at `LOW_SERVE_TARGET_Y=50`;
  - and the ball is moving right/up with `dy=-4` in `x=189..201,y=55..67`;
  - and the paddle is in `35..65`;
  - and baseline action is `NOOP`;
  - execute `UP` for that step.
- Added `is_low_serve_late_noop_correction_state()` and a unit test for action/target gating.

### Formal Validation

- Ran:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_5_low_serve_late_noop --notes "low-serve late noop correction: keep v3_4 opening low-dy8 fix and change only late low-serve target=50 noop at x=189..201 y=55..67 pad=35..65 to UP; counterfactual 20-seed probe improved mean 0.2 to 1.2 and min -21 to -1" --no-video`
- Result:
  - scores `[-1,-1,9,-1,-1,1,-1,4,1,-1,-1,-1,4,-1,4,9,-1,4,-1,-1]`
  - mean `1.2`, min `-1`, max `9`, env steps `235425`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Supported And Rejected Hypotheses

- Supported: seed `13` is an existing expanded-seed low-serve phase failure, not a regression from Round 6.
- Supported: the actionable counterfactual is specifically late low-serve `NOOP -> UP`; changing `DOWN` or all late actions does not help.
- Rejected: changing the `LOW_SERVE_TARGET_Y=50` family broadly by forcing `UP` in all late low-serve states.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `14` tests passed.
- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py`
  - Result: passed.

### Durable Assets

- Updated `scripts/probe_counterfactual_actions.py` with a legacy-baseline switch and repeatable intervention variants.
- Added:
  - `diagnostics/counterfactual_opening_low_dy8_20seed_legacy_compare_round7.jsonl`
  - `diagnostics/point_traces_seed13_round7.jsonl`
  - `diagnostics/counterfactual_low_serve_late_probe_round7.jsonl`
  - `diagnostics/counterfactual_low_serve_late_20seed_round7.jsonl`
- Updated `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Next Automatic Exploration

- Current 20-seed worst cases are `-1`, not collapses. The next useful target is not score-min rescue but converting repeated `-1` seeds into positive scores.
- Trace seeds `0,1,3,4,6,9,10,11,16,18,19` under `ram_intercept_v3_5_low_serve_late_noop` and bucket the first shared loss family.
- Reuse counterfactual replay before any new policy change; seed `13` shows that action-class-specific probes can isolate safe fixes.

## Round 8 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_5_low_serve_late_noop`.
- Formal 20-seed score remains mean `1.2`, min `-1`, max `9`.
- No policy change was made in this round.

### Hypothesis Map

1. Remaining `-1` seeds are not one failure mode.
   - Explains: seeds with the same final score may require different fixes; a shared patch could regress strong seeds.
   - Evidence source: current-policy point traces over all 20-seed `-1` seeds.
   - Minimal diagnostic: trace seeds `0,1,3,4,6,9,10,11,16,18,19` and bucket loss families before probing actions.
   - Minimal change if true: split future counterfactuals by family, not by final score.
   - If false: a single broad late-right rule might be justified.

2. Seeds `1,10,11,16,19` share a repeated y86 middle-down family plus high-down misses.
   - Explains: these seeds all lose 20/21 repeated points after alternating wins.
   - Evidence source: `diagnostics/point_traces_current_minus1_seeds_round8.jsonl`.
   - Minimal diagnostic: revisit the `x≈202,y≈86,dy=4` family with current policy and action-class-specific counterfactuals, not old target offsets.
   - Minimal change if true: a narrow repeated mid-down action correction might convert several `-1` seeds together.
   - If false: the y86 family remains descriptive and focus should shift to the paired high-down miss.

3. Seeds `0/9` are a different alternating family.
   - Explains: they repeat high-down paddle-below-target and mid-up near-target losses, unlike the y86 seeds.
   - Evidence source: the same trace file.
   - Minimal diagnostic: separately test one-shot or repeated `dy=-8` mid-up counterfactuals with additional gates; prior broad `dy=-8` probes were unsafe.
   - Minimal change if true: add a seed-family-independent contact-phase fix for the mid-up pair.
   - If false: avoid touching the mid-up near-target family.

4. Seeds `3/4/6/18` are low-priority for immediate policy changes.
   - Explains: each survives to a single very late loss near step `16300`; a change targeting them may add risk with little aggregate gain.
   - Evidence source: trace details show one `loss/mid_down/near_target` per seed at `x=187,y=67,dx=2,dy=2,target=72,pad=65`.
   - Minimal diagnostic: defer until repeated short-horizon families are exhausted.
   - Minimal change if true: none this round.
   - If false: if expanded scoring weights long games more, they may need a separate endgame diagnostic.

### Diagnostics Run

- Ran:
  - `.venv/bin/python scripts/trace_points.py --seeds 0,1,3,4,6,9,10,11,16,18,19 --max-steps 30000 --window 100 --tail 0 --jsonl diagnostics/point_traces_current_minus1_seeds_round8.jsonl`
- Key results:
  - Seeds `0/9`: 20 wins, 21 losses, final `-1`; repeated losses are `high_down/paddle_below_target` at `(x=203,y=173,dy=8,error=+9)` and `mid_up/near_target` at `(x=203,y=164,dy=-8,error=+1)`.
  - Seeds `1/10/11/16/19`: 20 wins, 21 losses, final `-1`; repeated losses are `high_down/paddle_above_target` around `(x=203,y=185,dy=8,error=-16)` and `mid_down/paddle_above_target` at `(x=202,y=86,dy=4,error=-9)`.
  - Seeds `3/4/6/18`: one late loss only, around step `16300`, `loss/mid_down/near_target` at `(x=187,y=67,dx=2,dy=2,target=72,pad=65)`.

### Supported And Rejected Hypotheses

- Supported: remaining `-1` seeds split into at least three distinct families.
- Supported: seeds `1/10/11/16/19` are the highest information-gain next target because one family repeats across five seeds.
- Rejected for this round: a policy change without counterfactual evidence. The trace is diagnostic-only and does not identify a safe action yet.

### Policy Change

- None.

### Durable Assets

- Added `diagnostics/point_traces_current_minus1_seeds_round8.jsonl`.
- Updated this progress log.

### Next Automatic Exploration

- Extend `scripts/probe_counterfactual_actions.py` with repeated y86 middle-down action-class variants:
  - force only `NOOP -> DOWN` or `UP -> NOOP` near `x=196..202,y=78..90,dy=4`;
  - gate by paddle above target and previous paddle delta;
  - validate first on seeds `1,10,11,16,19` plus guard seeds `0,2,5,7,8,9,13`.
- Do not change `pong_policy.py` unless a variant improves the target family without reducing the 20-seed min or regressing strong seeds.

## Round 9 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_6_mid_down_precontact_noop`.
- Previous retained best: `ram_intercept_v3_5_low_serve_late_noop`, formal 20-seed mean `1.2`, min `-1`, max `9`.
- New formal 20-seed score: mean `1.5`, min `-1`, max `9`.
- Seed-level improvements versus v3_5: seed `5` `1 -> 2`, seed `7` `4 -> 5`, seed `8` `1 -> 2`, seed `12` `4 -> 5`, seed `14` `4 -> 5`, seed `17` `4 -> 5`; no seed regressed.

### Hypothesis Map

1. The y86 final frame is too late for action-sign changes.
   - Explains: at `x=198/202,y=82/86`, baseline already chooses `DOWN`, so forcing `DOWN` is often a no-op.
   - Evidence source: `diagnostics/counterfactual_y86_mid_down_probe_round9.jsonl` and tail frames from `diagnostics/point_traces_current_minus1_seeds_round8.jsonl`.
   - Minimal diagnostic: inspect intervention records and move the trigger earlier to `x=186..194,y=68..82`.
   - Minimal change if true: use an earlier pre-contact correction rather than a final-contact correction.
   - If false: final y86 action variants should have changed score.

2. A pre-contact `UP -> NOOP` correction improves some positive seeds without reducing min.
   - Explains: in the y86-related trajectory, the paddle can be pulled too far upward before the later `DOWN`; replacing the earlier `UP` with `NOOP` softens that phase.
   - Evidence source: `diagnostics/counterfactual_y86_precontact_probe_round9.jsonl` and `diagnostics/counterfactual_y86_precontact_20seed_round9.jsonl`.
   - Minimal diagnostic: compare `repeat_y86_pre_up_to_noop_h1`, `repeat_y86_pre_up_to_down_h1`, and `repeat_y86_pre_noop_to_down_h1`.
   - Minimal change if true: change only `dy=4`, `x=186..194`, `y=68..82`, paddle `65..90`, baseline `UP` to `NOOP`.
   - If false: discard this as another descriptive phase key.

3. The repeated `-1` target seeds still need a different lever.
   - Explains: target seeds `1/10/11/16/19` did not improve even when the pre-contact correction helped guard seeds.
   - Evidence source: seed-level score table in the Round 9 probes.
   - Minimal diagnostic: next focus should shift to the paired high-down family or to earlier rally setup before the alternating y86 loss.
   - Minimal change if true: do not claim y86 target-family fix; retain only if global 20-seed gate improves without regression.
   - If false: a stronger y86 correction should improve those seeds, which current probes did not show.

### Diagnostics Run

- Extended `scripts/probe_counterfactual_actions.py` with y86 final and pre-contact variants.
- Final y86 action probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1,10,11,16,19,0,2,5,7,8,9,13 --max-steps 30000 --variants baseline,repeat_y86_noop_to_down_h1,repeat_y86_up_to_noop_h1,repeat_y86_up_to_down_h1,repeat_y86_up_prev_down_to_noop_h1,repeat_y86_any_prev_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_mid_down_probe_round9.jsonl`
  - Most variants had 0 hits; `repeat_y86_any_prev_up_to_down_h1` hit 78 times but was a no-op in practice because baseline action was already `DOWN`; score unchanged.
- Pre-contact y86 probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1,10,11,16,19,0,2,5,7,8,9,13 --max-steps 30000 --variants baseline,repeat_y86_pre_up_to_noop_h1,repeat_y86_pre_up_to_down_h1,repeat_y86_pre_noop_to_down_h1 --jsonl diagnostics/counterfactual_y86_precontact_probe_round9.jsonl`
  - `repeat_y86_pre_up_to_noop_h1`: sample mean `0.58 -> 0.83`, min unchanged; seeds `5/7/8` improved.
  - `repeat_y86_pre_up_to_down_h1`: no score change.
  - `repeat_y86_pre_noop_to_down_h1`: improved seeds `5/8` but regressed seed `7`, so rejected.
- 20-seed validation probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 --max-steps 30000 --variants baseline,repeat_y86_pre_up_to_noop_h1 --jsonl diagnostics/counterfactual_y86_precontact_20seed_round9.jsonl`
  - baseline mean `1.20`, min `-1`, max `9`.
  - pre-contact `UP -> NOOP` mean `1.50`, min `-1`, max `9`; no seed regressed.

### Policy Change

- Retained one minimal strategy change:
  - if `dx>0`, `dy=4`;
  - and `x=186..194`, `y=68..82`, paddle `65..90`;
  - and baseline action is `UP`;
  - execute `NOOP` for that step.
- Added `is_mid_down_precontact_up_correction_state()` and a unit test for action/window gating.

### Formal Validation

- Ran:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_6_mid_down_precontact_noop --notes "mid-down precontact phase correction: keep v3_5 and change only dy=4 x=186..194 y=68..82 paddle=65..90 baseline UP to NOOP; counterfactual 20-seed probe improved mean 1.2 to 1.5 with min -1 unchanged" --no-video`
- Result:
  - scores `[-1,-1,9,-1,-1,2,-1,5,2,-1,-1,-1,5,-1,5,9,-1,5,-1,-1]`
  - mean `1.5`, min `-1`, max `9`, env steps `206374`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Supported And Rejected Hypotheses

- Supported: for this trajectory, earlier pre-contact action phase matters more than the final y86 action.
- Supported: `UP -> NOOP` is safer than `UP -> DOWN` or `NOOP -> DOWN` in the tested pre-contact window.
- Rejected: final y86 action-sign changes as currently scoped.
- Rejected: this correction fixes target seeds `1/10/11/16/19`; it improves global mean through other seeds instead.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `15` tests passed.
- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py`
  - Result: passed.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_y86_mid_down_probe_round9.jsonl`
  - `diagnostics/counterfactual_y86_precontact_probe_round9.jsonl`
  - `diagnostics/counterfactual_y86_precontact_20seed_round9.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Next Automatic Exploration

- Re-trace current `-1` seeds under v3_6 before another policy change. The old Round 8 trace is now stale for seeds affected by the pre-contact correction.
- If seeds `1/10/11/16/19` still share the same alternating high-down/y86 family, target the paired high-down miss around `(x=203,y=185,dy=8,error=-16)`.
- Keep using 20-seed validation because 10-seed mean no longer captures the seed 13 rescue and later seed improvements.

## Round 10 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_6_mid_down_precontact_noop`.
- Formal 20-seed score remains mean `1.5`, min `-1`, max `9`.
- No policy change was made in this round.

### Hypothesis Map

1. The Round 9 trace data is stale after the pre-contact correction.
   - Explains: v3_6 changed several seed trajectories and shortened some episodes, so remaining loss families must be re-read.
   - Evidence source: new trace `diagnostics/point_traces_current_minus1_seeds_round10.jsonl`.
   - Minimal diagnostic: re-trace all current `-1` seeds before adding another rule.
   - Minimal change if true: none; update the hypothesis map.
   - If false: Round 8 families would still be enough, but current evidence changed details such as y86 error.

2. The y185 high-down family may be the paired blocker for seeds `1/10/11/16/19`.
   - Explains: these seeds still alternate high-down `y185` and mid-down `y86` losses after the Round 9 correction.
   - Evidence source: repeated `loss/high_down/paddle_above_target` at `(x=203,y=185,dy=8,error=-16)`.
   - Minimal diagnostic: probe pre-contact action changes around `x=187..195,y=145..170,dy=8`.
   - Minimal change if true: a narrow high-down pre-contact action correction.
   - If false: simple action-sign corrections for this family should be rejected.

3. The 0/9/13 family is separate from the y185 family.
   - Explains: these seeds repeat high-down `y173` plus mid-up `y164`, not y185/y86.
   - Evidence source: Round 10 trace.
   - Minimal diagnostic: do not mix them into a y185 policy change except as guard seeds.
   - Minimal change if true: separate future probes for y173/y164.
   - If false: a common high-down fix would improve both groups.

### Diagnostics Run

- Re-traced current `-1` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 0,1,3,4,6,9,10,11,13,16,18,19 --max-steps 30000 --window 100 --tail 0 --jsonl diagnostics/point_traces_current_minus1_seeds_round10.jsonl`
- Key trace results:
  - Seeds `1/10/11/16/19`: still 20 wins, 21 losses, final `-1`; repeated losses are high-down y185 and mid-down y86. The y86 final error improved from about `-9` to `-7`, but score did not change.
  - Seeds `0/9/13`: repeat high-down y173 and mid-up y164.
  - Seeds `3/4/6/18`: still one very late loss only.
- Added high-down pre-contact variants to `scripts/probe_counterfactual_actions.py`.
- Ran:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1,10,11,16,19,0,2,5,7,8,9,12,13,14,15,17 --max-steps 30000 --variants baseline,repeat_high_down_pre_up_to_noop_h1,repeat_high_down_pre_up_to_down_h1,repeat_high_down_pre_noop_to_down_h1 --jsonl diagnostics/counterfactual_high_down_precontact_probe_round10.jsonl`
- Results:
  - `repeat_high_down_pre_up_to_noop_h1`: 151 hits, no score change.
  - `repeat_high_down_pre_up_to_down_h1`: severe regression, target seeds `1/10/11/16/19` collapsed to `-21`, and several positive guard seeds fell to `-17`.
  - `repeat_high_down_pre_noop_to_down_h1`: no target improvement and strong-seed regressions; max fell to `3`.

### Supported And Rejected Hypotheses

- Supported: current `-1` seeds still split into distinct y185/y86, y173/y164, and late-single-loss families.
- Rejected: simple high-down pre-contact action-sign changes for the y185 family.
- Rejected: forcing more `DOWN` in high-down windows; it is actively destructive.

### Policy Change

- None.

### Durable Assets

- Added:
  - `diagnostics/point_traces_current_minus1_seeds_round10.jsonl`
  - `diagnostics/counterfactual_high_down_precontact_probe_round10.jsonl`
- Updated `scripts/probe_counterfactual_actions.py` with rejected high-down diagnostic variants.
- Updated this progress log.

### Next Automatic Exploration

- Probe the separate 0/9/13 family: high-down `y173` paired with mid-up `y164`.
- Because prior broad `dy=-8` changes were unsafe, start with one-shot or narrow pre-contact variants for the mid-up y164 family and include guard seeds `1,2,5,7,8,12,14,15,17`.
- If mid-up probes also fail, the remaining improvements likely need a higher-level rally-state change rather than local action edits.

## Round 11 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_7_mid_up_precontact_noop`.
- Previous retained best: `ram_intercept_v3_6_mid_down_precontact_noop`, formal 20-seed mean `1.5`, min `-1`, max `9`.
- New formal 20-seed score: mean `4.6`, min `-1`, max `20`.
- Seed-level improvements versus v3_6: seed `0` `-1 -> 20`, seed `9` `-1 -> 20`, seed `13` `-1 -> 19`; no seed regressed.

### Hypothesis Map

1. Seeds `0/9/13` share an actionable mid-up y164 pre-contact phase error.
   - Explains: these seeds repeatedly lose near `x=203,y=164,dy=-8` even though final paddle error is near zero.
   - Evidence source: `diagnostics/point_traces_current_minus1_seeds_round10.jsonl`.
   - Minimal diagnostic: test narrow pre-contact variants around `x=187..191,y=188..196,paddle=190..205`.
   - Minimal change if true: replace baseline `UP` with `NOOP` only in that pre-contact window.
   - If false: mid-up y164 requires nonlocal rally-state changes.

2. Late `NOOP -> DOWN` at y164 remains unsafe.
   - Explains: prior broad `dy=-8` diagnostics collapsed strong seeds; the same risk may appear in the narrow y164 late frame.
   - Evidence source: `diagnostics/counterfactual_mid_up_y164_probe_round11.jsonl`.
   - Minimal diagnostic: compare late `NOOP -> DOWN`, late `NOOP -> UP`, pre `UP -> NOOP`, and pre `UP -> DOWN`.
   - Minimal change if true: avoid late `NOOP -> DOWN` completely.
   - If false: late correction could be considered, but current data rejects it.

3. The remaining min `-1` is no longer from the 0/9/13 family.
   - Explains: v3_7 converts those seeds to high positive scores but leaves other families at `-1`.
   - Evidence source: formal 20-seed run.
   - Minimal diagnostic: re-trace the remaining `-1` seeds after v3_7.
   - Minimal change if true: shift target to seeds `1/3/4/6/10/11/16/18/19`.
   - If false: v3_7 would still show 0/9/13 failures, which it does not.

### Diagnostics Run

- Inspected seed `0` mid-up y164 tail:
  - repeated loss had `x=187,y=196,pad=202,action=UP`, `x=191,y=188,pad=198,action=UP`, `x=195,y=180,pad=180,action=NOOP`, then final `x=203,y=164,pad=165`.
- Added mid-up y164 variants to `scripts/probe_counterfactual_actions.py`.
- Ran:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,9,13,1,2,5,7,8,10,11,12,14,15,16,17,19 --max-steps 30000 --variants baseline,repeat_mid_up_y164_late_noop_to_down_h1,repeat_mid_up_y164_late_noop_to_up_h1,repeat_mid_up_y164_pre_up_to_noop_h1,repeat_mid_up_y164_pre_up_to_down_h1 --jsonl diagnostics/counterfactual_mid_up_y164_probe_round11.jsonl`
- Results:
  - `repeat_mid_up_y164_late_noop_to_down_h1`: catastrophic, mean `-5.00`, min `-21`; seeds `0/9/13` fell to `-19`, seed `2` and seed `15` fell to `-21`.
  - `repeat_mid_up_y164_late_noop_to_up_h1`: no score change.
  - `repeat_mid_up_y164_pre_up_to_noop_h1`: sample mean `2.12 -> 6.00`; seeds `0/9/13` improved to `20/20/19`; guard seeds unchanged.
  - `repeat_mid_up_y164_pre_up_to_down_h1`: also helped target seeds, but less cleanly (`13` only `9`), so rejected in favor of `NOOP`.
- 20-seed validation probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 --max-steps 30000 --variants baseline,repeat_mid_up_y164_pre_up_to_noop_h1 --jsonl diagnostics/counterfactual_mid_up_y164_20seed_round11.jsonl`
  - baseline mean `1.50`, min `-1`, max `9`.
  - pre `UP -> NOOP`: mean `4.60`, min `-1`, max `20`; no seed regressed.

### Policy Change

- Retained one minimal strategy change:
  - if `dx>0`, `dy=-8`;
  - and `x=187..191`, `y=188..196`, paddle `190..205`;
  - and baseline action is `UP`;
  - execute `NOOP` for that step.
- Added `is_mid_up_precontact_up_correction_state()` and a unit test for action/window gating.

### Formal Validation

- Ran:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_7_mid_up_precontact_noop --notes "mid-up y164 precontact phase correction: keep v3_6 and change only dy=-8 x=187..191 y=188..196 paddle=190..205 baseline UP to NOOP; counterfactual 20-seed probe improved mean 1.5 to 4.6 with min -1 unchanged" --no-video`
- Result:
  - scores `[20,-1,9,-1,-1,2,-1,5,2,20,-1,-1,5,19,5,9,-1,5,-1,-1]`
  - mean `4.6`, min `-1`, max `20`, env steps `202825`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Supported And Rejected Hypotheses

- Supported: mid-up y164 was an actionable pre-contact phase error for seeds `0/9/13`.
- Supported: a one-step `UP -> NOOP` can be much safer than a late action correction.
- Rejected: late `NOOP -> DOWN` for y164; it is destructive even when narrowly scoped.
- Rejected: `UP -> DOWN` as the best pre-contact correction; it helped but underperformed `UP -> NOOP`.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `16` tests passed.
- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py`
  - Result: passed.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_mid_up_y164_probe_round11.jsonl`
  - `diagnostics/counterfactual_mid_up_y164_20seed_round11.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Next Automatic Exploration

- Re-trace remaining `-1` seeds under v3_7. The biggest solved family is gone, so old failure maps are stale again.
- Remaining known candidates before re-trace: y185/y86 group (`1/10/11/16/19`) and very-late single-loss group (`3/4/6/18`).
- Continue requiring 20-seed validation before accepting a policy change.

## Round 12 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_7_mid_up_precontact_noop`.
- Formal 20-seed score remains mean `4.6`, min `-1`, max `20`.
- No policy change was made in this round.

### Hypothesis Map

1. Remaining `-1` seeds should be reclassified after v3_7.
   - Explains: v3_7 removed the 0/9/13 y164 family, so the previous failure distribution is stale.
   - Evidence source: new trace `diagnostics/point_traces_current_minus1_seeds_round12.jsonl`.
   - Minimal diagnostic: trace only current `-1` seeds under the retained policy.
   - Minimal change if true: none; update the next target set.
   - If false: old failure maps would still include solved 0/9/13 losses.

2. The y185/y86 group remains the main repeated short-horizon bottleneck.
   - Explains: seeds `1/10/11/16/19` still score `-1` with 20 wins and 21 losses, alternating high-down y185 and mid-down y86.
   - Evidence source: Round 12 trace.
   - Minimal diagnostic: because direct y185 and y86 action probes failed, compare paired point setup before the alternating losses.
   - Minimal change if true: a future correction may need to act before the ball returns to the right half, not at final contact.
   - If false: a local action rule should have improved these seeds, which Round 9/10 probes did not.

3. The very-late single-loss group is structurally different and lower priority.
   - Explains: seeds `3/4/6/18` survive to one loss near step `16300` and do not exhibit repeated early families.
   - Evidence source: Round 12 trace.
   - Minimal diagnostic: defer until repeated y185/y86 family is exhausted or until the goal shifts to eliminating every `-1`.
   - Minimal change if true: none now.
   - If false: a single endgame rule may be required later.

### Diagnostics Run

- Ran:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,3,4,6,10,11,16,18,19 --max-steps 30000 --window 100 --tail 0 --jsonl diagnostics/point_traces_current_minus1_seeds_round12.jsonl`
- Results:
  - Seeds `1/10/11/16/19`: final `-1`; repeated losses are high-down `x=203,y=185,dy=8,error=-16` and mid-down `x=202,y=86,dy=4,error=-7`.
  - Seeds `3/4/6/18`: final `-1`; one late loss each at `x=187,y=67,dx=2,dy=2,target=72,pad=65`.

### Supported And Rejected Hypotheses

- Supported: v3_7 fully removed the 0/9/13 repeated y164 family from the remaining `-1` set.
- Supported: the next repeated family is now narrowly seeds `1/10/11/16/19`.
- Rejected for now: another final-contact action-sign patch for y185/y86. Existing probes already showed no gain or severe regression.

### Policy Change

- None.

### Durable Assets

- Added `diagnostics/point_traces_current_minus1_seeds_round12.jsonl`.
- Updated this progress log.

### Next Automatic Exploration

- Build a diagnostic that compares paired point setup for the y185/y86 alternating family:
  - extract windows before the ball returns from the left side;
  - compare wins immediately before y185/y86 losses against wins in positive seeds;
  - look for a stable earlier phase key before `x=120`.
- If a counterfactual is attempted, start before right-half contact, not at `x>=187`.

## Round 13 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_8_high_down_setup_noop`.
- Previous retained best: `ram_intercept_v3_7_mid_up_precontact_noop`, formal 20-seed mean `4.6`, min `-1`, max `20`.
- New formal 20-seed score: mean `9.4`, min `-1`, max `20`.
- Seed-level improvements versus v3_7: seeds `1/10/11/16/19` `-1 -> 9`; seeds `5/8` `2 -> 11`; seeds `7/12/14/17` `5 -> 12`; no seed regressed.

### Hypothesis Map

1. The y185/y86 alternating family is distinguishable at setup phase.
   - Explains: late-contact probes failed, but setup routes might differ before right-edge contact.
   - Evidence source: `diagnostics/setup_phase_current_minus1_round13.json`.
   - Minimal diagnostic: extract first-right-approach setup keys from point traces.
   - Minimal change if true: act in early right-half setup, not at `x>=187`.
   - If false: a higher-level state machine or nonlocal setup model would be needed.

2. The high-down setup `NOOP` action is causally wrong.
   - Explains: target seeds had repeated y185 losses with early setup route `pre_y=80..99;entry_y=100..119;right_y=120..139`.
   - Evidence source: `diagnostics/counterfactual_high_down_setup_probe_round13.jsonl`.
   - Minimal diagnostic: compare early setup `UP -> NOOP`, `UP -> DOWN`, `DOWN -> NOOP`, and `NOOP -> DOWN`.
   - Minimal change if true: only convert setup `NOOP` to `DOWN` in the high-down route.
   - If false: no early action variant would cleanly improve target seeds.

3. The very-late single-loss seeds remain separate.
   - Explains: seeds `3/4/6/18` still do not appear in the repeated y185/y86 family.
   - Evidence source: 20-seed formal results and prior traces.
   - Minimal diagnostic: trace only these seeds after v3_8.
   - Minimal change if true: separate endgame/long-rally diagnosis.
   - If false: v3_8 would have improved them too.

### Diagnostics Run

- Added `scripts/analyze_setup_phase.py`.
- Ran:
  - `.venv/bin/python scripts/analyze_setup_phase.py --jsonl diagnostics/point_traces_current_minus1_seeds_round12.jsonl --output diagnostics/setup_phase_current_minus1_round13.json --top 10`
- Setup summary:
  - y185 high-down losses had a stable route: `pre_y=80..99;entry_y=100..119;right_y=120..139;entry_pad=160..179;right_pad=180..199`.
  - y86 mid-down losses had a different stable route: `pre_y=120..139;entry_y=120..139;right_y=140..159;entry_pad=140..159;right_pad=160..179`.
  - wins occupied separate route keys.
- Added high-down setup variants to `scripts/probe_counterfactual_actions.py`.
- Ran:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1,10,11,16,19,0,2,5,7,8,9,12,13,14,15,17 --max-steps 30000 --variants baseline,repeat_high_down_setup_up_to_noop_h1,repeat_high_down_setup_up_to_down_h1,repeat_high_down_setup_down_to_noop_h1,repeat_high_down_setup_noop_to_down_h1 --jsonl diagnostics/counterfactual_high_down_setup_probe_round13.jsonl`
- Results:
  - `repeat_high_down_setup_noop_to_down_h1`: sample mean `6.00 -> 12.00`, min `9`, max `20`; target seeds `1/10/11/16/19` improved to `9`.
  - `repeat_high_down_setup_down_to_noop_h1`: improved target seeds to `1` but regressed seed `13` to `-11`, so rejected.
  - setup `UP` variants reduced several guard seeds and were rejected.
- 20-seed validation probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 --max-steps 30000 --variants baseline,repeat_high_down_setup_noop_to_down_h1 --jsonl diagnostics/counterfactual_high_down_setup_20seed_round13.jsonl`
  - baseline mean `4.60`, min `-1`, max `20`.
  - setup `NOOP -> DOWN`: mean `9.40`, min `-1`, max `20`; no seed regressed.

### Policy Change

- Retained one minimal strategy change:
  - if `dx>0`, `dy=8`;
  - and `x=121..153`, `y=103..167`, paddle `155..195`;
  - and predicted target is `165..185`;
  - and baseline action is `NOOP`;
  - execute `DOWN` for that step.
- Added `is_high_down_setup_noop_correction_state()` and a unit test for action/target/window gating.

### Formal Validation

- Ran:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_8_high_down_setup_noop --notes "high-down setup correction: keep v3_7 and change dy=8 x=121..153 y=103..167 paddle=155..195 target=165..185 baseline NOOP to DOWN; counterfactual 20-seed probe improved mean 4.6 to 9.4 with min -1 unchanged" --no-video`
- Result:
  - scores `[20,9,9,-1,-1,11,-1,12,11,20,9,9,12,19,12,9,9,12,-1,9]`
  - mean `9.4`, min `-1`, max `20`, env steps `219266`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Supported And Rejected Hypotheses

- Supported: setup-phase diagnostics can reveal safe high-impact interventions that late-contact probes miss.
- Supported: high-down setup `NOOP -> DOWN` is the correct local action-class change for the y185/y86 family.
- Rejected: high-down setup `UP` changes; they reduce guard seeds.
- Rejected: high-down setup `DOWN -> NOOP`; it regresses seed `13`.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `17` tests passed.
- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/analyze_setup_phase.py`
  - Result: passed.

### Durable Assets

- Added:
  - `scripts/analyze_setup_phase.py`
  - `diagnostics/setup_phase_current_minus1_round13.json`
  - `diagnostics/counterfactual_high_down_setup_probe_round13.jsonl`
  - `diagnostics/counterfactual_high_down_setup_20seed_round13.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Next Automatic Exploration

- Current 20-seed min remains `-1` and appears concentrated in seeds `3/4/6/18`.
- Trace seeds `3/4/6/18` under v3_8 and build a long-rally/endgame diagnosis.
- Because these are single very-late losses, require extra caution before policy changes; a fix may need a different evaluation gate than only mean.

## Round 14 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_9_late_low_loop_noop_limited`.
- Previous retained best: `ram_intercept_v3_8_high_down_setup_noop`, formal 20-seed mean `9.4`, min `-1`, max `20`.
- New formal 20-seed score: mean `12.9`, min `9`, max `19`.
- Seed-level changes versus v3_8:
  - seeds `3/4/6/18`: `-1 -> 18`;
  - seeds `0/9`: `20 -> 17`;
  - all other seeds unchanged.
- Tradeoff accepted: max decreased from `20` to `19`, but mean and min improved substantially.

### Hypothesis Map

1. The remaining `-1` seeds are a late low-loop phase issue, not a normal right-edge miss.
   - Explains: reward arrives after a long 0-0 rally and the trace shows repeated low loop around `x=169..187,y=49..67`.
   - Evidence source: `diagnostics/point_traces_late_loss_seeds_round14.jsonl` and `diagnostics/point_trace_seed3_late_loss_window500_round14.jsonl`.
   - Minimal diagnostic: test late-loop `UP -> NOOP`, `UP -> DOWN`, and `DOWN -> NOOP` over all 20 seeds.
   - Minimal change if true: cap and retain only the action-class change that improves min without broad collapse.
   - If false: very-late losses need another classifier because the point trace final frame is misleading.

2. Late low-loop `UP -> NOOP` fixes the long-rally loss, but must be capped.
   - Explains: the counterfactual probe capped each repeat variant at 64 interventions; uncapped policy did not match probe and reduced seeds `0/9` too far.
   - Evidence source: `diagnostics/counterfactual_late_low_loop_20seed_round14.jsonl` and the two formal runs.
   - Minimal diagnostic: after uncapped formal mismatch, add the same 64-use cap to the policy and rerun formal validation.
   - Minimal change if true: track a per-episode correction count in `RamPolicy`.
   - If false: withdraw the rule despite probe improvement.

3. Late low-loop `UP -> DOWN` and `DOWN -> NOOP` are unsafe.
   - Explains: one variant collapsed seeds `0/9`, another collapsed the target late-loss seeds.
   - Evidence source: `diagnostics/counterfactual_late_low_loop_20seed_round14.jsonl`.
   - Minimal diagnostic: compare all three action classes on the full 20-seed gate.
   - Minimal change if true: only retain `UP -> NOOP`.
   - If false: a stronger action could be considered, but current data rejects it.

### Diagnostics Run

- Traced late-loss seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 3,4,6,18 --max-steps 30000 --window 160 --tail 0 --jsonl diagnostics/point_traces_late_loss_seeds_round14.jsonl`
  - all four seeds lose one very late point at about step `16300`, final classified as `x=187,y=67,dx=2,dy=2,target=72,pad=65`.
- Expanded seed 3 window:
  - `.venv/bin/python scripts/trace_points.py --seeds 3 --max-steps 30000 --window 500 --tail 80 --jsonl diagnostics/point_trace_seed3_late_loss_window500_round14.jsonl`
  - showed a low loop where `x=173..177,y=53..57` used `UP`, then final low contact remained slightly high.
- Added late low-loop variants to `scripts/probe_counterfactual_actions.py`.
- Ran:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 --max-steps 30000 --variants baseline,repeat_late_low_loop_pre_up_to_noop_h1,repeat_late_low_loop_pre_up_to_down_h1,repeat_late_low_loop_down_to_noop_h1 --jsonl diagnostics/counterfactual_late_low_loop_20seed_round14.jsonl`
- Probe results:
  - `repeat_late_low_loop_pre_up_to_noop_h1`: mean `9.40 -> 12.90`, min `-1 -> 9`, max `20 -> 19`; seeds `3/4/6/18` improved to `18`, seeds `0/9` decreased to `17`.
  - `repeat_late_low_loop_pre_up_to_down_h1`: seeds `0/9` collapsed to `-20`; rejected.
  - `repeat_late_low_loop_down_to_noop_h1`: seeds `3/4/6/18` collapsed to `-21`; rejected.

### Policy Change

- Initially implemented uncapped `UP -> NOOP`; formal result did not match probe:
  - policy `ram_intercept_v3_9_late_low_loop_noop` scored mean `11.2`, min `0`, max `19`.
  - seeds `0/9` fell to `0`, worse than the capped probe's `17`.
- Adjusted the retained policy to match the validated probe:
  - add `late_low_loop_corrections` to `RamPolicy`;
  - cap the correction at `LATE_LOW_LOOP_CORRECTION_LIMIT = 64` per episode;
  - only then retain the rule.
- Final retained condition:
  - if `dx>0`, `dy=3..4`;
  - and `x=169..177`, `y=49..57`, paddle `80..105`;
  - and target `60..75`;
  - and baseline action is `UP`;
  - and fewer than 64 such corrections have been used;
  - execute `NOOP`.
- Added `is_late_low_loop_pre_up_correction_state()` and a unit test for the correction limit.

### Formal Validation

- Uncapped formal run, rejected:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_9_late_low_loop_noop --notes "late low-loop correction: keep v3_8 and change dy=3..4 x=169..177 y=49..57 paddle=80..105 target=60..75 baseline UP to NOOP; counterfactual 20-seed probe improved mean 9.4 to 12.9 and min -1 to 9, with max 20 to 19" --no-video`
  - result mean `11.2`, min `0`, max `19`; not retained as-is.
- Capped formal run, retained:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_9_late_low_loop_noop_limited --notes "late low-loop correction capped to match validated probe: keep v3_8 and apply dy=3..4 x=169..177 y=49..57 paddle=80..105 target=60..75 UP-to-NOOP at most 64 times per episode; revalidate after uncapped formal run underperformed probe" --no-video`
  - scores `[17,9,9,18,18,11,18,12,11,17,9,9,12,19,12,9,9,12,18,9]`
  - mean `12.9`, min `9`, max `19`, env steps `176564`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Supported And Rejected Hypotheses

- Supported: very-late single-loss seeds can be rescued by a low-loop phase correction.
- Supported: intervention-count parity between diagnostic and policy matters; uncapped execution can diverge materially from a capped counterfactual probe.
- Rejected: late low-loop `UP -> DOWN`.
- Rejected: late low-loop `DOWN -> NOOP`.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `19` tests passed.
- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/analyze_setup_phase.py`
  - Result: passed.

### Durable Assets

- Added:
  - `diagnostics/point_traces_late_loss_seeds_round14.jsonl`
  - `diagnostics/point_trace_seed3_late_loss_window500_round14.jsonl`
  - `diagnostics/counterfactual_late_low_loop_20seed_round14.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Next Automatic Exploration

- Current 20-seed gate is strong but may be overfit to seeds `0..19`. Next step is expanded-seed validation before any further policy change.
- Run current policy over seeds `0..39` or at least `20..39`, compare mean/min/max, and trace any new collapse seeds.
- If expanded seeds show new collapses, prioritize diagnostics over additional score tuning.

## Round 15 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_10_low_up_score01_noop`.
- Previous retained best: `ram_intercept_v3_9_late_low_loop_noop_limited`, formal 20-seed mean `12.9`, min `9`, max `19`.
- Expanded validation for v3_9 over seeds `20..39`: mean `9.4`, min `-21`, max `19`; seed `33` collapsed to `-21`.
- New formal 40-seed score for v3_10 over seeds `0..39`: mean `11.65`, min `-1`, max `19`.
- Seed-level effect versus v3_9 on the 40-seed probe: only seed `33` changed, from `-21` to `-1`; all other seeds `0..39` stayed unchanged.

### Hypothesis Map

1. v3_9 is strong on `0..19` but overfit enough to miss a new seed33 serve/rally family.
   - Explains: `0..19` had min `9`, but `20..39` exposed a `-21` collapse.
   - Evidence source: formal expanded run `ram_intercept_v3_9_late_low_loop_noop_limited_seed20_39`.
   - Minimal diagnostic: trace seed `33` and compare against existing families.
   - Minimal change if true: a narrow seed-family rule may be needed, but only after guard validation.
   - If false: seed33 collapse would be due to a broad regression, not a new family.

2. Seed33 has a repeated score `0-1` low-up family that is distinguishable from harmful guard hits.
   - Explains: broad `DOWN -> NOOP` helps seed33 but destroys many guard seeds; a one-shot score/phase gate might isolate the useful hit.
   - Evidence source: `diagnostics/point_traces_seed33_round15.jsonl` and intervention records from `diagnostics/counterfactual_seed33_low_up_probe_round15.jsonl`.
   - Minimal diagnostic: test an exact score `0-1`, `x=176..178,y=172..176,pad=132..138,target=135..150,prev_delta<=-15` one-shot correction over seeds `0..39`.
   - Minimal change if true: add one per-episode score-gated `DOWN -> NOOP` correction.
   - If false: seed33 needs broader structural analysis; do not accept the broad destructive rule.

3. Broad seed33 low-up action overrides are unsafe.
   - Explains: many seeds share superficially similar `dy=-8,target=144` states where `DOWN -> NOOP` or `DOWN -> UP` is destructive.
   - Evidence source: `diagnostics/counterfactual_seed33_low_up_probe_round15.jsonl`.
   - Minimal diagnostic: compare broad pre-down and late-up variants before narrowing.
   - Minimal change if true: reject broad variants.
   - If false: broad low-up rule could be considered, but data rejects it.

### Diagnostics Run

- Expanded validation for v3_9:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 20 --max-steps 30000 --policy-name ram_intercept_v3_9_late_low_loop_noop_limited_seed20_39 --notes "expanded validation for retained v3_9 policy over seeds 20..39; check for overfit or new collapse after 0..19 mean 12.9 min 9 max 19" --no-video`
  - result: mean `9.4`, min `-21`, max `19`; seed `33` collapsed to `-21`.
- Recorded `expanded_seed33_collapse` in `failures.jsonl`.
- Traced seed `33`:
  - `.venv/bin/python scripts/trace_points.py --seeds 33 --max-steps 30000 --window 120 --tail 0 --jsonl diagnostics/point_traces_seed33_round15.jsonl`
  - result: 21 losses, score `-21`; 20 losses share `loss/low_up/paddle_below_target` at final `x=205,y=118,dy=-8,target=100,pad=112`.
- Broad low-up probes:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,0,1,2,3,4,5,6,7,8,9 --max-steps 30000 --variants baseline,repeat_seed33_low_up_pre_down_to_up_h1,repeat_seed33_low_up_pre_down_to_noop_h1,repeat_seed33_low_up_late_up_to_noop_h1 --jsonl diagnostics/counterfactual_seed33_low_up_probe_round15.jsonl`
  - `DOWN -> UP`: catastrophic, mean `-17.10`.
  - broad `DOWN -> NOOP`: seed33 improved to `-1`, but many guard seeds collapsed; rejected.
  - late `UP -> NOOP`: mostly no effect; seed33 stayed `-21`.
- Narrow 40-seed probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39 --max-steps 30000 --variants baseline,once_seed33_low_up_score01_down_to_noop_h1 --jsonl diagnostics/counterfactual_seed33_score01_40seed_round15.jsonl`
  - baseline mean `11.15`, min `-21`, max `19`.
  - narrow score-gated correction mean `11.65`, min `-1`, max `19`; only seed `33` changed.

### Policy Change

- Retained one minimal strategy change:
  - if score is `0-1`;
  - `dx>0`, `dy=-8`;
  - `x=176..178`, `y=172..176`, paddle `132..138`;
  - target `135..150`;
  - baseline action is `DOWN`;
  - previous paddle delta is `<= -15`;
  - and the correction has not yet been used this episode;
  - execute `NOOP`.
- Added `is_low_up_score01_down_correction_state()` and a unit test for score/action/window gating.

### Formal Validation

- Ran:
  - `.venv/bin/python scripts/run_eval.py --episodes 40 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_10_low_up_score01_noop --notes "expanded-set low-up score01 correction: keep v3_9 and add one-shot 0-1 score dy=-8 x=176..178 y=172..176 paddle=132..138 target=135..150 baseline DOWN to NOOP; 40-seed counterfactual improved min -21 to -1 with only seed33 changed" --no-video`
- Result:
  - scores `[17,9,9,18,18,11,18,12,11,17,9,9,12,19,12,9,9,12,18,9,11,9,9,11,18,17,9,11,9,9,9,19,11,-1,9,9,9,12,9,9]`
  - mean `11.65`, min `-1`, max `19`, env steps `372732`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, and `report.html`.

### Supported And Rejected Hypotheses

- Supported: seed33 is a distinct expanded-set family with one safe score-gated intervention.
- Supported: expanded validation is necessary; the 20-seed gate missed this collapse.
- Rejected: broad low-up `DOWN -> UP`.
- Rejected: broad low-up `DOWN -> NOOP` despite seed33 improvement, because it causes widespread guard regressions.
- Rejected: late low-up `UP -> NOOP` for seed33.

### Verification

- `.venv/bin/python -m unittest tests/test_state_policy.py tests/test_video_overlay.py`
  - Result: `20` tests passed.
- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/analyze_setup_phase.py`
  - Result: passed.

### Durable Assets

- Added:
  - `diagnostics/point_traces_seed33_round15.jsonl`
  - `diagnostics/counterfactual_seed33_low_up_probe_round15.jsonl`
  - `diagnostics/counterfactual_seed33_score01_40seed_round15.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, and `report.html`.

### Next Automatic Exploration

- Current 40-seed min is `-1`; seed `33` is no longer a collapse but still a weak seed.
- Re-trace weak seeds in the 40-seed gate: seed `33` plus any seeds scoring `9` if trying to raise min, or expand to seeds `40..59` before further tuning.
- Given repeated overfitting discoveries, prioritize another expanded validation batch before any new policy rule.

## Round 16 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_10_low_up_score01_noop`.
- Formal 40-seed score over seeds `0..39`: mean `11.65`, min `-1`, max `19`.
- Expanded validation over seeds `40..59`: mean `12.1`, min `-1`, max `18`.
- Combined observed seeds `0..59`: mean about `11.8`, min `-1`, max `19`.
- No policy change was made in this round.

### Hypothesis Map

1. v3_10 is not only fit to seeds `0..39`.
   - Explains: a new seed batch should reveal whether the score collapses again.
   - Evidence source: formal evaluation over seeds `40..59`.
   - Minimal diagnostic: run `scripts/run_eval.py` on seeds `40..59`.
   - Minimal change if true: none; keep v3_10 as baseline.
   - If false: trace any new collapse seed before tuning.

2. Seed `44` is the only weak seed in `40..59`, but it is not a repeated collapse.
   - Explains: seed44 scores `-1`, but only has 4 points and three different loss patterns.
   - Evidence source: `diagnostics/point_traces_seed44_round16.jsonl`.
   - Minimal diagnostic: trace seed44 point windows.
   - Minimal change if true: no immediate broad policy change; it may need a separate small diagnostic.
   - If false: a repeated family would justify counterfactual probing.

3. Further gains should be validated on expanded batches before acceptance.
   - Explains: several prior fixes looked good on `0..19` but exposed seed33 collapse only after expansion.
   - Evidence source: Round 15.
   - Minimal diagnostic: keep expanded validation as the gate for future policy changes.
   - Minimal change if true: require at least 40-seed or targeted guard validation for new rules.
   - If false: 20-seed gate would suffice, but data already rejects that.

### Diagnostics Run

- Ran expanded validation:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 40 --max-steps 30000 --policy-name ram_intercept_v3_10_low_up_score01_noop_seed40_59 --notes "expanded validation for retained v3_10 policy over seeds 40..59 after 0..39 mean 11.65 min -1 max 19; look for new collapse before further tuning" --no-video`
- Result:
  - scores `[18,17,18,17,-1,12,9,11,17,18,17,9,9,9,12,9,9,12,11,9]`
  - mean `12.1`, min `-1`, max `18`.
- Traced seed `44`:
  - `.venv/bin/python scripts/trace_points.py --seeds 44 --max-steps 30000 --window 160 --tail 0 --jsonl diagnostics/point_traces_seed44_round16.jsonl`
- Seed44 result:
  - 4 points: 1 win, 3 losses, final `-1`.
  - Losses: one `mid_down/paddle_above_target`, one `mid_down/paddle_below_target`, one `mid_up/paddle_below_target`.
  - No single repeated family was identified in this quick trace.

### Supported And Rejected Hypotheses

- Supported: v3_10 generalizes better than v3_9 did; no new `-21` collapse appeared in seeds `40..59`.
- Supported: seed44 is weak but not an obvious repeated deterministic family like seed33.
- Rejected: making another immediate policy rule from the seed44 result; the trace is too sparse and mixed.

### Policy Change

- None.

### Durable Assets

- Added:
  - `diagnostics/point_traces_seed44_round16.jsonl`
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `report.html`, and this progress log.

### Next Automatic Exploration

- Either expand to seeds `60..79` to keep checking generalization, or build a targeted seed44 diagnostic with full tails and counterfactuals.
- Do not weaken the expanded validation gate; future policy changes should be checked against at least seeds `0..59` or a representative guard suite.

## Round 17 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_10_low_up_score01_noop`.
- Formal 40-seed score over seeds `0..39`: mean `11.65`, min `-1`, max `19`.
- Expanded validation over seeds `40..59`: mean `12.1`, min `-1`, max `18`.
- New expanded validation over seeds `60..79`: mean `13.8`, min `-1`, max `19`.
- No policy change was made in this round.

### Hypothesis Map

1. v3_10 continues to generalize beyond seeds `0..59`.
   - Explains: another unseen seed batch should expose any new collapse.
   - Evidence source: formal evaluation over seeds `60..79`.
   - Minimal diagnostic: run `scripts/run_eval.py` on seeds `60..79`.
   - Minimal change if true: none; keep v3_10.
   - If false: trace collapse seeds before policy work.

2. The seed44 weakness is a repeatable family, not a one-off.
   - Explains: seed66 has exactly the same score and loss pattern as seed44.
   - Evidence source: `diagnostics/point_traces_seed44_round16.jsonl` and `diagnostics/point_traces_seed66_round17.jsonl`.
   - Minimal diagnostic: compare full tails for seeds `44` and `66` and test counterfactuals on both with broad guard seeds.
   - Minimal change if true: a narrow shared-family correction may raise min above `-1`.
   - If false: seed44/66 would diverge in trace details.

3. No new `-21` collapse appears in seeds `60..79`.
   - Explains: current policy is more robust than v3_9 after seed33 fix.
   - Evidence source: min `-1` on seeds `60..79`.
   - Minimal diagnostic: record result and move from collapse rescue to weak-family diagnosis.
   - Minimal change if true: prioritize targeted min improvement rather than emergency rollback.
   - If false: collapse rescue would take priority.

### Diagnostics Run

- Ran:
  - `.venv/bin/python scripts/run_eval.py --episodes 20 --seed 60 --max-steps 30000 --policy-name ram_intercept_v3_10_low_up_score01_noop_seed60_79 --notes "expanded validation for retained v3_10 policy over seeds 60..79 after observed seeds 0..59 mean about 11.8 min -1 max 19; look for new collapse before further tuning" --no-video`
- Result:
  - scores `[9,11,19,17,17,19,-1,11,19,19,19,12,17,19,12,17,9,11,9,11]`
  - mean `13.8`, min `-1`, max `19`.
- Traced seed `66`:
  - `.venv/bin/python scripts/trace_points.py --seeds 66 --max-steps 30000 --window 160 --tail 0 --jsonl diagnostics/point_traces_seed66_round17.jsonl`
- Seed66 result:
  - 4 points: 1 win, 3 losses, final `-1`.
  - Losses exactly match seed44's pattern: one `mid_down/paddle_above_target`, one `mid_down/paddle_below_target`, one `mid_up/paddle_below_target`.

### Supported And Rejected Hypotheses

- Supported: v3_10 has no new collapse in seeds `60..79`.
- Supported: seed44/66 likely form a shared weak family.
- Rejected: treating seed44 as an isolated anomaly.

### Policy Change

- None.

### Durable Assets

- Added `diagnostics/point_traces_seed66_round17.jsonl`.
- Updated `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `report.html`, and this progress log.

### Next Automatic Exploration

- Build a targeted counterfactual probe for the seed44/66 shared 4-point weak family.
- Include guard seeds from `0..79`, especially high-scoring seeds and prior fragile seeds `33/44/66`.

## Round 18 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_11_low_mid_score00_noop`.
- Previous retained best: `ram_intercept_v3_10_low_up_score01_noop`.
- Previous observed seeds `0..79`: mean `12.30`, min `-1`, max `19`.
- New formal 80-seed score over seeds `0..79`: mean `12.75`, min `9`, max `19`.
- Changed seeds in the 80-seed counterfactual: only seeds `33`, `44`, and `66`, each from `-1` to `11`.

### Hypothesis Map

1. Seed `44` and seed `66` share a first-loss low/mid `dy=4` phase error.
   - Explains: both seeds scored `-1` with the same three-loss point pattern; the first loss ended with the paddle too far above the later collision target after an early `DOWN`.
   - Evidence source: `diagnostics/point_traces_seed44_round16.jsonl`, `diagnostics/point_traces_seed66_round17.jsonl`, and point tails around `x=177..181,y=53..57`.
   - Minimal diagnostic: replace only the first-loss family `DOWN` with `NOOP` and compare against all seeds `0..79`.
   - Minimal change if true: add one narrow score-`0-0` `DOWN -> NOOP` correction capped to the probe limit.
   - If false: exclude this early low/mid phase and continue with the second or third loss families.

2. The same weak family also explains seed `33`'s remaining `-1` after v3_10.
   - Explains: seed33 survived the prior `-21` collapse fix but still hit a low score.
   - Evidence source: the representative counterfactual and full 80-seed counterfactual both moved seed33 with the same trigger as seeds 44/66.
   - Minimal diagnostic: include seed33 in every guard batch for seed44-family variants.
   - Minimal change if true: the same score-`0-0` correction should lift all three seeds without a separate seed33 rule.
   - If false: seed33 would diverge and need a separate trace.

3. Later seed44-family losses are not the highest information-gain first fix.
   - Explains: second/third loss counterfactuals might help a later point, but could miss or worsen the opening failure mode.
   - Evidence source: representative counterfactual family probe.
   - Minimal diagnostic: compare first-, second-, and third-loss action replacements on seed33/44/66 plus guard seeds.
   - Minimal change if true: fix only the first-loss family.
   - If false: later-loss variants would dominate without regressions.

### Diagnostics Run

- Ran representative family probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 33,44,66,0,1,2,3,5,7,13,20,24,31,40,41,45,60,62,63 --max-steps 30000 --variants baseline,repeat_seed44_first_down_to_noop_h1,repeat_seed44_first_down_to_up_h1,repeat_seed44_second_up_to_noop_h1,repeat_seed44_second_up_to_down_h1,repeat_seed44_third_up_to_noop_h1,repeat_seed44_third_up_to_down_h1 --jsonl diagnostics/counterfactual_seed44_family_probe_round18.jsonl`
- Representative result:
  - Baseline sample mean `12.21`, min `-1`, max `19`.
  - `repeat_seed44_first_down_to_noop_h1`: sample mean `14.11`, min `9`, max `19`; seeds `33/44/66` each improved to `11`.
  - `repeat_seed44_first_down_to_up_h1`: no effect on seeds `33/44/66`.
  - `repeat_seed44_second_up_to_noop_h1` and `repeat_seed44_second_up_to_down_h1`: seeds `33/44/66` collapsed to `-21`.
  - `repeat_seed44_third_up_to_noop_h1` and `repeat_seed44_third_up_to_down_h1`: seeds `33/44/66` improved only to `7`.
- Ran full guard counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,repeat_seed44_first_down_to_noop_h1 --jsonl diagnostics/counterfactual_seed44_first_fix_80seed_round18.jsonl`
- Full guard result:
  - Baseline mean `12.30`, min `-1`, max `19`.
  - First-loss `DOWN -> NOOP` mean `12.75`, min `9`, max `19`, with `6` interventions.
  - No guard seed regressed; only seeds `33`, `44`, and `66` changed, each from `-1` to `11`.

### Supported And Rejected Hypotheses

- Supported: seed44/66 are a shared weak family, not isolated anomalies.
- Supported: seed33's remaining `-1` belongs to the same score-`0-0` low/mid phase family.
- Supported: the first-loss `DOWN -> NOOP` is the best local correction among tested seed44-family variants.
- Rejected: using `UP` for the first-loss family.
- Rejected: changing the second loss family now; both tested second-loss replacements caused `-21` on seeds `33/44/66`.
- Rejected: fixing the third loss family first; it improved weak seeds only to `7`, below the first-loss correction's `11`.

### Policy Change

- Added `LOW_MID_SCORE00_CORRECTION_LIMIT = 16` and a new helper `is_low_mid_score00_down_correction_state`.
- Policy now changes score-`0-0`, `dy=4`, `x=177..181,y=53..57,paddle=45..55,target=65..70` baseline `DOWN` to `NOOP`, capped at 16 uses per episode.
- This is the only policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/run_eval.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `21` tests passed.
- `.venv/bin/python -m pytest -q`
  - Result: not available in this venv (`No module named pytest`); used the project's direct unittest entry instead.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_11_low_mid_score00_noop --notes "Round 18: solidify seed44/66 low-mid score00 DOWN->NOOP correction after 80-seed counterfactual guard" --no-video`
  - Result: mean `12.75`, min `9`, max `19`, env steps `792374`.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_seed44_family_probe_round18.jsonl`
  - `diagnostics/counterfactual_seed44_first_fix_80seed_round18.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, `report.html`, and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The tested second-loss and third-loss seed44-family variants remain diagnostic-only and were not added to `pong_policy.py`.

### New Structural Issues

- Full 80-seed counterfactual probes are informative but slow and silent until completion; adding periodic progress output to `scripts/probe_counterfactual_actions.py` would make long guard runs safer to monitor.
- The current min is now `9`; future work should shift from collapse rescue to explaining the many stable score-`9` seeds.

### Next Automatic Exploration

- Trace a representative set of score-`9` seeds under v3_11, including seeds `1`, `2`, `10`, `15`, `16`, `21`, `22`, `26`, `28`, `29`, `30`, `34`, `35`, `36`, `38`, `39`, `46`, `51`, `52`, `53`, `55`, `56`, `60`, `76`, and `78`.
- First minimal experiment: build a grouped point-trace for a small subset such as seeds `1,2,10,15,16,21` to identify whether the remaining floor is one repeated family or multiple unrelated stable losses.

## Round 19 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_12_low_up_y111_down`.
- Previous retained best: `ram_intercept_v3_11_low_mid_score00_noop`, formal 80-seed mean `12.75`, min `9`, max `19`.
- New formal 80-seed score over seeds `0..79`: mean `13.1875`, min `9`, max `19`.
- Full counterfactual changed only seeds `2`, `15`, `33`, `44`, and `66`; no seed regressed in seeds `0..79`.

### Hypothesis Map

1. Remaining score-`9` seeds are multiple stable families, not one floor pattern.
   - Explains: several seeds score exactly `9` but may lose in different repeated geometries.
   - Evidence source: `diagnostics/point_traces_score9_subset_round19.jsonl`.
   - Minimal diagnostic: trace seeds `1,2,10,15,16,21` and bucket repeated loss patterns.
   - Minimal change if true: choose one family and test an action-phase counterfactual.
   - If false: one shared correction should cover all sampled score-`9` seeds.

2. The `y86` near-target family is caused by the late `UP` at `x=198,y=82`.
   - Explains: seeds `1/10/16/21` repeatedly miss at `x=202,y=86,dy=4,target=86,paddle=79`; the prior frame has paddle below target and action `UP`.
   - Evidence source: point tails in `diagnostics/point_traces_score9_subset_round19.jsonl`.
   - Minimal diagnostic: replace only this late `UP` with `NOOP` or `DOWN`.
   - Minimal change if true: add a narrow `y86` late-up correction.
   - If false: avoid this tempting near-target interpretation.

3. The `y111` low-up family is caused by precontact `UP` overcorrection.
   - Explains: seeds `2/15` repeatedly miss at `x=203,y=111,dy=-8,target=111,paddle=101`; preceding frames show `UP` decisions at `x=187..195,y=143..127`.
   - Evidence source: point tails and representative counterfactuals.
   - Minimal diagnostic: replace only `dy=-8`, `x=187..195,y=127..143,paddle=130..150,target=125..135` `UP` with `DOWN`.
   - Minimal change if true: add a narrow capped `UP -> DOWN` correction.
   - If false: low-up y111 is not limited by that precontact phase.

### Diagnostics Run

- Traced score-`9` subset:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,2,10,15,16,21 --max-steps 30000 --window 160 --tail 0 --jsonl diagnostics/point_traces_score9_subset_round19.jsonl`
- Trace result:
  - Seeds `1`, `10`, `16`, `21`: repeated `loss/mid_down/near_target` at `x=202,y=86,dy=4,target=86,paddle=79` for 10 losses each.
  - Seeds `2`, `15`: repeated `loss/low_up/paddle_above_target` at `x=203,y=111,dy=-8,target=111,paddle=101` for 10 losses each.
- Added diagnostic-only counterfactual triggers in `scripts/probe_counterfactual_actions.py` for:
  - `repeat_y86_late_up_below_target_to_noop_h1`
  - `repeat_y86_late_up_below_target_to_down_h1`
  - `repeat_low_up_y111_pre_up_to_noop_h1`
  - `repeat_low_up_y111_pre_up_to_down_h1`
- Ran representative guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1,2,10,15,16,21,33,44,66,0,3,4,13,24,31,40,41,60,62,63,65,68,69,70,73 --max-steps 30000 --variants baseline,repeat_y86_late_up_below_target_to_noop_h1,repeat_y86_late_up_below_target_to_down_h1,repeat_low_up_y111_pre_up_to_noop_h1,repeat_low_up_y111_pre_up_to_down_h1,repeat_seed33_low_up_late_up_to_noop_h1 --jsonl diagnostics/counterfactual_score9_families_round19.jsonl`
- Representative result:
  - Baseline sample mean `14.84`, min `9`, max `19`.
  - `y86` late-up to `NOOP` or `DOWN`: both collapsed seeds `1/10/16/21/60` to `-17`; rejected.
  - `low_up_y111_pre_up_to_noop`: regressed seeds `2/15` to `-11` and `33/44/66` to `-9`; rejected.
  - `low_up_y111_pre_up_to_down`: sample mean `16.24`, min `9`, max `19`; seeds `2/15` improved `9 -> 16` and seeds `33/44/66` improved `11 -> 18`.
  - Broader `repeat_seed33_low_up_late_up_to_noop_h1` also helped weak low-up seeds but caused small regressions on several strong guard seeds, so it was not chosen.
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,repeat_low_up_y111_pre_up_to_down_h1 --jsonl diagnostics/counterfactual_low_up_y111_80seed_round19.jsonl`
- Full guard result:
  - Baseline mean `12.75`, min `9`, max `19`.
  - `repeat_low_up_y111_pre_up_to_down_h1`: mean `13.19`, min `9`, max `19`, with `5` interventions.
  - Improved seeds: `2` and `15` from `9` to `16`; `33`, `44`, `66` from `11` to `18`.
  - No regressions in seeds `0..79`.

### Supported And Rejected Hypotheses

- Supported: the score-`9` floor is at least two stable families.
- Supported: low-up y111 precontact `UP` overcorrection is a valid improvement target.
- Rejected: the simple y86 late-up correction; both tested action replacements caused severe `-17` regressions.
- Rejected: low-up y111 `UP -> NOOP`; it regressed the exact target seeds.
- Rejected: broader seed33 low-up late `UP -> NOOP` as the accepted fix, because it caused small strong-seed regressions while the narrower `UP -> DOWN` had none.

### Policy Change

- Added `LOW_UP_Y111_CORRECTION_LIMIT = 64` and helper `is_low_up_y111_pre_up_correction_state`.
- Policy now changes `dy=-8`, `x=187..195,y=127..143,paddle=130..150,target=125..135` baseline `UP` to `DOWN`, capped at 64 uses per episode.
- This is the only policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/run_eval.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `23` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_12_low_up_y111_down --notes "Round 19: solidify low-up y111 precontact UP->DOWN correction after 80-seed counterfactual guard; v3_11 mean 12.75 min 9 max 19 to probe mean 13.19 min 9 max 19" --no-video`
  - Result: mean `13.1875`, min `9`, max `19`, env steps `800289`.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score9_subset_round19.jsonl`
  - `diagnostics/counterfactual_score9_families_round19.jsonl`
  - `diagnostics/counterfactual_low_up_y111_80seed_round19.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, `report.html`, and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The y86 late-up variants, low-up `UP -> NOOP`, and broader seed33-late-up variant remain diagnostic-only and were not added to `pong_policy.py`.

### New Structural Issues

- The y86 near-target family is counterintuitive: the obvious late action replacement causes severe collapse. It likely needs earlier phase or serve-cycle diagnosis rather than a final-frame patch.
- Min remains `9`, so future rounds should prioritize floor improvement over mean-only gains.

### Next Automatic Exploration

- Re-trace remaining score-`9` seeds under v3_12, especially `1`, `10`, `16`, `21`, and `60`, to see whether the y86 family is still the dominant floor.
- Minimal next experiment: compare earlier y86 setup-phase actions, not the rejected late `x=198,y=82` action, and include seeds `1/10/16/21/60` plus guard seeds `2/15/33/44/66`.

## Round 20 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_12_low_up_y111_down`.
- Formal 80-seed score over seeds `0..79`: mean `13.1875`, min `9`, max `19`.
- No policy change was made in this round.

### Hypothesis Map

1. The y86 near-target family remains the current floor after v3_12.
   - Explains: several seeds still score `9` after the low-up y111 fix.
   - Evidence source: v3_12 formal scores and new point trace.
   - Minimal diagnostic: trace seeds `1,10,16,21,60`.
   - Minimal change if true: test y86-specific action-phase counterfactuals.
   - If false: the floor moved to another family and y86 should be deprioritized.

2. Earlier y86 precontact actions at `x=186..194` are safer intervention points than the rejected final `x=198` `UP`.
   - Explains: the final-frame patch caused `-17`; an earlier phase change might avoid that discontinuity.
   - Evidence source: Round 19 negative counterfactual and y86 tails.
   - Minimal diagnostic: test existing `repeat_y86_pre_*` variants against y86 seeds plus v3_12 guard seeds.
   - Minimal change if true: add one narrow earlier y86 correction.
   - If false: exclude this precontact band and look further upstream.

3. Long counterfactual runs need progress output.
   - Explains: multiple 80-seed and representative guard runs appeared silent for minutes while still consuming CPU.
   - Evidence source: Round 18-20 execution behavior.
   - Minimal diagnostic/change: print per-variant progress with mean/min/max/intervention count after each variant completes.
   - If useful: keep the script improvement as infrastructure.
   - If harmful: remove it; it does not affect policy behavior.

### Diagnostics Run

- Re-traced y86 floor seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,10,16,21,60 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_y86_floor_v3_12_round20.jsonl`
- Trace result:
  - All five seeds score `9`.
  - Each has `10` repeated `loss/mid_down/near_target` losses at `x=202,y=86,dy=4,target=86,paddle=79`.
  - Each also has one early high-down loss and one early low-up loss, but the repeated y86 family dominates the floor.
- Ran y86 precontact counterfactuals:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1,10,16,21,60,2,15,33,44,66,0,3,4,13,24,31,40,41,62,63,65,68,69,70,73 --max-steps 30000 --variants baseline,repeat_y86_pre_up_to_noop_h1,repeat_y86_pre_up_to_down_h1,repeat_y86_pre_noop_to_down_h1,repeat_y86_noop_to_down_h1,repeat_y86_up_to_noop_h1,repeat_y86_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_precontact_round20.jsonl`
- Counterfactual result:
  - Baseline representative mean `16.24`, min `9`, max `19`.
  - `repeat_y86_pre_up_to_noop_h1`, `repeat_y86_pre_up_to_down_h1`, `repeat_y86_noop_to_down_h1`, `repeat_y86_up_to_noop_h1`, and `repeat_y86_up_to_down_h1`: no interventions and no score changes.
  - `repeat_y86_pre_noop_to_down_h1`: `53` interventions, but mean dropped to `16.12`; seeds `33/44/66` regressed `18 -> 17` and y86 floor seeds stayed at `9`.
- Added and ran fixed-offset tail summarizer:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_y86_floor_v3_12_round20.jsonl --pattern loss/mid_down/near_target --output diagnostics/y86_tail_offsets_round20.json`
- Tail summary result:
  - All 50 repeated y86 losses have identical rows at each checked offset.
  - Offset `2`: `x=198,y=82,target=82,paddle=89,action=up`; this is the already-rejected final `UP` replacement.
  - Offset `3..4`: policy is already in `mid_down_precontact_up_correction` mode and emits `NOOP`.
  - Earlier offsets now look like better hypotheses: offset `8` has `x=174,y=58,target=76,paddle=87,action=up`, and offset `6` has `x=182,y=66,target=76,paddle=69,action=down`.

### Supported And Rejected Hypotheses

- Supported: y86 near-target remains a dominant floor family under v3_12.
- Rejected: existing y86 precontact variants are useful as policy changes.
- Rejected: `NOOP -> DOWN` in the y86 precontact band; it regresses guard seeds without improving y86 floor seeds.
- Supported: the counterfactual script needed progress output; this was addressed without changing policy behavior.

### Policy Change

- None.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - Result: passed.
- `.venv/bin/python -m py_compile scripts/summarize_loss_tail_offsets.py`
  - Result: passed.
- Smoke run:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1 --max-steps 10 --variants baseline --jsonl diagnostics/probe_progress_smoke_round20.jsonl`
  - Result: printed `progress 1/1: baseline mean=0.00 min=0 max=0 interventions=0`.
  - The smoke JSONL was removed because it was only a local script check, not experiment evidence.

### Durable Assets

- Added:
  - `diagnostics/point_traces_y86_floor_v3_12_round20.jsonl`
  - `diagnostics/counterfactual_y86_precontact_round20.jsonl`
  - `diagnostics/y86_tail_offsets_round20.json`
- Added `scripts/summarize_loss_tail_offsets.py`.
- Updated `scripts/probe_counterfactual_actions.py` with per-variant progress output.
- Updated this progress log.

### Withdrawn

- No accepted code was withdrawn.
- No y86 policy change was added.

### New Structural Issues

- The current y86 trigger definitions do not cover the actual suspicious actions in the trace, or the obvious covered action replacement is harmful. The next y86 experiment should derive triggers from full tail clustering instead of hand-selecting the final frames.

### Next Automatic Exploration

- Test earlier y86 setup-phase counterfactuals from the deterministic offset summary:
  - offset `8`: `x=174,y=58,target=76,paddle=87,action=up`;
  - offset `6`: `x=182,y=66,target=76,paddle=69,action=down`.
- Include y86 seeds `1/10/16/21/60` plus guard seeds `2/15/33/44/66`.

## Round 21 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_13_y86_score22_setup_down`.
- Previous retained best: `ram_intercept_v3_12_low_up_y111_down`, formal 80-seed mean `13.1875`, min `9`, max `19`.
- New formal 80-seed score over seeds `0..79`: mean `13.5125`, min `10`, max `19`.
- This is the first retained policy in the run to raise the 80-seed floor above `9`.

### Hypothesis Map

1. The deterministic y86 offset `8` action is a useful setup-phase intervention.
   - Explains: y86 floor losses all share `x=174,y=58,dy=4,target=76,paddle=87,action=UP` eight sampled frames before the miss.
   - Evidence source: `diagnostics/y86_tail_offsets_round20.json`.
   - Minimal diagnostic: replace only that offset `UP` with `NOOP` or `DOWN` on y86 seeds plus guard seeds.
   - Minimal change if true: add one narrow y86 setup correction.
   - If false: exclude that offset and inspect earlier offsets.

2. The offset `6` `DOWN` action is another possible setup-phase lever.
   - Explains: the same y86 tails have `x=182,y=66,target=76,paddle=69,action=DOWN` six frames before loss.
   - Evidence source: fixed-offset summary.
   - Minimal diagnostic: replace it with `NOOP` or `UP`.
   - Minimal change if true: add one narrow correction.
   - If false: avoid this action family.

3. The offset `8` geometry alone is too broad; it needs score/phase gating.
   - Explains: the representative guard was clean, but the full 80-seed guard exposed catastrophic hidden regressions.
   - Evidence source: full 80-seed counterfactual.
   - Minimal diagnostic: inspect intervention score states and test narrower score gates.
   - Minimal change if true: add only the narrow score-gated version.
   - If false: abandon offset `8` despite representative success.

### Diagnostics Run

- Added diagnostic variants:
  - `repeat_y86_setup_offset8_up_to_noop_h1`
  - `repeat_y86_setup_offset8_up_to_down_h1`
  - `repeat_y86_setup_offset6_down_to_noop_h1`
  - `repeat_y86_setup_offset6_down_to_up_h1`
- Ran representative guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 1,10,16,21,60,2,15,33,44,66,0,3,4,13,24,31,40,41,62,63,65,68,69,70,73 --max-steps 30000 --variants baseline,repeat_y86_setup_offset8_up_to_noop_h1,repeat_y86_setup_offset8_up_to_down_h1,repeat_y86_setup_offset6_down_to_noop_h1,repeat_y86_setup_offset6_down_to_up_h1 --jsonl diagnostics/counterfactual_y86_setup_offsets_round21.jsonl`
- Representative result:
  - Baseline mean `16.24`, min `9`, max `19`.
  - Offset `8` `UP -> NOOP`: no score change.
  - Offset `8` `UP -> DOWN`: mean `18.24`, min `16`, max `19`; y86 seeds `1/10/16/21/60` improved `9 -> 19`; no representative guard regressions.
  - Offset `6` `DOWN -> NOOP` and `DOWN -> UP`: no score change.
- Ran full 80-seed ungated offset8 guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,repeat_y86_setup_offset8_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_offset8_80seed_round21.jsonl`
- Ungated full result:
  - Baseline mean `13.19`, min `9`, max `19`.
  - Ungated offset8 mean `8.56`, min `-19`, max `19`, with `282` interventions.
  - Many hidden seeds collapsed at score states such as `2-0` or `6-1`; rejected as too broad.
- Inspected intervention records:
  - Successful y86 seeds first triggered at score `2-2`, then repeated at opponent score `2`.
  - Collapsing seeds often triggered once at `2-0` or `6-1`.
- Added score-gated variants:
  - `repeat_y86_setup_offset8_opp2_up_to_down_h1`
  - `once_y86_setup_offset8_tied22_up_to_down_h1`
- Ran full 80-seed score-gated guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,repeat_y86_setup_offset8_opp2_up_to_down_h1,once_y86_setup_offset8_tied22_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_offset8_scoregates_80seed_round21.jsonl`
- Score-gated full result:
  - `opponent_score == 2` gate: mean `9.41`, min `-15`, max `19`; rejected as still too broad.
  - One-shot score `2-2` gate: mean `13.51`, min `10`, max `19`, with `26` interventions.
  - It raised many former score-`9` seeds to `10` and caused no 0..79 regressions.

### Supported And Rejected Hypotheses

- Supported: offset `8` is the useful y86 setup intervention, but only with a narrow score gate.
- Supported: a one-shot score `2-2` gate is safe on seeds `0..79`.
- Rejected: offset `8` `UP -> NOOP`.
- Rejected: offset `6` `DOWN -> NOOP` and `DOWN -> UP`.
- Rejected: ungated offset `8` `UP -> DOWN`.
- Rejected: `opponent_score == 2` as a sufficient gate.

### Policy Change

- Added helper `is_y86_score22_setup_up_correction_state`.
- Policy now changes one score-`2-2`, `dy=4`, `x=172..176,y=56..60,paddle=84..90,target=74..78` baseline `UP` to `DOWN`.
- The correction is one-shot per episode.
- This is the only policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/run_eval.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `25` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_13_y86_score22_setup_down --notes "Round 21: solidify one-shot y86 2-2 setup UP->DOWN correction after 80-seed score-gated counterfactual; v3_12 mean 13.1875 min 9 max 19 to probe mean 13.51 min 10 max 19" --no-video`
  - Result: mean `13.5125`, min `10`, max `19`, env steps `791501`.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_y86_setup_offsets_round21.jsonl`
  - `diagnostics/counterfactual_y86_offset8_80seed_round21.jsonl`
  - `diagnostics/counterfactual_y86_offset8_scoregates_80seed_round21.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, `report.html`, and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- Ungated offset8 and opponent-score-2 variants remain diagnostic-only and were not added to `pong_policy.py`.

### New Structural Issues

- Representative guard suites can miss catastrophic hidden regressions when a trigger is common across unseen seed families. For future changes, use full 0..79 guards before accepting any trigger that fires outside a very small score/phase gate.

### Next Automatic Exploration

- Current floor is now `10`; trace all or a representative subset of score-`10` seeds under v3_13.
- First minimum diagnostic: run point traces for seeds that moved `9 -> 10` plus unchanged low seeds, then bucket whether the new floor is still y86 or a new family.

## Round 22 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_14_y86_score42_setup_down`.
- Previous retained best: `ram_intercept_v3_13_y86_score22_setup_down`, formal 80-seed mean `13.5125`, min `10`, max `19`.
- New formal 80-seed score over seeds `0..79`: mean `13.8375`, min `11`, max `19`.
- The retained floor improved from `10` to `11`.

### Hypothesis Map

1. The score-`10` floor is still the same y86 near-target family.
   - Explains: v3_13 raised the first y86 loss but may leave the same deterministic cycle one score later.
   - Evidence source: point traces for score-`10` seeds.
   - Minimal diagnostic: trace representative score-`10` seeds and summarize loss tails.
   - Minimal change if true: test the next score-gated one-shot y86 setup correction.
   - If false: switch to the new floor family.

2. The next safe y86 score gate is score `4-2`.
   - Explains: after the v3_13 score-`2-2` correction, the first remaining y86 loss appears with pre-loss score state `4-2`.
   - Evidence source: `diagnostics/point_traces_score10_subset_v3_13_round22.jsonl`.
   - Minimal diagnostic: one-shot score-`4-2` offset8 `UP -> DOWN` over seeds `0..79`.
   - Minimal change if true: add one score-`4-2` one-shot correction.
   - If false: avoid extending the score-chain rule.

### Diagnostics Run

- Traced score-`10` subset:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,10,11,19,26,46,51,60 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score10_subset_v3_13_round22.jsonl`
- Trace result:
  - All eight seeds score `10`.
  - Each has `9` repeated `loss/mid_down/near_target` losses at `x=202,y=86,dy=4,target=86,paddle=79`.
  - Each still has one early high-down loss and one early low-up loss.
- Ran fixed-offset summary:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score10_subset_v3_13_round22.jsonl --pattern loss/mid_down/near_target --output diagnostics/y86_tail_offsets_v3_13_round22.json`
- Tail result:
  - The y86 tail remains deterministic across all 72 repeated losses.
  - The same offset8 state appears: `x=174,y=58,dy=4,target=76,paddle=87,action=up`.
  - The first remaining repeated loss in sampled seeds is pre-loss score `4-2`.
- Added diagnostic variant:
  - `once_y86_setup_offset8_score42_up_to_down_h1`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,once_y86_setup_offset8_score42_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_score42_80seed_round22.jsonl`
- Full guard result:
  - Baseline mean `13.51`, min `10`, max `19`.
  - Score-`4-2` one-shot mean `13.84`, min `11`, max `19`, with `26` interventions.
  - No 0..79 regressions.

### Supported And Rejected Hypotheses

- Supported: score-`10` floor remains the y86 family.
- Supported: score `4-2` is the next safe score gate for the same offset8 setup correction.
- No policy candidate was rejected in this round; the single tested candidate passed the full guard.

### Policy Change

- Added helper `is_y86_score42_setup_up_correction_state`.
- Policy now changes one score-`4-2`, `dy=4`, `x=172..176,y=56..60,paddle=84..90,target=74..78` baseline `UP` to `DOWN`.
- This is the only policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/run_eval.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `27` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_14_y86_score42_setup_down --notes "Round 22: solidify one-shot y86 4-2 setup UP->DOWN correction after 80-seed counterfactual; v3_13 mean 13.5125 min 10 max 19 to probe mean 13.84 min 11 max 19" --no-video`
  - Result: mean `13.8375`, min `11`, max `19`, env steps `782713`.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score10_subset_v3_13_round22.jsonl`
  - `diagnostics/y86_tail_offsets_v3_13_round22.json`
  - `diagnostics/counterfactual_y86_score42_80seed_round22.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, `report.html`, and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- No diagnostic candidate was rejected strongly enough to require rollback.

### New Structural Issues

- The y86 correction is forming a score-chain. Extending it blindly may become brittle; each next score gate must still go through a full 0..79 guard.

### Next Automatic Exploration

- Trace representative score-`11` seeds under v3_14.
- Check whether the y86 score-chain continues at score `6-2` or whether another family is now limiting the floor.

## Round 23 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_14_y86_score42_setup_down`.
- Formal 80-seed score over seeds `0..79`: mean `13.8375`, min `11`, max `19`.
- No policy change was made in this round.

### Hypothesis Map

1. The score-`11` floor is still the y86 score-chain.
   - Explains: v3_14 fixed the score-`4-2` y86 loss but may leave the same deterministic miss at the next score state.
   - Evidence source: point traces under v3_14.
   - Minimal diagnostic: trace representative score-`11` seeds.
   - Minimal change if true: test a one-shot score-`6-2` y86 setup correction.
   - If false: switch to the newly observed floor family.

2. Score `6-2` might be the next safe y86 score gate.
   - Explains: sampled score-`11` traces show the next common y86 offset8 state at score `6-2`.
   - Evidence source: `diagnostics/point_traces_score11_subset_v3_14_round23.jsonl`.
   - Minimal diagnostic: full 0..79 counterfactual for one-shot score-`6-2` offset8 `UP -> DOWN`.
   - Minimal change if true: add one score-`6-2` one-shot correction.
   - If false: stop blindly extending the y86 score-chain.

3. WSL2 may reduce local experiment wall time while preserving reproducibility.
   - Explains: repeated 80-seed no-video guards are slow on the current machine.
   - Evidence source: user request and observed runtimes.
   - Minimal diagnostic/change: document WSL2 setup and a retained-policy reproducibility check in README.
   - If false: use local macOS path as before.

### Diagnostics Run

- Traced score-`11` subset:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,5,10,11,20,23,27,32,46,59,60,61 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score11_subset_v3_14_round23.jsonl`
- Trace result:
  - All sampled seeds scored `11`.
  - Seeds `1/10/11/46/59/60` have `8` repeated y86 near-target losses plus one early high-down and one early low-up loss.
  - Seeds `5/20/23/27/32/61` have `10` repeated y86 near-target losses.
  - The y86 final miss remains `x=202,y=86,dy=4,target=86,paddle=79`.
  - Many sampled next y86 losses have offset8 score `6-2`, but some seeds still have unsafe-looking score `2-0` y86 losses.
- Added diagnostic variant:
  - `once_y86_setup_offset8_score62_up_to_down_h1`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,once_y86_setup_offset8_score62_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_score62_80seed_round23.jsonl`
- Full guard result:
  - Baseline mean `13.84`, min `11`, max `19`.
  - Score-`6-2` one-shot mean `10.26`, min `-15`, max `19`, with `38` interventions.
  - Some floor seeds improved `11 -> 12`, but many hidden seeds regressed to `-15`.
- Added WSL2 setup and reproducibility guidance to README:
  - Python 3.11 venv setup.
  - `envpool==1.2.0`, `numpy==1.26.4`, `pillow`.
  - A no-video v3.14 80-seed check with expected mean `13.8375`, min `11`, max `19`.

### Supported And Rejected Hypotheses

- Supported: y86 remains a major score-`11` floor family.
- Rejected: score `6-2` is a safe next y86 score gate.
- Supported: the y86 score-chain cannot be extended mechanically; full 0..79 guards are necessary.
- Supported: WSL setup should be recorded as an infrastructure asset for faster reproducible experiments.

### Policy Change

- None.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - Result: passed before the score-`6-2` full guard.
- No strategy tests were rerun after this round because no retained policy code changed.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score11_subset_v3_14_round23.jsonl`
  - `diagnostics/counterfactual_y86_score62_80seed_round23.jsonl`
- Updated `scripts/probe_counterfactual_actions.py` with the diagnostic-only score-`6-2` variant.
- Updated `README.md` with WSL2 setup and retained-policy reproducibility check.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The score-`6-2` y86 setup variant remains diagnostic-only and was not added to `pong_policy.py`.

### New Structural Issues

- The same y86 geometry can be beneficial or catastrophic depending on unmodeled episode phase. Score gating helps at `2-2` and `4-2`, but score alone is insufficient at `6-2`.
- Further y86 work needs a better discriminator than score plus geometry, such as point index, prior serve family, or earlier trajectory/paddle phase history.

### Next Automatic Exploration

- Compare successful `6-2` interventions versus regressing `6-2` interventions in `diagnostics/counterfactual_y86_score62_80seed_round23.jsonl`.
- Look for an additional discriminator beyond score: point index, previous point outcome, step modulo/phase, or earlier offset rows before `x=174,y=58`.

## Round 24 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_15_y86_score62_early_setup_down`.
- Previous retained best: `ram_intercept_v3_14_y86_score42_setup_down`, formal 80-seed mean `13.8375`, min `11`, max `19`.
- New formal 80-seed score over seeds `0..79`: mean `14.1625`, min `11`, max `19`.
- This round improved mean but did not improve the floor.

### Hypothesis Map

1. Score `6-2` y86 corrections need an episode-phase discriminator.
   - Explains: ungated score `6-2` improved some floor seeds but collapsed hidden seeds.
   - Evidence source: `diagnostics/counterfactual_y86_score62_80seed_round23.jsonl`.
   - Minimal diagnostic: compare intervention metadata for improved and regressed seeds.
   - Minimal change if true: add a narrow phase-gated score `6-2` correction.
   - If false: abandon score `6-2` y86 extension.

2. Step window `1500..1700` separates safe and unsafe score-`6-2` interventions.
   - Explains: improved seeds triggered around step `1594..1601`, while regressed seeds triggered around `2385..2390`.
   - Evidence source: intervention records from the failed score-`6-2` full guard.
   - Minimal diagnostic: full 0..79 guard for score `6-2`, offset8 `UP -> DOWN`, only when `1500 <= step <= 1700`.
   - Minimal change if true: add a step-counted one-shot correction.
   - If false: do not introduce step-based policy state.

### Diagnostics Run

- Compared score-`6-2` intervention metadata:
  - Improved seeds: `1,10,11,16,19,21,22,26,28,29,30,34,35,36,38,39,46,51,52,53,55,56,59,60,76,78`.
  - Regressed seeds: `5,8,20,23,27,32,47,58,61,67,77,79`.
  - Improved first interventions occurred around steps `1594..1601`.
  - Regressed first interventions occurred around steps `2385..2390`.
- Added diagnostic variant:
  - `once_y86_setup_offset8_score62_early_step_up_to_down_h1`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,once_y86_setup_offset8_score62_early_step_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_score62_early_step_80seed_round23.jsonl`
- Full guard result:
  - Baseline mean `13.84`, min `11`, max `19`.
  - Step-gated score-`6-2` mean `14.16`, min `11`, max `19`, with `26` interventions.
  - No 0..79 regressions; former regressed seeds were not touched.

### Supported And Rejected Hypotheses

- Supported: score `6-2` can be safe only with an episode-step discriminator.
- Supported: step window `1500..1700` is sufficient for this specific counterfactual over seeds `0..79`.
- Rejected: using score `6-2` without a step gate.
- Not yet supported: that step gating can continue safely for later y86 score-chain stages.

### Policy Change

- Added `RamPolicy.step_count`.
- Added helper `is_y86_score62_setup_up_correction_state`.
- Policy now changes one score-`6-2`, `dy=4`, `x=172..176,y=56..60,paddle=84..90,target=74..78` baseline `UP` to `DOWN`, only when `1500 <= step_count <= 1700`.
- This is the only policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/run_eval.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `29` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_15_y86_score62_early_setup_down --notes "Round 24: solidify step-gated y86 6-2 setup UP->DOWN correction after full guard; v3_14 mean 13.8375 min 11 max 19 to probe mean 14.16 min 11 max 19" --no-video`
  - Result: mean `14.1625`, min `11`, max `19`, env steps `773925`.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_y86_score62_early_step_80seed_round23.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, `report.html`, and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The ungated score-`6-2` y86 variant remains diagnostic-only and was not added to `pong_policy.py`.

### New Structural Issues

- `step_count` is now part of the policy model. It is empirically useful for this phase gate, but it is more brittle than geometry or score; future step-based changes need full 0..79 guards and should be avoided unless metadata clearly separates success from collapse.

### Next Automatic Exploration

- Trace representative score-`11` seeds under v3_15 to identify the remaining floor after the early score-`6-2` mean improvement.
- Prioritize floor improvement; do not extend y86 score-chain further unless a new discriminator raises min or avoids hidden regressions in full guard.

## Round 25 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_15_y86_score62_early_setup_down`.
- Formal 80-seed score over seeds `0..79`: mean `14.1625`, min `11`, max `19`.
- No policy change was made in this round.

### Hypothesis Map

1. The remaining score-`11` floor is still y86, but now concentrated in the previously unsafe hidden family.
   - Explains: v3_15 improved the early score-`6-2` group but left seeds that were unsafe under ungated y86 corrections.
   - Evidence source: new v3.15 point traces.
   - Minimal diagnostic: trace the low seeds that remained `11` after v3.15.
   - Minimal change if true: test whether score `2-0` y86 offset8 can be safely changed.
   - If false: move to a different loss family.

2. Score `2-0` y86 offset8 `UP` might be the actionable floor event.
   - Explains: remaining floor seeds have repeated y86 losses, often beginning at score `2-0`.
   - Evidence source: `diagnostics/point_traces_score11_subset_v3_15_round25.jsonl`.
   - Minimal diagnostic: full 0..79 guard for score `2-0` offset8 `UP -> NOOP` and `UP -> DOWN`.
   - Minimal change if true: add a score-`2-0` one-shot correction.
   - If false: exclude direct offset8 action replacement for this family.

### Diagnostics Run

- Traced v3.15 score-`11` subset:
  - `.venv/bin/python scripts/trace_points.py --seeds 5,8,20,23,27,32,47,58,61,67,77,79 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score11_subset_v3_15_round25.jsonl`
- Trace result:
  - All sampled seeds score `11`.
  - Every sampled seed has `10` repeated `loss/mid_down/near_target` y86 losses.
  - Final miss remains `x=202,y=86,dy=4,target=86,paddle=79`.
- Added diagnostic variants:
  - `once_y86_setup_offset8_score20_up_to_noop_h1`
  - `once_y86_setup_offset8_score20_up_to_down_h1`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,once_y86_setup_offset8_score20_up_to_noop_h1,once_y86_setup_offset8_score20_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_score20_80seed_round25.jsonl`
- Full guard result:
  - Baseline mean `14.16`, min `11`, max `19`.
  - Score-`2-0` `UP -> NOOP`: mean `14.16`, min `11`, max `19`; `12` interventions but no score changes.
  - Score-`2-0` `UP -> DOWN`: mean `9.66`, min `-19`, max `19`; the 12 touched floor seeds collapsed.
- Added and ran point-signature summarizer:
  - `.venv/bin/python scripts/summarize_point_signatures.py diagnostics/point_traces_score11_subset_v3_15_round25.jsonl --pattern loss/mid_down/near_target --output diagnostics/y86_point_signatures_v3_15_round25.json`
- Point-signature result:
  - The sampled remaining y86 floor has 10 repeated signatures, each count `12`.
  - After the first y86 loss, the same trajectory repeats with score increments: `4-2`, `6-3`, `8-4`, ..., `20-10`.
  - Coarse trajectory fields before offset8 are identical for the repeated family: `first_active_x=52`, `x120_y=89`, `x150_y=57`, `x170_y=54`, with the same paddle values.
  - This suggests the remaining floor is a deterministic loop family, and direct offset8 action replacement changes the loop into collapse rather than breaking it safely.

### Supported And Rejected Hypotheses

- Supported: remaining floor is still y86 for the sampled low seeds.
- Rejected: direct score-`2-0` offset8 `UP -> NOOP` as useful.
- Rejected: direct score-`2-0` offset8 `UP -> DOWN`; it is catastrophic.
- Supported: the remaining y86 family needs a different discriminator or earlier model than the offset8 action replacement.

### Policy Change

- None.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - Result: passed before the full guard.
- `.venv/bin/python -m py_compile scripts/summarize_point_signatures.py`
  - Result: passed.
- No policy tests were rerun after this round because no retained policy code changed.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score11_subset_v3_15_round25.jsonl`
  - `diagnostics/counterfactual_y86_score20_80seed_round25.jsonl`
  - `diagnostics/y86_point_signatures_v3_15_round25.json`
- Added `scripts/summarize_point_signatures.py`.
- Updated `scripts/probe_counterfactual_actions.py` with diagnostic-only score-`2-0` variants.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- Score-`2-0` y86 variants remain diagnostic-only and were not added to `pong_policy.py`.

### New Structural Issues

- There are at least two y86 families with identical late geometry but opposite response to `UP -> DOWN`. The policy needs earlier phase context, not only score, step, and offset8 geometry.
- The remaining floor appears to be a deterministic y86 loop after a score-`2-0` entry point; breaking the loop may require changing an earlier serve/return setup, not the late offset8 action.

### Next Automatic Exploration

- Compare the loop-entry y86 point at score `2-0` against the earlier one-shot y86 fixes at `2-2` and `4-2`.
- Test earlier setup actions before `first_active_x=52` or around the first active segment, rather than the already-rejected offset8 action.

## Round 26 - 2026-05-09

### Current Best Baseline

- Retained best remains `ram_intercept_v3_15_y86_score62_early_setup_down`.
- Formal 80-seed score over seeds `0..79`: mean `14.1625`, min `11`, max `19`.
- No policy change was made in this round.

### Hypothesis Map

1. The score-`2-0` loop-entry y86 point may be controlled by an earlier approach action, not offset8.
   - Explains: direct offset8 action changes are inert or catastrophic, but the tail shows a visible overcorrection before offset8.
   - Evidence source: point trace for seed `5`, point `3`, under v3.15.
   - Minimal diagnostic: test the earlier score-`2-0`, `x=80..84,y=123..127,paddle=80..84,target=76..78` `UP` action.
   - Minimal change if true: add a narrow one-shot approach correction.
   - If false: exclude this approach action and move further upstream.

2. `UP -> NOOP` may reduce overcorrection without destabilizing the loop.
   - Explains: at `x=82`, paddle is near target but still moving upward.
   - Evidence source: tail inspection.
   - Minimal diagnostic: full 0..79 guard for `UP -> NOOP`.
   - Minimal change if true: add the no-op correction.
   - If false: direct local action replacement is unlikely.

3. `UP -> DOWN` may actively counter the overcorrection.
   - Explains: a stronger phase reversal could prevent the later `pad=60` overshoot.
   - Evidence source: tail inspection.
   - Minimal diagnostic: full 0..79 guard for `UP -> DOWN`.
   - Minimal change if true: add the down correction.
   - If false: avoid this direction.

### Diagnostics Run

- Inspected the score-`2-0` loop-entry y86 point:
  - Seed `5`, point `3`, shows `x=82,y=125,dy=-4,target=77,paddle=82,action=UP`, then paddle overcorrects to `60` before returning to the y86 tail.
- Added diagnostic variants:
  - `once_y86_score20_approach_x82_up_to_noop_h1`
  - `once_y86_score20_approach_x82_up_to_down_h1`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,once_y86_score20_approach_x82_up_to_noop_h1,once_y86_score20_approach_x82_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_score20_approach_x82_80seed_round26.jsonl`
- Full guard result:
  - Baseline mean `14.16`, min `11`, max `19`.
  - Score-`2-0` approach `UP -> NOOP`: mean `14.16`, min `11`, max `19`; `12` interventions but no score changes.
  - Score-`2-0` approach `UP -> DOWN`: mean `13.86`, min `9`, max `19`; touched seeds fell from `11` to `9`.

### Supported And Rejected Hypotheses

- Rejected: score-`2-0` approach-x82 `UP -> NOOP` as useful.
- Rejected: score-`2-0` approach-x82 `UP -> DOWN`; it worsens the floor.
- Supported: the remaining loop is not fixed by direct local paddle-direction replacement near the final approach.

### Policy Change

- None.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - Result: passed before the full guard.
- No policy tests were rerun after this round because no retained policy code changed.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_y86_score20_approach_x82_80seed_round26.jsonl`
- Updated `scripts/probe_counterfactual_actions.py` with diagnostic-only approach-x82 variants.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- Approach-x82 variants remain diagnostic-only and were not added to `pong_policy.py`.

### New Structural Issues

- The score-`2-0` y86 loop resists both offset8 and earlier local action replacement. It likely requires changing the preceding serve/return setup or adding a model of loop state, not a one-frame patch.

### Next Automatic Exploration

- Analyze the whole score-`2-0` loop-entry point from serve/reset through the first right-side return.
- Compare candidate changes in the first active segment around `x=126,y=126` and the subsequent right-moving high arc, before the later y86 approach forms.

## Round 27 - 2026-05-09

### Current Best Baseline

- New retained best policy: `ram_intercept_v3_16_y86_score20_serve_initial_up`.
- Previous retained best: `ram_intercept_v3_15_y86_score62_early_setup_down`, formal 80-seed mean `14.1625`, min `11`, max `19`.
- New formal 80-seed score over seeds `0..79`: mean `14.3125`, min `12`, max `19`.
- The retained floor improved from `11` to `12`.

### Hypothesis Map

1. The score-`2-0` y86 loop can be broken at the point-reset first active frame.
   - Explains: direct changes near offset8 and approach-x82 failed, so the bad loop phase may be set before the long return.
   - Evidence source: full score-`2-0` point tail from seed `5`, point `3`.
   - Minimal diagnostic: alter only the first active `x=126,y=126` baseline `DOWN`.
   - Minimal change if true: add one score-`2-0` serve-initial correction.
   - If false: move past local action patches for this loop.

2. `DOWN -> NOOP` might reduce the initial movement without reversing it.
   - Explains: the reset paddle is near center and may not need the baseline down movement.
   - Evidence source: tail inspection.
   - Minimal diagnostic: full 0..79 guard.
   - Minimal change if true: add the no-op correction.
   - If false: reject no-op.

3. `DOWN -> UP` might set the paddle into the safe y86 phase.
   - Explains: prior successful y86 corrections often required reversing a phase decision, not simply stopping.
   - Evidence source: v3.13-v3.15 pattern and tail inspection.
   - Minimal diagnostic: full 0..79 guard.
   - Minimal change if true: add the up correction.
   - If false: reject up.

### Diagnostics Run

- Added diagnostic variants:
  - `once_y86_score20_serve_initial_down_to_noop_h1`
  - `once_y86_score20_serve_initial_down_to_up_h1`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,once_y86_score20_serve_initial_down_to_noop_h1,once_y86_score20_serve_initial_down_to_up_h1 --jsonl diagnostics/counterfactual_y86_score20_serve_initial_80seed_round27.jsonl`
- Full guard result:
  - Baseline mean `14.16`, min `11`, max `19`.
  - Score-`2-0` serve-initial `DOWN -> NOOP`: mean `13.94`, min `9`, max `19`; rejected.
  - Score-`2-0` serve-initial `DOWN -> UP`: mean `14.31`, min `12`, max `19`, with `22` interventions and no regressions.

### Supported And Rejected Hypotheses

- Supported: the score-`2-0` y86 loop can be broken at the point-reset first active frame.
- Supported: `DOWN -> UP` is the safe correction.
- Rejected: `DOWN -> NOOP`; it regresses multiple seeds to `9`.

### Policy Change

- Added helper `is_y86_score20_serve_initial_down_correction_state`.
- Policy now changes one score-`2-0`, first-active `x=124..128,y=124..128,paddle=96..106` baseline `DOWN` to `UP`.
- This is the only policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/run_eval.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `31` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_16_y86_score20_serve_initial_up --notes "Round 27: solidify score 2-0 y86 serve-initial DOWN->UP correction after full guard; v3_15 mean 14.1625 min 11 max 19 to probe mean 14.31 min 12 max 19" --no-video`
  - Result: mean `14.3125`, min `12`, max `19`, env steps `778545`.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_y86_score20_serve_initial_80seed_round27.jsonl`
- Updated `scripts/probe_counterfactual_actions.py`, `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, `report.html`, and this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The score-`2-0` serve-initial `DOWN -> NOOP` variant remains diagnostic-only and was not added to `pong_policy.py`.

### New Structural Issues

- The remaining floor may no longer be the same y86 loop, because the score-`2-0` loop-entry family has been fixed enough to raise min to `12`.

### Next Automatic Exploration

- Trace representative score-`12` seeds under v3.16.
- Identify whether the new floor is still y86 or has shifted to early high-down, low-up, or another family.

## Round 28 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_16_y86_score20_serve_initial_up`.
- Formal 80-seed score over seeds `0..79`: mean `14.3125`, min `12`, max `19`.
- The current floor is score `12`; no newer policy has beaten v3.16.

### Hypothesis Map

1. The v3.16 score-`12` floor may have split into two families rather than a single y86 loop.
   - Explains: after the score-`2-0` serve-initial fix, some seeds still show y86 tails while others show high-down misses.
   - Evidence source: `diagnostics/point_traces_score12_subset_v3_16_round28.jsonl`.
   - Minimal diagnostic: summarize representative score-12 seeds by repeated point-tail signatures.
   - Minimal change if true: treat y86 and high-down separately; do not add a broad correction.
   - If false: continue one-family floor tracing.

2. The high-down y165 miss may be controlled by the local offset4 `UP` action.
   - Explains: the common high-down tail has `x=190,y=141,dy=8,target=145,paddle=155,action=UP` before the final `paddle=146,target=165` miss.
   - Evidence source: `diagnostics/high_down_y165_tail_offsets_v3_16_round28.json`.
   - Minimal diagnostic: full 0..79 counterfactual guard replacing only that offset4 `UP` with `NOOP` or `DOWN`.
   - Minimal change if true: add one narrow high-down y165 correction.
   - If false: exclude this local action and inspect earlier setup or multi-frame timing.

3. A one-frame high-down y165 replacement may be inert because the miss is determined by a longer action phase.
   - Explains: the tail alternates `DOWN/NOOP/UP` with large paddle-error swings before contact.
   - Evidence source: offsets 10 through 1 in `diagnostics/high_down_y165_tail_offsets_v3_16_round28.json`.
   - Minimal diagnostic: if offset4 one-frame replacement is inert, test a minimal multi-frame horizon or earlier offset.
   - Minimal change if true: add the smallest phase correction, not a broad high-down rule.
   - If false: deprioritize local high-down action edits.

### Diagnostics Run

- Traced score-`12` representatives under v3.16:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,5,7,8,10,11,12,14,16,17,19,20,21,22,23,26,27,28,29,30 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score12_subset_v3_16_round28.jsonl`
- Summarized the high-down y165 tail:
  - `diagnostics/high_down_y165_tail_offsets_v3_16_round28.json`
- Added diagnostic variants:
  - `repeat_high_down_y165_offset4_up_to_noop_h1`
  - `repeat_high_down_y165_offset4_up_to_down_h1`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --max-steps 30000 --variants baseline,repeat_high_down_y165_offset4_up_to_noop_h1,repeat_high_down_y165_offset4_up_to_down_h1 --jsonl diagnostics/counterfactual_high_down_y165_offset4_80seed_round28.jsonl`
- Full guard result:
  - Baseline mean `14.31`, min `12`, max `19`.
  - High-down y165 offset4 `UP -> NOOP`: mean `14.31`, min `12`, max `19`; `108` interventions but no score changes.
  - High-down y165 offset4 `UP -> DOWN`: mean `14.31`, min `12`, max `19`; `12` interventions but no score changes.

### Supported And Rejected Hypotheses

- Supported: the score-`12` floor is split; traced seeds show both repeated y86 tails and repeated high-down y165 tails.
- Rejected: one-frame offset4 `UP -> NOOP` controls the high-down y165 miss.
- Rejected: one-frame offset4 `UP -> DOWN` controls the high-down y165 miss.
- Still open: whether the high-down family needs a longer phase correction or an earlier setup action.

### Policy Change

- None. The diagnostic replacements were score-neutral and were not added to `pong_policy.py`.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - Result: passed before the full guard.
- No policy tests were rerun after this round because no retained policy code changed.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score12_subset_v3_16_round28.jsonl`
  - `diagnostics/high_down_y165_tail_offsets_v3_16_round28.json`
  - `diagnostics/counterfactual_high_down_y165_offset4_80seed_round28.jsonl`
- Updated `scripts/probe_counterfactual_actions.py` with diagnostic-only high-down y165 offset4 variants.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- High-down y165 offset4 variants remain diagnostic-only.

### New Structural Issues

- One-frame counterfactuals can be inert even with many interventions. The high-down family likely needs a phase-length diagnostic, earlier setup comparison, or state-clone style local rollout rather than another isolated final-tail action replacement.
- The score-`12` floor now has at least two families, so future diagnostics must report which family each touched seed belongs to.

### Next Automatic Exploration

- Test the minimal longer-horizon high-down y165 offset4 replacement to distinguish "right action, too short" from "wrong local control point".
- If longer horizon is still inert or regressive, shift to earlier offsets around `x=166..182` or compare high-down successes and failures by score/serve phase before adding any policy code.

## Round 29 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_16_y86_score20_serve_initial_up`.
- Formal 80-seed score over seeds `0..79`: mean `14.3125`, min `12`, max `19`.
- No policy change has been accepted after v3.16.

### Hypothesis Map

1. The high-down y165 offset4 action may be the right control point but one frame is too short.
   - Explains: Round 28 h1 replacements were inert despite interventions.
   - Evidence source: Round 28 offset4 h1 results and the alternating high-down tail in `diagnostics/high_down_y165_tail_offsets_v3_16_round28.json`.
   - Minimal diagnostic: repeat the same offset4 `UP` replacement with horizon `3`.
   - Minimal change if true: add the smallest horizon/phase correction that improves full-guard score.
   - If false: exclude offset4 duration as the limiting issue.

2. `UP -> NOOP` over three frames may damp the overcorrection without reversing phase.
   - Explains: offset4 has paddle below the future y165 contact but above the current target; stopping might avoid a later oscillation.
   - Evidence source: offset4 `paddle=155,target=145`, followed by offset2 `paddle=138,target=157`.
   - Minimal diagnostic: full 0..79 guard for `UP -> NOOP`, horizon `3`.
   - Minimal change if true: add a narrow h3 no-op correction.
   - If false: no-op at this point is not useful.

3. `UP -> DOWN` over three frames may actively prepare the y165 contact.
   - Explains: final miss has paddle too high numerically low (`146`) for target `165`; sustained `DOWN` might move toward the final contact zone.
   - Evidence source: high-down tail final offset and Round 28 h1 `DOWN` inertness.
   - Minimal diagnostic: full 0..79 guard for `UP -> DOWN`, horizon `3`.
   - Minimal change if true: add a narrow h3 down correction.
   - If false: direct offset4 correction should be abandoned.

### Diagnostics Run

- Added diagnostic variants:
  - `repeat_high_down_y165_offset4_up_to_noop_h3`
  - `repeat_high_down_y165_offset4_up_to_down_h3`
- Verified script syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_high_down_y165_offset4_up_to_noop_h3,repeat_high_down_y165_offset4_up_to_down_h3 --jsonl diagnostics/counterfactual_high_down_y165_offset4_h3_80seed_round29.jsonl`
- Full guard result:
  - Baseline mean `14.31`, min `12`, max `19`.
  - High-down y165 offset4 `UP -> NOOP`, horizon `3`: mean `14.31`, min `12`, max `19`; `108` interventions and no seed-level score changes.
  - High-down y165 offset4 `UP -> DOWN`, horizon `3`: mean `14.31`, min `12`, max `19`; `108` interventions and no seed-level score changes.

### Supported And Rejected Hypotheses

- Rejected: the high-down y165 offset4 point only needs a longer three-frame replacement.
- Rejected: offset4 `UP -> NOOP` h3 is useful.
- Rejected: offset4 `UP -> DOWN` h3 is useful.
- Supported: the useful high-down control point is earlier than offset4 or depends on a phase discriminator not captured by this local trigger.

### Policy Change

- None. Both h3 variants were exactly score-neutral and were not added to `pong_policy.py`.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - Result: passed.
- No policy tests were rerun because no retained policy code changed.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_high_down_y165_offset4_h3_80seed_round29.jsonl`
- Updated `scripts/probe_counterfactual_actions.py` with diagnostic-only h3 variants.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- The h3 variants remain diagnostic-only.

### New Structural Issues

- The high-down y165 tail cannot be controlled by simple one-point local replacement at offset4, even over three frames. A higher-value diagnostic should either move earlier in the tail or compare high-down failures against high-down successes to find a real discriminator.

### Next Automatic Exploration

- Build a high-down success/failure comparison from existing point traces, focusing on offsets `6..16`, score, step, serve phase, and target/paddle-error evolution.
- Use that comparison to select one earlier counterfactual rather than testing offsets blindly.

## Round 30 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_16_y86_score20_serve_initial_up`.
- Formal 80-seed score over seeds `0..79`: mean `14.3125`, min `12`, max `19`.
- No policy change has been accepted after v3.16.

### Hypothesis Map

1. High-down wins and high-down y165 losses may differ at an earlier trajectory anchor before offset4.
   - Explains: offset4 h1/h3 replacements were inert, suggesting the decisive phase is earlier.
   - Evidence source: `diagnostics/point_traces_score12_subset_v3_16_round28.jsonl`.
   - Minimal diagnostic: compare win/loss action and paddle-error distributions at high-down anchors.
   - Minimal change if true: test one earlier counterfactual with a narrow trigger.
   - If false: abandon local high-down tail action edits.

2. The x166/y93 anchor may be a safe control point because losses use `DOWN` while similar wins use `NOOP`.
   - Explains: failure trajectory may be pulled too far below the current predicted target before the later y165 miss.
   - Evidence source: `diagnostics/high_down_outcome_compare_v3_16_round29.json`.
   - Minimal diagnostic: full 0..79 guard for `DOWN -> NOOP`.
   - Minimal change if true: add one narrow x166 no-op correction.
   - If false: treat the action difference as correlated but not causal.

3. `DOWN -> UP` at x166 may be an intentionally stronger counter-phase.
   - Explains: if x166 `DOWN` is too low numerically high/low in the control cycle, reversing might avoid the later miss.
   - Evidence source: same x166 win/loss anchor comparison.
   - Minimal diagnostic: full 0..79 guard for `DOWN -> UP`.
   - Minimal change if true: add one narrow x166 up correction.
   - If false: reject direct reversal.

### Diagnostics Run

- Added `scripts/compare_high_down_outcomes.py`.
- Ran high-down win/loss anchor comparison:
  - `.venv/bin/python scripts/compare_high_down_outcomes.py diagnostics/point_traces_score12_subset_v3_16_round28.jsonl --output diagnostics/high_down_outcome_compare_v3_16_round29.json`
- Key comparison:
  - At `x166_y93`, high-down y165 losses: `60` records, all `DOWN`, mean paddle error `-15.75`.
  - At nearby high-down wins: `172` records, all `NOOP`, mean paddle error `-0.576`.
- Added diagnostic variants:
  - `repeat_high_down_y165_x166_down_to_noop_h1`
  - `repeat_high_down_y165_x166_down_to_up_h1`
- Verified script syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/compare_high_down_outcomes.py`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_high_down_y165_x166_down_to_noop_h1,repeat_high_down_y165_x166_down_to_up_h1 --jsonl diagnostics/counterfactual_high_down_y165_x166_80seed_round30.jsonl`
- Full guard result:
  - Baseline mean `14.31`, min `12`, max `19`.
  - x166 high-down y165 `DOWN -> NOOP`: mean `8.90`, min `-20`, max `19`; `97` interventions.
  - x166 high-down y165 `DOWN -> UP`: mean `4.45`, min `-20`, max `19`; `97` interventions.

### Supported And Rejected Hypotheses

- Supported: high-down wins and losses have strong anchor-level action/phase differences.
- Rejected: the x166 `DOWN -> NOOP` replacement is safe.
- Rejected: the x166 `DOWN -> UP` replacement is safe.
- Supported: the win/loss x166 difference is a correlated phase signature, not a directly portable local action.

### Policy Change

- None. Both x166 variants were catastrophic and were not added to `pong_policy.py`.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/compare_high_down_outcomes.py`
  - Result: passed.
- No policy tests were rerun because no retained policy code changed.

### Durable Assets

- Added:
  - `scripts/compare_high_down_outcomes.py`
  - `diagnostics/high_down_outcome_compare_v3_16_round29.json`
  - `diagnostics/counterfactual_high_down_y165_x166_80seed_round30.jsonl`
- Updated `scripts/probe_counterfactual_actions.py` with diagnostic-only x166 variants.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- x166 variants remain diagnostic-only.

### New Structural Issues

- Similar geometric anchors can require different actions depending on hidden phase, score, or preceding trajectory. The current trigger model can overgeneralize correlated action signatures into catastrophic interventions.
- High-down corrections likely need a discriminator based on setup phase or a short rollout/local model, not single-row geometry alone.

### Next Automatic Exploration

- Analyze which seeds/regimes were harmed by x166 variants and compare their score/phase against the original score-12 floor seeds.
- Prefer a diagnostic that can distinguish hidden high-down phases before testing another action replacement.

## Round 31 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_16_y86_score20_serve_initial_up`.
- Formal 80-seed score over seeds `0..79`: mean `14.3125`, min `12`, max `19`.
- No policy change has been accepted after v3.16.

### Hypothesis Map

1. The broad x166 disaster may hide a narrower safe first-point correction.
   - Explains: `diagnostics/high_down_y165_x166_deltas_round30.json` showed the broad `DOWN -> UP` variant had some `+1` seeds at `score=0-0,x166,y97,pad135,previous_paddle_delta=-12`.
   - Evidence source: Round 30 delta summary.
   - Minimal diagnostic: isolate only score `0-0`, `x=165..167`, `y=95..99`, `paddle=132..138`, `previous_paddle_delta<=-10`, one-shot `DOWN -> UP`.
   - Minimal change if true: add one first-point high-down correction with strict phase gates.
   - If false: reject x166 action replacement entirely.

2. The previous `+1` seeds may have been artifacts from broader repeated interventions.
   - Explains: broad variants changed later points and score phases, so the seed-level final score may not identify a safe first intervention.
   - Evidence source: broad x166 variants had many harmful first-intervention buckets.
   - Minimal diagnostic: compare isolated one-shot result against baseline.
   - Minimal change if true: none; keep diagnostic-only result.
   - If false: proceed to policy solidification.

### Diagnostics Run

- Added diagnostic variant:
  - `once_high_down_y165_initial_x166_down_to_up_h1`
- Verified script syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/summarize_counterfactual_deltas.py`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,once_high_down_y165_initial_x166_down_to_up_h1 --jsonl diagnostics/counterfactual_high_down_y165_initial_x166_80seed_round31.jsonl`
- Full guard result:
  - Baseline mean `14.31`, min `12`, max `19`.
  - Isolated initial x166 `DOWN -> UP`: mean `14.24`, min `12`, max `19`; `15` interventions.
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_y165_initial_x166_80seed_round31.jsonl --output diagnostics/high_down_y165_initial_x166_deltas_round31.json`
  - `0` improved seeds, `3` worsened seeds, `77` unchanged.
  - Regressions: seeds `33`, `44`, and `66` dropped from `18` to `16`.

### Supported And Rejected Hypotheses

- Rejected: the isolated score-`0-0` x166 `DOWN -> UP` correction is safe.
- Supported: the broad x166 `+1` observations were not a reliable local causal signal.
- Supported: high-down x166 action replacement should be abandoned for now.

### Policy Change

- None. The isolated x166 variant reduced mean score and was not added to `pong_policy.py`.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/summarize_counterfactual_deltas.py`
  - Result: passed.
- No policy tests were rerun because no retained policy code changed.

### Durable Assets

- Added:
  - `scripts/summarize_counterfactual_deltas.py`
  - `diagnostics/high_down_y165_x166_deltas_round30.json`
  - `diagnostics/counterfactual_high_down_y165_initial_x166_80seed_round31.jsonl`
  - `diagnostics/high_down_y165_initial_x166_deltas_round31.json`
- Updated `scripts/probe_counterfactual_actions.py` with the diagnostic-only isolated x166 variant.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- The isolated x166 variant remains diagnostic-only.

### New Structural Issues

- Seed-level improvements in a broad repeated intervention can be misleading; isolated one-shot probes are required before accepting a candidate.
- The high-down y165 family has now rejected offset4 h1, offset4 h3, broad x166, and isolated initial x166 action changes. The next high-value work is likely state/phase modeling or returning to the y86 floor subset.

### Next Automatic Exploration

- Re-split the current score-`12` floor after these rejected high-down probes and quantify how many floor seeds are y86 versus high-down.
- Prefer diagnostics that compare full point histories or phase state, not more one-row high-down action replacements.

## Round 32 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_16_y86_score20_serve_initial_up`.
- Formal 80-seed score over seeds `0..79`: mean `14.3125`, min `12`, max `19`.
- No policy change has been accepted after v3.16.

### Hypothesis Map

1. The current score-`12` floor may be dominated by y86, not high-down.
   - Explains: high-down y165 local probes have repeatedly failed, so the remaining improvement path may be another family.
   - Evidence source: all score-12 seeds from the latest 80-seed baseline.
   - Minimal diagnostic: trace every score-12 seed and classify repeated loss families by seed.
   - Minimal change if true: prioritize y86 diagnostics over more high-down single-row action edits.
   - If false: return to high-down state/phase modeling.

2. The y86 repeated loss may be caused by the final contact-frame `DOWN`.
   - Explains: the y86 tail ends at `x=202,y=86,target=86,paddle=79,action=DOWN`, only `7` px above target.
   - Evidence source: `diagnostics/y86_mid_down_tail_offsets_v3_16_round32.json`.
   - Minimal diagnostic: full 0..79 guard for final-frame `DOWN -> NOOP` and `DOWN -> UP`.
   - Minimal change if true: add the smallest narrow final-frame correction.
   - If false: exclude final-frame action and inspect earlier phase/setup.

3. The y86 loop may be determined earlier than the final frame by precontact correction or setup phase.
   - Explains: offset3/4 are both `mid_down_precontact_up_correction` with `NOOP`, while final-frame `NOOP` may be too late.
   - Evidence source: identical y86 tails across 262 records.
   - Minimal diagnostic: if final-frame probes fail, compare earlier precontact/setup with successful non-y86 points.
   - Minimal change if true: avoid final-frame edits and build a phase-specific earlier correction.
   - If false: revisit state/target model assumptions.

### Diagnostics Run

- Extracted all current score-`12` seeds from the latest baseline: `48` seeds.
- Traced all score-`12` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,5,7,8,10,11,12,14,16,17,19,20,21,22,23,26,27,28,29,30,32,34,35,36,37,38,39,45,46,47,51,52,53,54,55,56,57,58,59,60,61,67,71,74,76,77,78,79 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score12_all_v3_16_round32.jsonl`
- Added `scripts/summarize_floor_families.py`.
- Ran floor-family summary:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score12_all_v3_16_round32.jsonl --output diagnostics/score12_floor_families_v3_16_round32.json`
- Floor split:
  - `36/48` score-12 seeds are y86-dominant:
    - `26` `repeated_y86_with_low_up_setup`
    - `10` `repeated_y86_after_high_down`
  - `12/48` score-12 seeds are `repeated_high_down_y165`.
  - Loss records: `262` y86 `loss/mid_down/near_target`, `144` high-down, `26` low-up setup.
- Ran y86 tail summaries:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score12_all_v3_16_round32.jsonl --pattern loss/mid_down/near_target --offsets 1,2,3,4,5,6,7,8,9,10,12,14,16,18,20 --output diagnostics/y86_mid_down_tail_offsets_v3_16_round32.json`
  - `.venv/bin/python scripts/summarize_point_signatures.py diagnostics/point_traces_score12_all_v3_16_round32.jsonl --pattern loss/mid_down/near_target --output diagnostics/y86_mid_down_signatures_v3_16_round32.json`
- Added diagnostic variants:
  - `repeat_y86_final_down_to_noop_h1`
  - `repeat_y86_final_down_to_up_h1`
- Verified syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/summarize_floor_families.py`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_y86_final_down_to_noop_h1,repeat_y86_final_down_to_up_h1 --jsonl diagnostics/counterfactual_y86_final_down_80seed_round32.jsonl`
- Full guard result:
  - Baseline mean `14.31`, min `12`, max `19`.
  - y86 final `DOWN -> NOOP`: mean `14.31`, min `12`, max `19`; `262` interventions and no seed-level score changes.
  - y86 final `DOWN -> UP`: mean `5.84`, min `-11`, max `19`; `132` interventions.
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_y86_final_down_80seed_round32.jsonl --output diagnostics/y86_final_down_deltas_round32.json`

### Supported And Rejected Hypotheses

- Supported: the current score-`12` floor is mostly y86, not high-down; y86-dominant seeds are `36/48`.
- Rejected: final-frame y86 `DOWN -> NOOP` is useful; it is exactly score-neutral.
- Rejected: final-frame y86 `DOWN -> UP` is useful; it is catastrophic for the y86 floor.
- Supported: the repeated y86 loop is set earlier than the final contact frame.

### Policy Change

- None. Final-frame y86 probes did not improve score and were not added to `pong_policy.py`.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/summarize_floor_families.py`
  - Result: passed.
- No policy tests were rerun because no retained policy code changed.

### Durable Assets

- Added:
  - `scripts/summarize_floor_families.py`
  - `diagnostics/point_traces_score12_all_v3_16_round32.jsonl`
  - `diagnostics/score12_floor_families_v3_16_round32.json`
  - `diagnostics/y86_mid_down_tail_offsets_v3_16_round32.json`
  - `diagnostics/y86_mid_down_signatures_v3_16_round32.json`
  - `diagnostics/counterfactual_y86_final_down_80seed_round32.jsonl`
  - `diagnostics/y86_final_down_deltas_round32.json`
- Updated `scripts/probe_counterfactual_actions.py` with diagnostic-only y86 final-frame variants.
- Updated `failures.jsonl` and this progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- y86 final-frame variants remain diagnostic-only.

### New Structural Issues

- The y86 failure is not a final-frame action problem. Because the final-frame `NOOP` is inert and `UP` is catastrophic, a useful fix likely needs an earlier phase discriminator, a different target model for the y86 loop, or a controller-state model.
- The current score-12 floor is heterogeneous: improving min requires addressing both y86-dominant and high-down-y165 families, but y86 is the larger group.

### Next Automatic Exploration

- Compare y86-dominant score-12 losses against successful y86-like wins at offsets `10..20` and around the `mid_down_precontact_up_correction` rows.
- Test only a narrow earlier y86 phase candidate if that comparison identifies a causal-looking discriminator; otherwise add a diagnostic that exposes policy correction modes and target errors by point phase.

## Round 33 - 2026-05-09

### Current Best Baseline

- Retained best policy at round start: `ram_intercept_v3_16_y86_score20_serve_initial_up`.
- Formal 80-seed score over seeds `0..79`: mean `14.3125`, min `12`, max `19`.

### Hypothesis Map

1. The y86 loss may still be controlled at offset8, but at a later score phase.
   - Explains: score `2-2`, `4-2`, and early `6-2` offset8 `UP -> DOWN` corrections were retained earlier.
   - Evidence source: `diagnostics/y86_outcome_compare_v3_16_round33.json` and `diagnostics/y86_mid_down_tail_offsets_v3_16_round32.json`.
   - Minimal diagnostic: full 0..79 guard for one score-`8-2` offset8 `UP -> DOWN`.
   - Minimal change if true: add one score-`8-2` y86 setup correction.
   - If false: look for phase/step discriminators.

2. Score `8-2` may have a hidden phase split like score `6-2`.
   - Explains: the ungated score `6-2` correction previously improved some floor seeds but collapsed hidden seeds.
   - Evidence source: Round 23/24 score `6-2` history.
   - Minimal diagnostic: inspect seed deltas and first-intervention steps if score `8-2` is unsafe.
   - Minimal change if true: add only a step-gated correction.
   - If false: reject this direction.

### Diagnostics Run

- Added `scripts/compare_y86_outcomes.py`.
- Ran y86 outcome comparison:
  - `.venv/bin/python scripts/compare_y86_outcomes.py diagnostics/point_traces_score12_all_v3_16_round32.jsonl --output diagnostics/y86_outcome_compare_v3_16_round33.json`
- Key comparison:
  - y86 loss offset8 at `x174,y58,paddle87,target76` uses `UP`.
  - y86-like wins at nearby `x173,y57,paddle57,target76` use `DOWN`, showing the action difference is phase-dependent.
- Added diagnostic variant:
  - `once_y86_setup_offset8_score82_up_to_down_h1`
- Verified syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/compare_y86_outcomes.py`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,once_y86_setup_offset8_score82_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_score82_80seed_round33.jsonl`
- Full guard result:
  - Baseline mean `14.31`, min `12`, max `19`.
  - Ungated score-`8-2` offset8 `UP -> DOWN`: mean `11.51`, min `-13`, max `19`; `36` interventions.
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_y86_score82_80seed_round33.jsonl --output diagnostics/y86_score82_deltas_round33.json`
  - `26` improved seeds, all `+1`, with first interventions at steps `2086..2093`.
  - `10` worsened seeds, all `12 -> -13`, with first interventions at steps `2332..2336`.

### Supported And Rejected Hypotheses

- Rejected: ungated score-`8-2` offset8 `UP -> DOWN` is safe.
- Supported: score `8-2` has a hidden phase split; step timing separates the improved and collapsed seeds.
- Supported: an early-step score-`8-2` gate is worth testing.

### Policy Change

- None in Round 33. The ungated variant was rejected.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/compare_y86_outcomes.py`
  - Result: passed.

### Durable Assets

- Added:
  - `scripts/compare_y86_outcomes.py`
  - `diagnostics/y86_outcome_compare_v3_16_round33.json`
  - `diagnostics/counterfactual_y86_score82_80seed_round33.jsonl`
  - `diagnostics/y86_score82_deltas_round33.json`
- Updated `scripts/probe_counterfactual_actions.py`, `failures.jsonl`, and this progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- Ungated score-`8-2` remains diagnostic-only.

### New Structural Issues

- Score and geometry still under-specify y86 phase. Step windows remain necessary discriminators for later y86 score-chain fixes.

### Next Automatic Exploration

- Test score-`8-2` offset8 `UP -> DOWN` only during the safe early step band.

## Round 34 - 2026-05-09

### Current Best Baseline

- Previous retained best: `ram_intercept_v3_16_y86_score20_serve_initial_up`, formal 80-seed mean `14.3125`, min `12`, max `19`.
- New retained best: `ram_intercept_v3_17_y86_score82_early_setup_down`.
- New formal 80-seed score over seeds `0..79`: mean `14.6375`, min `12`, max `19`, env steps `769757`.

### Hypothesis Map

1. Score-`8-2` y86 offset8 `UP -> DOWN` is safe only in the early step band.
   - Explains: Round 33 improved seeds first intervened at steps `2086..2093`; collapsed seeds first intervened at `2332..2336`.
   - Evidence source: `diagnostics/y86_score82_deltas_round33.json`.
   - Minimal diagnostic: full 0..79 guard with `2050 <= step <= 2110`.
   - Minimal change if true: add one step-gated score-`8-2` correction.
   - If false: reject score-`8-2` entirely.

2. The early gate may improve mean without improving min because high-down y165 seeds remain at score `12`.
   - Explains: only y86-dominant score-12 seeds are touched.
   - Evidence source: Round 32 floor split.
   - Minimal diagnostic: compare mean/min/max and seed deltas after the early gate.
   - Minimal change if true: retain mean-improving policy but continue floor work.
   - If false: withdraw.

### Diagnostics Run

- Added diagnostic variant:
  - `once_y86_setup_offset8_score82_early_step_up_to_down_h1`
- Verified syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,once_y86_setup_offset8_score82_early_step_up_to_down_h1 --jsonl diagnostics/counterfactual_y86_score82_early_step_80seed_round34.jsonl`
- Full guard result:
  - Baseline mean `14.31`, min `12`, max `19`.
  - Score-`8-2`, steps `2050..2110`, offset8 `UP -> DOWN`: mean `14.64`, min `12`, max `19`; `26` interventions and no regressions.

### Supported And Rejected Hypotheses

- Supported: score-`8-2` y86 offset8 correction is safe only in the early step band.
- Supported: the correction improves mean but not min; remaining floor seeds are not all in this y86 subfamily.

### Policy Change

- Added `is_y86_score82_setup_up_correction_state`.
- Added `RamPolicy.y86_score82_early_setup_correction_used`.
- Policy now applies one score-`8-2` y86 offset8 `UP -> DOWN` correction only when `2050 <= step_count <= 2110`.
- This was the only retained policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/compare_y86_outcomes.py scripts/summarize_floor_families.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `33` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_17_y86_score82_early_setup_down --notes "Round 34: solidify score 8-2 y86 offset8 UP->DOWN correction only during steps 2050..2110 after full guard; v3_16 mean 14.3125 min 12 max 19 to probe mean 14.64 min 12 max 19" --no-video`
  - Result: mean `14.6375`, min `12`, max `19`, env steps `769757`.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_y86_score82_early_step_80seed_round34.jsonl`
- Updated `pong_policy.py`, `tests/test_state_policy.py`, `README.md`, `summary.csv`, `trials.jsonl`, `episodes.jsonl`, `failures.jsonl`, `report.html`, `scripts/probe_counterfactual_actions.py`, and this progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- The ungated score-`8-2` variant remains rejected and diagnostic-only.

### New Structural Issues

- The retained mean improved, but min remains `12`; the next floor likely includes high-down y165 and/or later y86 phases not touched by the early score-`8-2` gate.

### Next Automatic Exploration

- Trace current score-`12` seeds under v3.17 and re-split the floor.
- Determine whether the score-12 floor is now dominated by high-down y165, later y86, or another repeated family before proposing another strategy change.

## Round 35 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_17_y86_score82_early_setup_down`.
- Formal 80-seed score over seeds `0..79`: mean `14.6375`, min `12`, max `19`.
- Current score-`12` floor count after v3.17: `22/80` seeds.

### Hypothesis Map

1. The v3.17 floor may have shifted toward high-down y165 after the early score-`8-2` y86 fix.
   - Explains: v3.17 improved 26 y86 seeds from `12` to `13`, leaving untouched high-down seeds at `12`.
   - Evidence source: formal v3.17 episode scores and Round 32 floor split.
   - Minimal diagnostic: trace all v3.17 score-`12` seeds and classify repeated loss families.
   - Minimal change if true: prioritize high-down phase/model diagnostics.
   - If false: continue y86 score-chain work.

2. Later y86 remains a floor family even after the early score-`8-2` gate.
   - Explains: Round 33 showed later score-`8-2` phases are unsafe for the same action replacement, so those seeds may still loop.
   - Evidence source: Round 33 collapsed seeds and v3.17 formal scores.
   - Minimal diagnostic: classify v3.17 score-`12` seeds by final signatures.
   - Minimal change if true: any next y86 correction needs a different discriminator, not the early step gate.
   - If false: y86 can be deprioritized.

### Diagnostics Run

- Extracted v3.17 score-`12` seeds from `episodes.jsonl`: `22` seeds.
- Traced all v3.17 score-`12` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score12_all_v3_17_round35.jsonl`
- Ran floor-family summary:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score12_all_v3_17_round35.jsonl --output diagnostics/score12_floor_families_v3_17_round35.json`
- Ran high-down tail summary:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score12_all_v3_17_round35.jsonl --pattern loss/high_down/paddle_above_target --offsets 1,2,3,4,5,6,7,8,9,10,12,14,16 --output diagnostics/high_down_tail_offsets_v3_17_round35.json`
- Floor split:
  - `12/22` seeds are `repeated_high_down_y165`.
  - `10/22` seeds are `repeated_y86_after_high_down`.
  - Loss records: `118` high-down and `80` y86 mid-down.
  - Top signatures:
    - `108` high-down y165 losses at `x=202,y=165,dy=8,target=165,paddle=146,error=-19`.
    - `80` y86 losses at `x=202,y=86,dy=4,target=86,paddle=79,error=-7`.
    - `10` early high-down y173 losses at `x=204,y=173,dy=8,target=173,paddle=163,error=-10`.

### Supported And Rejected Hypotheses

- Supported: the score-`12` floor is now smaller and more concentrated after v3.17.
- Supported: high-down y165 is now the largest single remaining family.
- Supported: later y86 remains a real floor family, but the earlier score-`8-2` step gate deliberately avoids it.

### Policy Change

- None. This round was diagnostic-only.

### Verification

- No policy code changed after the v3.17 verification, so no policy tests were rerun.
- The diagnostic scripts used were already py-compiled in Round 34.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score12_all_v3_17_round35.jsonl`
  - `diagnostics/score12_floor_families_v3_17_round35.json`
  - `diagnostics/high_down_tail_offsets_v3_17_round35.json`
- Updated this progress log.

### Withdrawn

- No accepted code was withdrawn.

### New Structural Issues

- The remaining floor is split between high-down y165 and later y86. A min improvement now likely requires either a high-down family fix or a later-y86 discriminator that avoids the unsafe score-`8-2` late phase.
- Repeated high-down y165 has survived offset4 h1/h3 and x166 action probes, so the next high-down experiment should not be another single-row replacement without a stronger phase model.

### Next Automatic Exploration

- Build or extend a diagnostic that compares high-down y165 failures against high-down successes after v3.17, including step/score/serve phase and controller mode history.
- In parallel, inspect later-y86 seeds from v3.17 to see whether the unsafe late score-`8-2` phase has a non-action discriminator.

## Round 36 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_17_y86_score82_early_setup_down`.
- Formal 80-seed score over seeds `0..79`: mean `14.6375`, min `12`, max `19`.
- Current score-`12` floor after v3.17 remains split between high-down y165 and later-y86 families.

### Hypothesis Map

1. High-down y165 failures may diverge from wins before the previously tested x166/x190 anchors.
   - Explains: Round 35 showed the dominant high-down floor still reaches final `x=202,y=165,target=165,paddle=146,error=-19`, while local x166 and x190 replacements were unsafe or inert.
   - Evidence source: `diagnostics/score12_floor_families_v3_17_round35.json` and prior rejected high-down probes.
   - Minimal diagnostic: compare high-down failure and win records across earlier anchors from x150 through x198.
   - Minimal change if true: test one earlier, narrow counterfactual action replacement before changing policy.
   - If false: local high-down action replacement can be deprioritized in favor of phase-history or setup diagnostics.

2. The x150 high-down `UP` action in losses might be a causal overcorrection.
   - Explains: the new comparison found losses at `x=150,y=61,target=145,paddle=165` mostly use `UP`, while wins around the same anchor use `NOOP` with much smaller paddle error.
   - Evidence source: `diagnostics/high_down_outcome_compare_v3_17_round36.json`.
   - Minimal diagnostic: full 0..79 counterfactual replacing only that x150 high-down `UP` with `NOOP` for one frame.
   - Minimal change if true: add one narrow high-down x150 setup correction.
   - If false: the x150 action difference is only a phase signature, not an actionable fix.

3. High-down failure may require a structural controller-history model rather than more local frame edits.
   - Explains: several local high-down probes have either no effect or catastrophic regressions.
   - Evidence source: Round 28 through Round 31 failures and Round 36 comparison.
   - Minimal diagnostic: record mode/action history around high-down wins and losses, not only final anchors.
   - Minimal change if true: add a history-aware diagnostic or state-machine feature, not a policy patch.
   - If false: a smaller causal action gate should emerge from the anchor comparison.

### Diagnostics Run

- Added high-down win/loss anchor comparison script:
  - `scripts/compare_high_down_outcomes.py`
- Ran comparison on the v3.17 score-`12` point traces:
  - `.venv/bin/python scripts/compare_high_down_outcomes.py diagnostics/point_traces_score12_all_v3_17_round35.jsonl --output diagnostics/high_down_outcome_compare_v3_17_round36.json`
- Key comparison result:
  - At `x150_y61`, loss high-down records: `118`, top bucket `x=150,y=61,target=145,paddle=165,score=4-0,action=up`, mean paddle error `19.492`.
  - At `x150_y61`, win records: `178`, top bucket `x=149,y=61,target=147,paddle=150,action=noop`, mean paddle error `3.0`.
  - Later anchors still showed action differences, but previous x166 and x190 local probes already showed that direct replacement there is unsafe or inert.
- Added diagnostic-only counterfactual variant:
  - `repeat_high_down_y165_x150_up_to_noop_h1`
- Verified syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
- Ran full 80-seed guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_high_down_y165_x150_up_to_noop_h1 --jsonl diagnostics/counterfactual_high_down_y165_x150_80seed_round36.jsonl`
- Summarized deltas:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_y165_x150_80seed_round36.jsonl --output diagnostics/high_down_y165_x150_deltas_round36.json`

### Supported And Rejected Hypotheses

- Supported: high-down failures and wins differ strongly by paddle phase/error as early as x150.
- Rejected: x150 `UP -> NOOP` is a causal one-frame fix. It fired `12` times but every seed score was unchanged.
- Supported: more local high-down action edits are low-value without an earlier setup or controller-history discriminator.

### Policy Change

- None. The only tested x150 counterfactual was inert:
  - Baseline: mean `14.64`, min `12`, max `19`.
  - `repeat_high_down_y165_x150_up_to_noop_h1`: mean `14.64`, min `12`, max `19`, interventions `12`.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - Result: passed.
- `.venv/bin/python -m py_compile scripts/compare_high_down_outcomes.py scripts/summarize_counterfactual_deltas.py scripts/probe_counterfactual_actions.py`
  - Result: passed.
- No policy tests were rerun because no retained policy code changed.

### Durable Assets

- Added:
  - `scripts/compare_high_down_outcomes.py`
  - `diagnostics/high_down_outcome_compare_v3_17_round36.json`
  - `diagnostics/counterfactual_high_down_y165_x150_80seed_round36.jsonl`
  - `diagnostics/high_down_y165_x150_deltas_round36.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py` with the diagnostic-only x150 variant.
  - `failures.jsonl` with the rejected x150 high-down hypothesis.
  - This progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- The x150 high-down action replacement remains diagnostic-only and rejected for policy use.

### New Structural Issues

- The high-down y165 floor is visible at multiple anchors, but single-frame replacements at x150, x166, and x190 do not produce a safe improvement. The likely missing variable is controller/paddle phase accumulated before x150, not the local action at the anchor itself.
- The remaining min `12` cannot currently be improved by the tested high-down local edits; next work should either model pre-x150 setup history or switch to the later-y86 floor family.

### Next Automatic Exploration

- Build a compact history diagnostic for high-down y165 wins/losses that records the last 8-12 `(mode, action, target, paddle_error, ball_x, ball_y)` samples before x150 and before final contact.
- Use it to find a discriminator that separates inert/correlated local differences from earlier causal setup differences.

## Round 37 - 2026-05-09

### Current Best Baseline

- Previous retained best: `ram_intercept_v3_17_y86_score82_early_setup_down`.
- Previous formal 80-seed score over seeds `0..79`: mean `14.6375`, min `12`, max `19`.
- New retained best: `ram_intercept_v3_18_high_down_score40_bounce_noop`.
- New formal 80-seed score over seeds `0..79`: mean `14.9125`, min `13`, max `19`, env steps `763117`.

### Hypothesis Map

1. High-down y165 failures differ from wins by a controller phase before x150, not by the x150 action itself.
   - Explains: Round 36 x150 `UP -> NOOP` fired but was score-inert.
   - Evidence source: `diagnostics/high_down_outcome_compare_v3_17_round36.json`.
   - Minimal diagnostic: compare compact pre-x150 histories for high-down wins and losses.
   - Minimal change if true: test the first pre-x150 frame with a clean causal split.
   - If false: abandon local high-down control and inspect later-y86.

2. The high-down bounce frame one step before x150 is causal.
   - Explains: history comparison shows losses at `x=146,y=53,dy=9,target=156.5,paddle=146` use `DOWN`, then arrive at x150 with a bad `UP` phase; wins use a different phase and arrive at x150 with `NOOP`.
   - Evidence source: `diagnostics/high_down_history_x150_v3_17_round37.json`.
   - Minimal diagnostic: full 0..79 counterfactual for bounce-frame `DOWN -> NOOP` and `DOWN -> UP`.
   - Minimal change if true: add one narrow bounce-frame correction.
   - If false: the history difference is correlated but not causal.

3. The bounce-frame geometry has a hidden score split.
   - Explains: the same geometry appears at score `0-0` and score `4-0`; score `0-0` may be an opening family rather than the repeated high-down floor.
   - Evidence source: Round 37 ungated delta summary.
   - Minimal diagnostic: gate the bounce-frame `DOWN -> NOOP` to score `4-0`.
   - Minimal change if true: add the score-gated correction only.
   - If false: reject the bounce-frame policy change.

### Diagnostics Run

- Added compact history comparison script:
  - `scripts/compare_high_down_history.py`
- Ran x150 history comparison:
  - `.venv/bin/python scripts/compare_high_down_history.py diagnostics/point_traces_score12_all_v3_17_round35.jsonl --history 12 --output diagnostics/high_down_history_x150_v3_17_round37.json`
- Key history result:
  - Loss high-down count `118`; top signature count `108`:
    - pre-x150 action/error pattern `UP,NOOP,DOWN,NOOP,UP,NOOP,DOWN,NOOP,UP,NOOP,DOWN,DOWN,UP`
    - anchor `x=150,y=61,target=145,paddle=165,error=20,action=UP`
  - Win count `178`; top signature count `178`:
    - pre-x150 pattern `UP,UP,NOOP,DOWN,DOWN,UP,UP,NOOP,DOWN,DOWN,UP,UP,NOOP`
    - anchor `x=149,y=61,target=147,paddle=150,error=3,action=NOOP`
  - The strongest causal candidate moved from x150 to the preceding dy=9 bounce frame.
- Added diagnostic variants:
  - `repeat_high_down_y165_bounce_x146_down_to_noop_h1`
  - `repeat_high_down_y165_bounce_x146_down_to_up_h1`
  - `repeat_high_down_y165_bounce_score40_x146_down_to_noop_h1`
- Ran ungated bounce-frame full guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_high_down_y165_bounce_x146_down_to_noop_h1,repeat_high_down_y165_bounce_x146_down_to_up_h1 --jsonl diagnostics/counterfactual_high_down_y165_bounce_x146_80seed_round37.jsonl`
- Ungated result:
  - Baseline mean `14.64`, min `12`, max `19`.
  - `DOWN -> NOOP`: mean `14.81`, min `11`, max `19`, interventions `26`; `22` improvements and `4` regressions.
  - `DOWN -> UP`: mean `10.76`, min `-17`, max `19`, interventions `26`; rejected as catastrophic.
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_y165_bounce_x146_80seed_round37.jsonl --output diagnostics/high_down_y165_bounce_x146_deltas_round37.json`
  - The `DOWN -> NOOP` improvements all first intervened at score `4-0`; all regressions first intervened at score `0-0`.
- Ran score-gated full guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_high_down_y165_bounce_score40_x146_down_to_noop_h1 --jsonl diagnostics/counterfactual_high_down_y165_bounce_score40_80seed_round37.jsonl`
- Score-gated result:
  - Baseline mean `14.64`, min `12`, max `19`.
  - Score `4-0` bounce `DOWN -> NOOP`: mean `14.91`, min `13`, max `19`, interventions `22`, no regressions.
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_y165_bounce_score40_80seed_round37.jsonl --output diagnostics/high_down_y165_bounce_score40_deltas_round37.json`

### Supported And Rejected Hypotheses

- Supported: the x150 action itself is not causal, but the immediately preceding dy=9 bounce frame is causal.
- Supported: score `4-0` cleanly separates the beneficial high-down floor correction from score `0-0` regressions.
- Rejected: replacing bounce-frame `DOWN` with `UP`; it collapsed score-12 seeds to `-17`.
- Rejected: ungated bounce-frame `DOWN -> NOOP`; it improves mean but lowers min to `11`.

### Policy Change

- Added `is_high_down_score40_bounce_down_correction_state`.
- Policy now changes only score-`4-0`, dy=9, `x=144..148,y=50..56,paddle=143..150,target=154..162` baseline `DOWN` to `NOOP`.
- This was the only retained policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py scripts/compare_high_down_history.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `35` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_18_high_down_score40_bounce_noop --notes "Round 37: solidify score 4-0 high-down bounce x144-148 y50-56 dy9 DOWN->NOOP after history diagnostic; v3_17 mean 14.6375 min 12 max 19 to probe mean 14.91 min 13 max 19" --no-video`
  - Result: mean `14.9125`, min `13`, max `19`, env steps `763117`.

### Durable Assets

- Added:
  - `scripts/compare_high_down_history.py`
  - `diagnostics/high_down_history_x150_v3_17_round37.json`
  - `diagnostics/counterfactual_high_down_y165_bounce_x146_80seed_round37.jsonl`
  - `diagnostics/high_down_y165_bounce_x146_deltas_round37.json`
  - `diagnostics/counterfactual_high_down_y165_bounce_score40_80seed_round37.jsonl`
  - `diagnostics/high_down_y165_bounce_score40_deltas_round37.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `failures.jsonl`
  - `report.html`
  - This progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- Ungated bounce-frame `DOWN -> NOOP` and bounce-frame `DOWN -> UP` remain rejected for policy use.

### New Structural Issues

- The new floor is score `13`; score-12 high-down floor has been removed in the 80-seed gate.
- The remaining score-13 seeds likely combine previously hidden y86, opening, and post-high-down families. The next round should not assume high-down still dominates without tracing the new floor.

### Next Automatic Exploration

- Extract all v3.18 score-`13` seeds from `episodes.jsonl`.
- Trace their point windows under the retained v3.18 policy and re-run floor-family summaries to identify the new min-limiting families before proposing another policy change.

## Round 38 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_18_high_down_score40_bounce_noop`.
- Formal 80-seed score over seeds `0..79`: mean `14.9125`, min `13`, max `19`.
- Current score-`13` floor count after v3.18: `48/80` seeds.

### Hypothesis Map

1. The v3.18 floor may now be dominated by the older y86 mid-down family.
   - Explains: v3.18 removed score-12 high-down failures, but many seeds remain at score 13.
   - Evidence source: formal v3.18 episode scores and previous y86 floor diagnostics.
   - Minimal diagnostic: trace all score-13 seeds and summarize loss families.
   - Minimal change if true: prioritize y86 phase diagnostics over further high-down edits.
   - If false: inspect the new non-y86 family first.

2. Some score-13 seeds may still include an early high-down/low-up pair before entering the y86 loop.
   - Explains: score-13 seeds might be mixed, with one early miss family plus repeated y86 losses.
   - Evidence source: v3.18 episode scores alone cannot distinguish repeated losses from single early losses.
   - Minimal diagnostic: classify per-seed loss pattern counts from point traces.
   - Minimal change if true: split score-13 floor by seed family before any counterfactual.
   - If false: a single y86 experiment may cover most floor seeds.

3. Existing y86 action replacements remain risky unless a new score/phase discriminator is found.
   - Explains: final y86 DOWN replacements were inert or harmful, and late score-`8-2` gates previously collapsed.
   - Evidence source: Rounds 32-34.
   - Minimal diagnostic: compare y86 outcomes across anchors and score buckets under v3.18.
   - Minimal change if true: build a score-chain or phase-history diagnostic before policy changes.
   - If false: a narrow y86 anchor replacement can be tested directly.

### Diagnostics Run

- Extracted v3.18 score-`13` seeds from trial `37`: `48` seeds.
- Traced all v3.18 score-`13` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 1,5,7,8,10,11,12,14,16,17,19,20,21,22,23,26,27,28,29,30,32,34,35,36,37,38,39,45,46,47,51,52,53,54,55,56,57,58,59,60,61,67,71,74,76,77,78,79 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score13_all_v3_18_round38.jsonl`
- Ran floor-family summary:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score13_all_v3_18_round38.jsonl --output diagnostics/score13_floor_families_v3_18_round38.json`
- Ran mid-down tail summary:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score13_all_v3_18_round38.jsonl --pattern loss/mid_down/near_target --offsets 1,2,3,4,5,6,7,8,9,10,12,14,16 --output diagnostics/mid_down_y86_tail_offsets_v3_18_round38.json`
- Ran y86 outcome comparison:
  - `.venv/bin/python scripts/compare_y86_outcomes.py diagnostics/point_traces_score13_all_v3_18_round38.jsonl --output diagnostics/y86_outcome_compare_v3_18_round38.json`
- Floor split:
  - `22/48` seeds are pure `repeated_y86_after_high_down` with `8` mid-down losses each.
  - `26/48` seeds are `early_high_down_y173` with one high-down y173 loss, one low-up loss, and `6` mid-down y86 losses each.
  - Loss records: `332` mid-down y86, `26` high-down y173, `26` low-up.
- Dominant signature:
  - `332` y86 mid-down losses at `x=202,y=86,dy=4,target=86,paddle=79,error=-7`.
  - The tail before contact is perfectly consistent across the 332 records:
    - offset 5: `x=186,y=70,target=76,paddle=77,action=NOOP,error=1`
    - offset 4: `x=190,y=74,target=76,paddle=86,action=NOOP,mode=mid_down_precontact_up_correction,error=10`
    - offset 3: `x=194,y=78,target=78,paddle=89,action=NOOP,mode=mid_down_precontact_up_correction,error=11`
    - offset 2: `x=198,y=82,target=82,paddle=89,action=UP,error=7`
    - offset 1: `x=202,y=86,target=86,paddle=79,action=DOWN,error=-7`
- Score buckets for y86 mid-down losses:
  - `10-2`, `12-3`, `14-4`, `16-5`, `18-6`, `20-7`: `48` each.
  - `6-0`, `8-1`: `22` each.

### Supported And Rejected Hypotheses

- Supported: the new min floor is dominated by y86 mid-down, not high-down y165.
- Supported: score-13 seeds are mixed; 26 seeds still have one early high-down y173 and one low-up loss before repeated y86 losses.
- Supported: the y86 tail is highly regular, but prior direct final-frame edits should not be reused without a new discriminator.
- No policy hypothesis was accepted or rejected in this round; this was diagnostic-only.

### Policy Change

- None. No new counterfactual was run after the floor split, and no policy code changed.

### Verification

- No policy tests were rerun after Round 37 because Round 38 only produced diagnostic data.
- Diagnostic scripts completed and wrote their outputs successfully.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score13_all_v3_18_round38.jsonl`
  - `diagnostics/score13_floor_families_v3_18_round38.json`
  - `diagnostics/mid_down_y86_tail_offsets_v3_18_round38.json`
  - `diagnostics/y86_outcome_compare_v3_18_round38.json`
- Updated this progress log.

### Withdrawn

- No accepted code was withdrawn.

### New Structural Issues

- The y86 mid-down loop now appears as the dominant repeated floor, but previous y86 final-frame counterfactuals were either inert or harmful. A useful next experiment needs to target the earlier score-chain phase, especially around x166/x174/x182, and must distinguish scores `6-0/8-1` from later `10-2..20-7` buckets.
- The 26 mixed seeds still include early high-down y173 and low-up losses, but these are single losses per seed; fixing repeated y86 has higher expected floor impact.

### Next Automatic Exploration

- Create a counterfactual diagnostic for the v3.18 y86 mid-down tail that tests one narrow earlier anchor at a time, starting with the x166 bounce/frame split from `diagnostics/y86_outcome_compare_v3_18_round38.json`.
- Run full 80-seed guards before any y86 policy change because previous y86 gates have hidden phase collapses.

## Round 39 - 2026-05-09

### Current Best Baseline

- Previous retained best: `ram_intercept_v3_18_high_down_score40_bounce_noop`.
- Previous formal 80-seed score over seeds `0..79`: mean `14.9125`, min `13`, max `19`.
- New retained best: `ram_intercept_v3_19_y86_after_score22_bounce_up`.
- New formal 80-seed score over seeds `0..79`: mean `16.8625`, min `13`, max `19`, env steps `721387`.

### Hypothesis Map

1. The y86 x166 bounce frame is causal for the new score-13 floor.
   - Explains: Round 38 y86 outcome comparison showed score-13 losses at `x=166,y=50,dy=5,target=82.5,paddle=78,action=DOWN`.
   - Evidence source: `diagnostics/y86_outcome_compare_v3_18_round38.json` and `diagnostics/mid_down_y86_tail_offsets_v3_18_round38.json`.
   - Minimal diagnostic: full 0..79 guard for x166 `DOWN -> NOOP` and `DOWN -> UP`.
   - Minimal change if true: one narrow x166 bounce correction.
   - If false: move earlier in the y86 history.

2. The x166 frame has hidden phase families that need a discriminator.
   - Explains: previous y86 gates often improved one score chain while collapsing another.
   - Evidence source: Rounds 23, 25, 33 and the v3.18 floor split.
   - Minimal diagnostic: inspect delta buckets by first intervention score/step.
   - Minimal change if true: add a score or stateful family gate, not an ungated local replacement.
   - If false: accept the narrow local replacement.

3. A score-`2-2` family marker may identify the safe chain better than a single-score action gate.
   - Explains: initial delta buckets showed improvements first intervened at score `2-2`, while regressions first intervened at score `6-0`; however a single score-`2-2` replacement might be insufficient if repeated later interventions are needed.
   - Evidence source: `diagnostics/y86_bounce_x166_deltas_round39.json`.
   - Minimal diagnostic: compare score-`2-2` only, score-exclusion, and stateful-after-score-`2-2` variants.
   - Minimal change if true: add one boolean policy marker and one narrow x166 action correction gated by that marker.
   - If false: reject this y86 bounce direction.

### Diagnostics Run

- Added diagnostic variants:
  - `repeat_y86_bounce_x166_down_to_noop_h1`
  - `repeat_y86_bounce_x166_down_to_up_h1`
  - `repeat_y86_bounce_score22_x166_down_to_up_h1`
  - `repeat_y86_bounce_not_score60_x166_down_to_up_h1`
  - `repeat_y86_bounce_after_score22_x166_down_to_up_h1`
- Ran ungated x166 full guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_y86_bounce_x166_down_to_noop_h1,repeat_y86_bounce_x166_down_to_up_h1 --jsonl diagnostics/counterfactual_y86_bounce_x166_80seed_round39.jsonl`
- Ungated result:
  - Baseline mean `14.91`, min `13`, max `19`.
  - `DOWN -> NOOP`: mean `13.61`, min `9`, max `19`, interventions `436`; rejected.
  - `DOWN -> UP`: mean `14.94`, min `6`, max `19`, interventions `612`; split effect, rejected ungated.
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_y86_bounce_x166_80seed_round39.jsonl --output diagnostics/y86_bounce_x166_deltas_round39.json`
  - `DOWN -> UP` improved `26` seeds by `+6` but regressed `22` seeds by `-7`.
  - Improvements first intervened at score `2-2`; regressions first intervened at score `6-0`.
- Ran score-`2-2` only guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_y86_bounce_score22_x166_down_to_up_h1 --jsonl diagnostics/counterfactual_y86_bounce_score22_x166_80seed_round39.jsonl`
  - Result: mean `14.91`, min `13`, max `19`, interventions `26`, no score changes.
  - Delta output: `diagnostics/y86_bounce_score22_x166_deltas_round39.json`.
- Ran “not score `6-0`” guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_y86_bounce_not_score60_x166_down_to_up_h1 --jsonl diagnostics/counterfactual_y86_bounce_not_score60_x166_80seed_round39.jsonl`
  - Result: mean `15.21`, min `7`, max `19`; still unsafe.
  - Delta output: `diagnostics/y86_bounce_not_score60_x166_deltas_round39.json`.
  - New regressions first intervened at score `8-1`, so a simple score-exclusion list is not the right model.
- Added stateful diagnostic branch:
  - Marker: episode has seen score-`2-2` x166 y86 bounce.
  - Action: after marker is set, change later x166 y86 bounce `DOWN -> UP`.
- Ran stateful full guard:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --max-steps 30000 --variants baseline,repeat_y86_bounce_after_score22_x166_down_to_up_h1 --jsonl diagnostics/counterfactual_y86_bounce_after_score22_x166_80seed_round39.jsonl`
  - Result: mean `16.86`, min `13`, max `19`, interventions `260`.
  - Delta output: `diagnostics/y86_bounce_after_score22_x166_deltas_round39.json`.
  - Improved `26` seeds by `+6`; no regressions.

### Supported And Rejected Hypotheses

- Supported: y86 x166 bounce action is causal.
- Supported: the visually identical x166 geometry contains at least two hidden score-chain families.
- Supported: the safe discriminator is an episode-level score-`2-2` family marker, not a one-frame score-`2-2` replacement.
- Rejected: x166 `DOWN -> NOOP`; it lowered mean and min.
- Rejected: ungated x166 `DOWN -> UP`; it improved one family but collapsed another to score `6`.
- Rejected: score-`2-2` only; it was inert.
- Rejected: excluding only score `6-0`; score `8-1` regressions remained.

### Policy Change

- Added `RamPolicy.y86_score22_bounce_family_seen`.
- Added helpers:
  - `is_y86_bounce_x166_down_correction_state`
  - `is_y86_bounce_score22_x166_down_correction_state`
- Policy now sets the marker when it sees score `2-2` x166 y86 bounce.
- Once marked, it changes later matching x166 y86 bounce baseline `DOWN` to `UP`.
- This was the only retained policy change in the round.

### Verification

- `.venv/bin/python -m py_compile pong_policy.py scripts/probe_counterfactual_actions.py tests/test_state_policy.py`
  - Result: passed.
- `PYTHONPATH=. .venv/bin/python tests/test_state_policy.py`
  - Result: `37` tests passed.
- Formal evaluation:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_19_y86_after_score22_bounce_up --notes "Round 39: solidify stateful y86 x166 bounce DOWN->UP only after score 2-2 family marker; v3_18 mean 14.9125 min 13 max 19 to probe mean 16.86 min 13 max 19" --no-video`
  - Result: mean `16.8625`, min `13`, max `19`, env steps `721387`.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_y86_bounce_x166_80seed_round39.jsonl`
  - `diagnostics/y86_bounce_x166_deltas_round39.json`
  - `diagnostics/counterfactual_y86_bounce_score22_x166_80seed_round39.jsonl`
  - `diagnostics/y86_bounce_score22_x166_deltas_round39.json`
  - `diagnostics/counterfactual_y86_bounce_not_score60_x166_80seed_round39.jsonl`
  - `diagnostics/y86_bounce_not_score60_x166_deltas_round39.json`
  - `diagnostics/counterfactual_y86_bounce_after_score22_x166_80seed_round39.jsonl`
  - `diagnostics/y86_bounce_after_score22_x166_deltas_round39.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `failures.jsonl`
  - `report.html`
  - This progress log.

### Withdrawn

- No accepted policy code was withdrawn.
- Ungated x166 `DOWN -> NOOP`, ungated x166 `DOWN -> UP`, score-`2-2` only, and “not score `6-0`” remain diagnostic-only and rejected for policy use.

### New Structural Issues

- The score-13 floor remains because the pure score `6-0/8-1` y86 family was deliberately not touched by v3.19.
- The accepted fix proves that some residual failures need controller state/family markers rather than purely local score/geometry gates.

### Next Automatic Exploration

- Extract v3.19 score-`13` seeds and re-split the floor.
- Determine whether the remaining score-13 seeds are now the pure y86 score `6-0/8-1` family, early high-down/low-up family, or a new residual structure.

## Round 40 - 2026-05-09

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_19_y86_after_score22_bounce_up`.
- Formal 80-seed score over seeds `0..79`: mean `16.8625`, min `13`, max `19`.
- Current score-`13` floor count after v3.19: `22/80` seeds.

### Hypothesis Map

1. The v3.19 floor may now be the pure y86 score `6-0/8-1` family that was deliberately excluded by the stateful marker.
   - Explains: v3.19 improved the mixed score-`2-2` family to `19` while leaving the pure family at `13`.
   - Evidence source: Round 39 stateful delta summary and formal v3.19 scores.
   - Minimal diagnostic: trace all v3.19 score-`13` seeds and summarize loss families.
   - Minimal change if true: next y86 experiment should focus on this pure family, not on the accepted score-`2-2` marker family.
   - If false: identify the new floor family before counterfactuals.

2. Early high-down/low-up losses may no longer contribute to the min floor.
   - Explains: Round 39 moved the 26 mixed seeds from `13` to `19`.
   - Evidence source: v3.19 per-seed scores.
   - Minimal diagnostic: check whether score-`13` seeds still contain high-down/low-up records.
   - Minimal change if true: deprioritize high-down/low-up for the next min-improvement round.
   - If false: split mixed floor again.

3. Remaining y86 losses may still share the same tail but require a different episode marker.
   - Explains: the accepted marker intentionally does not activate on the pure family.
   - Evidence source: Round 39 rejected gates and accepted stateful gate.
   - Minimal diagnostic: compare the pure family’s early score chain with the accepted family and look for a safe marker analogous to score `2-2`.
   - Minimal change if true: add a new marker/state-machine gate only after full guard.
   - If false: avoid x166 and inspect earlier setup.

### Diagnostics Run

- Extracted v3.19 score-`13` seeds from trial `38`: `22` seeds.
- Traced all v3.19 score-`13` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score13_all_v3_19_round40.jsonl`
- Ran floor-family summary:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score13_all_v3_19_round40.jsonl --output diagnostics/score13_floor_families_v3_19_round40.json`
- Ran y86 tail summary:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score13_all_v3_19_round40.jsonl --pattern loss/mid_down/near_target --offsets 1,2,3,4,5,6,7,8,9,10,12,14,16 --output diagnostics/mid_down_y86_tail_offsets_v3_19_round40.json`
- Floor split:
  - `22/22` seeds are `repeated_y86_after_high_down`.
  - Loss records: `176` mid-down y86, no high-down y173, no low-up.
- Dominant signature:
  - `176` losses at `x=202,y=86,dy=4,target=86,paddle=79,error=-7`.
  - Tail remains identical to the v3.18 pure-family tail:
    - offset 10: `x=166,y=50,dy=5,target=82.5,paddle=78,action=DOWN,error=-4.5`
    - offset 8: `x=174,y=58,target=76,paddle=87,action=UP,error=11`
    - offset 4/3: `x=190/194`, `NOOP`, `mode=mid_down_precontact_up_correction`
    - offset 1: `x=202,y=86,target=86,paddle=79,action=DOWN,error=-7`

### Supported And Rejected Hypotheses

- Supported: the new min floor is now pure y86; the mixed high-down/low-up floor was removed from the min seeds by v3.19.
- Supported: the remaining family is the one deliberately excluded from the score-`2-2` marker correction.
- No policy hypothesis was accepted or rejected in this round; this was diagnostic-only.

### Policy Change

- None. This round was floor reclassification only.

### Verification

- No policy tests were rerun because no code changed after the v3.19 verification.
- Diagnostic commands completed and wrote their outputs successfully.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score13_all_v3_19_round40.jsonl`
  - `diagnostics/score13_floor_families_v3_19_round40.json`
  - `diagnostics/mid_down_y86_tail_offsets_v3_19_round40.json`
- Updated this progress log.

### Withdrawn

- No accepted code was withdrawn.

### New Structural Issues

- The remaining floor is a pure y86 family that shares the same local x166/x202 geometry as the accepted family but lacks the score-`2-2` marker. Any next correction must find a different safe episode marker or earlier setup variable.
- A direct x166 `DOWN -> UP` replacement is known unsafe; future work should not repeat it without a new discriminator.

### Next Automatic Exploration

- Compare accepted v3.19-improved y86 seeds against remaining v3.19 score-`13` seeds by early score-chain history and first x166 trigger sequence.
- Build a diagnostic that summarizes per-seed x166 intervention opportunities by score order, not just final point windows.

## Round 41 - 2026-05-10

### Current Best Baseline

- Retained best policy remains `ram_intercept_v3_19_y86_after_score22_bounce_up`.
- Formal 80-seed baseline remains mean `16.8625`, min `13`, max `19`.
- This round was triggered by manual review of a recorded seed `0` HTML/GIF run, where the game appeared to repeat a small action cycle.

### Hypothesis Map

1. The visible loop is only GIF playback restarting after the episode ends.
   - Explains: browser GIFs loop by default.
   - Evidence source: `scripts/run_eval.py` writes GIFs with `loop=0`.
   - Minimal diagnostic: inspect the actual episode trace and final done step.
   - Minimal change if true: no policy change; optionally document GIF looping behavior.
   - If false: an in-episode RAM/action loop exists.

2. The policy enters an inactive-ball control loop after a late score event.
   - Explains: video shows repeated paddle movements while no new point appears to start.
   - Evidence source: the recorded run `ram_intercept_v3_19_video_check` ended at score `17` after `27000` steps, much longer than the last reward shown by `scripts/trace_episode.py`.
   - Minimal diagnostic: count actions and modes after the last reward in the same seed.
   - Minimal change if true: later test whether inactive mode needs a different reset/serve action or max-step handling; do not change policy without multi-seed evidence.
   - If false: look for a live-rally oscillation instead.

3. The apparent loop is a normal live-rally paddle oscillation around the center target.
   - Explains: center-mode actions can alternate because the paddle has phase and momentum.
   - Evidence source: earlier action-effect diagnostics showed actions are not instantaneous.
   - Minimal diagnostic: verify whether `ball_y` is active and whether rewards continue during the loop.
   - Minimal change if true: only consider control smoothing if score validation supports it.
   - If false: deprioritize live-rally smoothing for this artifact.

### Diagnostics Run

- Ran the recorded seed `0` trace without video:
  - `.venv/bin/python scripts/trace_episode.py --seed 0 --max-steps 30000 --window 12`
- Added reusable loop diagnostic:
  - `scripts/analyze_action_loops.py`
- Ran:
  - `.venv/bin/python scripts/analyze_action_loops.py --seeds 0 --max-steps 30000 --tail 80 --count-after-step 16148 --output diagnostics/action_loop_seed0_video_check_round41.json`
- Result:
  - final score `17.0`, done step `27000`;
  - last reward step `16148`;
  - `10852` steps after the last reward;
  - after step `16148`, modes: `inactive:10716`, `center:74`, `intercept:61`, `initial:1`;
  - actions: `up:5423`, `down:5422`, `noop:7`;
  - tail shows repeated inactive rows with `ball_x=69`, `ball_y=0`, `dx=0`, `dy=0`, `target=100`, and paddle cycling through `92,113,124,110,89,77`.

### Supported And Rejected Hypotheses

- Supported: the user-visible loop is a real in-episode inactive-ball/action loop, not only browser GIF playback.
- Supported: the loop is mostly inactive-mode control after the last reward, not a normal active rally.
- Not yet tested: whether this is caused by EnvPool end-state behavior, a missing serve/reset action convention, or current inactive target control.

### Policy Change

- None. This round produced diagnostic evidence only. A policy change would need a multi-seed check because the current v3.19 baseline was validated with the same environment behavior.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_action_loops.py`

### Durable Assets

- Added:
  - `scripts/analyze_action_loops.py`
  - `diagnostics/action_loop_seed0_video_check_round41.json`
- Updated this progress log.

### Withdrawn

- Nothing withdrawn.

### New Structural Issues

- The evaluation can spend many thousands of steps after the last reward in an inactive-ball loop before `done`. This affects video review speed and may distort `env_steps`, but the score baseline remains comparable because previous formal runs used the same evaluation path.
- Need to distinguish whether the inactive loop is an environment terminal-state quirk, a missing serve/reset action, or an avoidable policy behavior.

### Next Automatic Exploration

- Run `scripts/analyze_action_loops.py` across a small seed set and compare whether long inactive tails correlate with final score, max-step truncation, or specific score states.
- If the inactive tail is widespread and score-neutral, consider an evaluation/report improvement that can trim video after the last reward for viewing while preserving formal scoring separately.

## Round 42 - 2026-05-10

### Current Best Baseline

- Retained best policy remains `ram_intercept_v3_19_y86_after_score22_bounce_up`.
- Formal 80-seed baseline remains mean `16.8625`, min `13`, max `19`.
- This round expanded the Round 41 inactive-loop diagnostic from seed `0` to seeds `0..9`.

### Hypothesis Map

1. Long inactive tails are common across many seeds.
   - Explains: video review may generally be slow because many episodes keep recording after the last score.
   - Evidence source: seed `0` recorded run had `10852` no-reward steps after the last reward.
   - Minimal diagnostic: run `scripts/analyze_action_loops.py` over seeds `0..9`.
   - Minimal change if true: prioritize report/video trimming or eval end-condition diagnostics.
   - If false: treat the issue as seed-family-specific.

2. Long inactive tails correlate with final score `17`.
   - Explains: seeds `0` and `9` in earlier baselines often shared related trajectories, and both can score `17`.
   - Evidence source: seed `0` loop ended at score `17`.
   - Minimal diagnostic: compare final scores and steps-after-last-reward for seeds `0..9`.
   - Minimal change if true: include final-score/state gates in future loop diagnostics.
   - If false: look for a different terminal-state trigger.

3. The inactive loop is not relevant to the retained score baseline.
   - Explains: score is already fixed at the last reward, while extra inactive steps only affect video/env_steps.
   - Evidence source: Round 41 showed no further rewards during the loop.
   - Minimal diagnostic: check whether seeds with inactive tails have stable final score at last reward.
   - Minimal change if true: avoid policy changes; improve viewing/report behavior instead.
   - If false: investigate whether actions can restart play and alter score.

### Diagnostics Run

- Ran:
  - `.venv/bin/python scripts/analyze_action_loops.py --seeds 0,1,2,3,4,5,6,7,8,9 --max-steps 30000 --tail 40 --output diagnostics/action_loop_seeds0_9_round42.json`
- Result:
  - seed `0`: score `17`, done step `27000`, last reward step `16148`, `10852` steps after last reward, mostly inactive;
  - seed `9`: score `17`, done step `27000`, last reward step `16144`, `10856` steps after last reward, mostly inactive;
  - seeds `1,2,3,4,5,6,7,8`: `steps_after_last_reward=0`; they ended on the final reward step.
- For the two loop seeds, the post-reward action counts are nearly symmetric `UP/DOWN` cycling with only seven `NOOP`s.

### Supported And Rejected Hypotheses

- Rejected: long inactive tails are common across all sampled seeds. They appeared in `2/10` seeds.
- Supported: in this sample, long inactive tails correlate with score `17` and done step `27000`.
- Supported: the loop does not change final score after the last reward in these sampled seeds; it mainly affects video length and `env_steps`.

### Policy Change

- None. This remains an evaluation/reporting diagnostic, not a proven policy improvement opportunity.

### Verification

- `scripts/analyze_action_loops.py` completed successfully and wrote `diagnostics/action_loop_seeds0_9_round42.json`.

### Durable Assets

- Added:
  - `diagnostics/action_loop_seeds0_9_round42.json`
- Updated this progress log.

### Withdrawn

- No code or strategy was withdrawn.

### New Structural Issues

- The `done` semantics can differ by seed family: most sampled episodes terminate exactly on the last reward, but score-`17` seeds `0` and `9` continue in an inactive loop until step `27000`.
- Video review of those seeds is misleading unless the report makes the long inactive tail obvious or offers a trimmed view.

### Next Automatic Exploration

- Run the loop diagnostic over the full 80-seed baseline records, or at least all seeds with final score `17`, to confirm whether the score-`17`/`27000` inactive-tail pattern is systematic.
- If confirmed, add a report/video option to trim or mark post-last-reward inactive tails for human review, while keeping formal no-video scoring unchanged.

## Round 43 - 2026-05-10

### Current Best Baseline

- Retained best policy remains `ram_intercept_v3_19_y86_after_score22_bounce_up`.
- Formal 80-seed baseline remains mean `16.8625`, min `13`, max `19`.
- Round 40 identified the current min floor as `22/80` pure y86 score-`13` seeds.

### Hypothesis Map

1. Local failure-family hypothesis: the remaining score-`13` seeds are one pure y86 score-chain family, not mixed high-down or low-up families.
   - Explains: all min seeds share the same final y86 tail at `x=202,y=86,dy=4,target=86,paddle=79`.
   - Evidence source: Round 40 floor summary and new full-chain diagnostic.
   - Minimal diagnostic: record all y86 x166 bounce opportunities over seeds `0..79`, including score pairs, action, mode, and v3.19 marker state.
   - Distinguishes from other hypotheses by showing whether score-13 seeds contain unique chain history before the shared tail.
   - If true: next experiments should target the pure chain separately. If false: resplit the floor before any counterfactual.

2. Basic model/action hypothesis: the pure family might be controlled by the first score `6-0` x166 bounce action.
   - Explains: the accepted v3.19 family is controlled by x166 bounce after a score-`2-2` marker, while the pure family still has `DOWN` at x166.
   - Evidence source: Round 39 accepted stateful x166 correction and Round 40 pure-family tail.
   - Minimal diagnostic: one-shot counterfactual at only score `6-0`, x166/y50, `DOWN -> NOOP` and `DOWN -> UP`, over all 80 seeds.
   - Distinguishes from earlier broad x166 rejection by testing only the first pure-family trigger rather than repeated or mixed score chains.
   - If true: a very narrow score-`6-0` rule could be considered. If false: do not patch x166; inspect earlier setup.

3. Control-structure hypothesis: the remaining floor needs an earlier family marker or phase model, not another local x166 action replacement.
   - Explains: v3.19 works only after an episode-level marker, and visually identical x166 geometry has opposite effects across chains.
   - Evidence source: v3.19 marker success, Round 39 broad x166 collapse, and the new score-chain split.
   - Minimal diagnostic: compare accepted `2-2|4-2|...` chains against pure `6-0|8-1|...` chains and test the earliest discriminating action point.
   - Distinguishes from a local-action hypothesis by checking whether the first pure local edit improves, is inert, or collapses.
   - If true: next work should build earlier setup/history diagnostics. If false: a local guarded action may still be viable.

### Diagnostics Run

- Added reusable chain diagnostic:
  - `scripts/analyze_y86_bounce_chains.py`
- Ran:
  - `.venv/bin/python -m py_compile scripts/analyze_y86_bounce_chains.py`
  - `.venv/bin/python scripts/analyze_y86_bounce_chains.py --seeds 0..79 --max-steps 30000 --output diagnostics/y86_bounce_chains_v3_19_round43.json`
- Result:
  - Score counts reproduced v3.19: `{13: 22, 16: 2, 17: 11, 18: 11, 19: 34}`.
  - `22/22` score-`13` seeds have y86 x166 events.
  - The score-`13` chain is exactly `6-0|8-1|10-2|12-3|14-4|16-5|18-6|20-7`.
  - The accepted v3.19 marker family is exactly `2-2|4-2|6-2|8-2|10-2|12-2|14-2|16-2|18-2|20-2` across `26` seeds.
  - `32` seeds have no y86 x166 chain events.
- Added two diagnostic-only counterfactual variants to `scripts/probe_counterfactual_actions.py`:
  - `once_y86_bounce_score60_x166_down_to_noop_h1`
  - `once_y86_bounce_score60_x166_down_to_up_h1`
- Ran:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --variants baseline,once_y86_bounce_score60_x166_down_to_noop_h1,once_y86_bounce_score60_x166_down_to_up_h1 --max-steps 30000 --jsonl diagnostics/counterfactual_y86_score60_bounce_x166_80seed_round43.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_y86_score60_bounce_x166_80seed_round43.jsonl --output diagnostics/y86_score60_bounce_x166_deltas_round43.json`
- Counterfactual result:
  - Baseline: mean `16.86`, min `13`, max `19`.
  - `6-0 DOWN -> NOOP`: fired on `22` seeds; mean `16.86`, min `13`, max `19`; no seed changed.
  - `6-0 DOWN -> UP`: fired on the same `22` seeds; every touched seed collapsed from `13` to `-15`; mean `9.16`, min `-15`, max `19`.

### Supported And Rejected Hypotheses

- Supported: the remaining floor is a pure y86 chain with a clean score-history signature, not a mixed residual floor.
- Rejected: the first score-`6-0` x166 bounce is a safe local action-improvement point.
- Supported: pure y86 x166 needs opposite treatment from the accepted score-`2-2` marker family; local geometry is not sufficient.
- Supported: `DOWN -> NOOP` at the first pure x166 bounce is inert, while `DOWN -> UP` is strongly harmful.

### Policy Change

- None. The only code changes were diagnostic tooling.
- No `pong_policy.py` change was made because the counterfactual rejected the only tested local action candidate.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_y86_bounce_chains.py`
- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`

### Durable Assets

- Added:
  - `scripts/analyze_y86_bounce_chains.py`
  - `diagnostics/y86_bounce_chains_v3_19_round43.json`
  - `diagnostics/counterfactual_y86_score60_bounce_x166_80seed_round43.jsonl`
  - `diagnostics/y86_score60_bounce_x166_deltas_round43.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No policy code was changed or withdrawn.
- The candidate score-`6-0` x166 `DOWN -> UP` rule is rejected and must not be retried without a new discriminator.
- The candidate score-`6-0` x166 `DOWN -> NOOP` rule is diagnostic-only and inert.

### New Structural Issues

- The accepted y86 controller state is still too specific: it handles the `2-2` chain but does not explain the pure `6-0` chain.
- The pure chain diverges before the first x166 bounce; a useful next diagnostic must inspect earlier serve, left-moving setup, paddle phase, or point history before score `6-0`.

### Next Automatic Exploration

- Compare the accepted `2-2` chain and rejected pure `6-0` chain before their first x166 event, especially reset/serve initial state, left-moving center-mode paddle phase, and the first high-down setup that leads into the y86 loop.
- Build the smallest diagnostic that summarizes pre-x166 history windows for both chains and identifies the earliest stable discriminator.

## Round 44 - 2026-05-10

### Current Best Baseline

- Retained best policy remains `ram_intercept_v3_19_y86_after_score22_bounce_up`.
- Formal 80-seed baseline remains mean `16.8625`, min `13`, max `19`.
- Round 43 rejected the first score-`6-0` x166 bounce as a safe local policy-edit point.

### Hypothesis Map

1. Local failure-family hypothesis: the accepted `2-2` y86 chain and pure `6-0` y86 floor chain may diverge in the last 80 frames before x166.
   - Explains: the two chains require opposite x166 actions, so a nearby state/action phase difference might be visible before the bounce.
   - Evidence source: Round 43 score-chain split and x166 counterfactual collapse.
   - Minimal diagnostic: capture fixed windows before the first x166 event for all seeds and compare offsets by family.
   - Distinguishes local phase from pure score/history by checking whether pre-bounce RAM/action rows differ before x166.
   - If true: test the earliest differing local action point. If false: inspect earlier point/serve history.

2. Basic model hypothesis: the local target/physics model may produce identical states for both families near the bounce, making score/history the only visible discriminator in the last approach.
   - Explains: same ball/paddle/target but different safe action implies hidden episode phase, not a coordinate error.
   - Evidence source: accepted stateful marker and rejected pure x166 action replacements.
   - Minimal diagnostic: summarize offsets `0..200` before first x166 by top row and mean paddle error.
   - Distinguishes model error from control history by showing whether target or paddle error differs locally.
   - If true: avoid local target offsets. If false: a local model correction may exist.

3. Control-structure hypothesis: the relevant discriminator is in the previous point or serve setup before the y86 approach.
   - Explains: the pure chain's score history starts at `6-0`, while accepted y86 starts at `2-2`; both may enter x166 with identical local state but different episode trajectory.
   - Evidence source: Round 43 chain split.
   - Minimal diagnostic: if offsets close to x166 are identical, compare earlier reset/left-phase/setup windows.
   - Distinguishes history-marker needs from another local rule by finding where the first stable divergence appears.
   - If true: build a previous-point/setup diagnostic next. If false: continue local counterfactuals.

### Diagnostics Run

- Added reusable pre-bounce window diagnostic:
  - `scripts/analyze_y86_pre_bounce_windows.py`
- Ran:
  - `.venv/bin/python -m py_compile scripts/analyze_y86_pre_bounce_windows.py`
  - `.venv/bin/python scripts/analyze_y86_pre_bounce_windows.py --seeds 0..79 --max-steps 2600 --window 220 --output diagnostics/y86_pre_bounce_windows_v3_19_round44.json`
- Result:
  - Family counts: `accepted_score22_chain: 26`, `pure_score60_floor_chain: 22`, `no_first_bounce: 32`.
  - First x166 steps reproduced prior split:
    - accepted: steps `608..615`;
    - pure: steps `1526,1529,1530,2193,2194,2197,2198`.
  - Offsets `80,40,20,12,8,4,2,1` before x166 have identical top RAM/action/target rows for both families except score.
  - At offset `0`, the only local difference is the accepted policy mode/action (`y86_bounce_after_score22_x166_down_correction`, `UP`) versus pure baseline (`intercept`, `DOWN`).
  - Earliest sampled differences appear before offset `120`:
    - offset `120`: accepted top `ball=(71,171), pad=132, action=UP, score=1-2`; pure top `ball=(71,186), pad=115, action=DOWN/NOOP split, score=5-0`.
    - offset `160`: accepted top `ball=(145,175), dy=8, target=151, pad=167`; pure top `ball=(145,53), dy=9, target=158.75, pad=167`.
    - offset `200`: both are center-mode left-moving, but pure has mixed `DOWN/NOOP` actions and a different previous-point y level.

### Supported And Rejected Hypotheses

- Rejected: there is a useful local discriminator in the last 80 frames before x166. The two families are locally identical through the y86 approach.
- Supported: target/physics rows near x166 do not explain the opposite safe action; local geometry and target error are identical.
- Supported: the decisive divergence is earlier than the y86 approach, likely in the previous point or serve/setup phase before the first x166 event.

### Policy Change

- None. This round was diagnostic-only.
- No `pong_policy.py` change was made because the local pre-bounce rows do not identify a safe guard or action edit.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_y86_pre_bounce_windows.py`

### Durable Assets

- Added:
  - `scripts/analyze_y86_pre_bounce_windows.py`
  - `diagnostics/y86_pre_bounce_windows_v3_19_round44.json`
- Updated this progress log.

### Withdrawn

- No policy code was changed or withdrawn.
- Local x166-neighborhood probing is now lower priority unless a new earlier marker justifies revisiting it.

### New Structural Issues

- The pure and accepted y86 chains enter the final y86 approach in the same local state. The controller needs a prior phase/history discriminator, not another local paddle-target rule.
- A future controller abstraction may need to represent point-family phase before the first y86 bounce, instead of accumulating independent score-gated patches.

### Next Automatic Exploration

- Build a diagnostic that aligns the previous point ending and the reset/serve transition immediately before the first y86 chain, comparing accepted `2-2` and pure `6-0` families.
- Focus on the earliest stable divergence around offsets `120..220` before x166, then use counterfactuals only after a causal candidate is isolated.

## Round 45 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_19_y86_after_score22_bounce_up`.
- Formal 80-seed baseline over seeds `0..79`: mean `16.8625`, min `13`, max `19`.
- Current floor before this round: the 22 pure y86 score-`13` seeds from Round 43/44.

### Hypothesis Map

1. Local failure-family hypothesis: the pure y86 floor may be controlled before x166, at the point-reset first active frame for score `6-0`.
   - Explains: Round 44 showed the last 80 frames before x166 are identical across accepted and pure y86 families, while the current point starts at score `6-0`.
   - Evidence source: `diagnostics/y86_pre_bounce_windows_v3_19_round44.json`.
   - Minimal diagnostic: align last reward and serve transition before first x166; then counterfactually change only score-`6-0` serve-initial `DOWN`.
   - Distinguishes from x166-local hypotheses because x166 `NOOP` was inert and x166 `UP` collapsed in Round 43.
   - If true: add one guarded one-shot serve-initial correction. If false: inspect previous-point ending or a broader phase marker.

2. Basic model/action hypothesis: the first active serve frame may set paddle phase for the later pure y86 loop even when later local states look identical.
   - Explains: prior accepted score `2-0` serve-initial correction improved the y86 floor, and pure score `6-0` has the same `x=126,y=126,target=126` initial frame.
   - Evidence source: Round 27 score-`2-0` serve-initial family and new setup diagnostic.
   - Minimal diagnostic: test `DOWN -> NOOP` and `DOWN -> UP` at only score `6-0`, mode `initial`, `x=124..128,y=124..128,paddle=96..106`.
   - Distinguishes action direction from "avoid DOWN" because both variants are tested separately.
   - If true: choose the smaller/no less safe action change. If false: do not touch serve-initial.

3. Control-structure hypothesis: y86 corrections may share a broader point-reset phase mechanism rather than independent local x166 rules.
   - Explains: accepted y86 setup fixes and serve-initial fixes all affect future repeated y86 loops through earlier phase.
   - Evidence source: accumulated y86 score-chain corrections and Round 44 local identity near x166.
   - Minimal diagnostic: after accepting or rejecting score-`6-0`, trace the new floor and consider whether these one-shot score gates can be generalized.
   - Distinguishes structure from patchwork by checking whether score `6-0` behaves like score `2-0` serve-initial.
   - If true: future induction round should look for a smaller phase/controller abstraction. If false: keep gates separate.

### Diagnostics Run

- Added setup-history diagnostic:
  - `scripts/analyze_y86_chain_setup.py`
- Ran:
  - `.venv/bin/python -m py_compile scripts/analyze_y86_chain_setup.py`
  - `.venv/bin/python scripts/analyze_y86_chain_setup.py --seeds 0..79 --max-steps 2600 --output diagnostics/y86_chain_setup_v3_19_round45.json`
- Setup result:
  - Family counts: `accepted_score22_chain: 26`, `pure_score60_floor_chain: 22`, `no_first_bounce: 32`.
  - Both accepted and pure chains reach first x166 exactly `116` steps after the last reward.
  - The first right-moving active frame after the last reward:
    - accepted: score `2-2`, `x=126,y=126,pad=100,target=126,action=DOWN,mode=initial`;
    - pure: score `6-0`, `x=126,y=126,pad=101,target=126,action=DOWN,mode=initial`.
  - First high-down and first y86 approach anchors after reward are identical except score.
- Added diagnostic-only counterfactual variants:
  - `once_y86_score60_serve_initial_down_to_noop_h1`
  - `once_y86_score60_serve_initial_down_to_up_h1`
- Ran:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --variants baseline,once_y86_score60_serve_initial_down_to_noop_h1,once_y86_score60_serve_initial_down_to_up_h1 --max-steps 30000 --jsonl diagnostics/counterfactual_y86_score60_serve_initial_80seed_round45.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_y86_score60_serve_initial_80seed_round45.jsonl --output diagnostics/y86_score60_serve_initial_deltas_round45.json`
- Counterfactual result:
  - Baseline: mean `16.86`, min `13`, max `19`.
  - `6-0 serve-initial DOWN -> NOOP`: mean `17.14`, min `14`, max `19`; 22 improved, 0 worsened, 58 unchanged.
  - `6-0 serve-initial DOWN -> UP`: same score result, 22 improved, 0 worsened, 58 unchanged.
  - Both variants improved exactly the previous 22 score-`13` floor seeds by `+1`.

### Policy Change

- Accepted one minimal policy change:
  - score `6-0`, mode `initial`, `x=124..128,y=124..128,paddle=96..106`, baseline `DOWN -> NOOP`, one use per episode.
- Chose `NOOP` over `UP` because both had identical 80-seed deltas and `NOOP` is the smaller action change.
- Added guard tests for narrow trigger, one-shot behavior, and applied policy action.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `40` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_20_y86_score60_serve_initial_noop --notes "Round 45: solidify score 6-0 y86 serve-initial DOWN->NOOP after full 80-seed counterfactual guard; v3_19 mean 16.8625 min 13 max 19 to probe mean 17.14 min 14 max 19" --no-video`
  - Result: mean `17.1375`, min `14`, max `19`, env steps `727217`.
- Seed-level delta versus v3.19:
  - Improved: seeds `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79` by `+1`.
  - Worsened: none.
  - Unchanged: 58 seeds.

### Durable Assets

- Added:
  - `scripts/analyze_y86_chain_setup.py`
  - `diagnostics/y86_chain_setup_v3_19_round45.json`
  - `diagnostics/counterfactual_y86_score60_serve_initial_80seed_round45.jsonl`
  - `diagnostics/y86_score60_serve_initial_deltas_round45.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The score-`6-0` x166 action candidates from Round 43 remain rejected; the accepted intervention is earlier at serve-initial.

### New Structural Issues

- The score-`6-0` result supports a broader y86 phase mechanism: some y86 failures are best disrupted at point reset, not at the visible miss or x166 bounce.
- The retained policy has accumulated multiple score/phase-specific y86 one-shot rules; after more accepted local rules, an induction round should test whether these share a smaller phase-controller model.

### Next Automatic Exploration

- Trace and re-split the new v3.20 score-`14` floor seeds.
- Determine whether the remaining floor is still pure y86, a different y86 score-chain, or a new non-y86 family before making another policy change.

## Round 46 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_20_y86_score60_serve_initial_noop`.
- Formal 80-seed baseline: mean `17.1375`, min `14`, max `19`.
- New floor seeds: `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79`.

### Hypothesis Map

1. Local failure-family hypothesis: after v3.20, the floor may have moved from pure y86 to high-down y165.
   - Explains: the y86 serve-initial fix improved all previous score-`13` floor seeds by `+1`; the next repeated loss family may now be exposed.
   - Evidence source: v3.20 formal seed-level deltas and prior high-down diagnostics.
   - Minimal diagnostic: trace all new score-`14` seeds with point windows and summarize final loss families.
   - Distinguishes from residual y86 by checking final signatures and loss patterns.
   - If true: run high-down-specific diagnostics. If false: continue y86 chain analysis.

2. Basic model/action hypothesis: the new high-down floor may reuse the same dy=9 bounce mechanism as the accepted score-`4-0` correction.
   - Explains: score-`4-0` high-down bounce `DOWN -> NOOP` was previously causal and safe.
   - Evidence source: Round 37 accepted high-down score-`4-0` fix.
   - Minimal diagnostic: inspect tail offsets and test score-`8-0` dy=9 bounce `DOWN -> NOOP/UP`.
   - Distinguishes from later contact-frame hypotheses because the final frame has large paddle error but prior local final-frame fixes were often inert.
   - If true: add a score-`8-0` one-shot guard. If false: inspect earlier high-down setup/history.

3. Control-structure hypothesis: high-down fixes may also be a score-chain phase family, similar to y86.
   - Explains: score-`4-0` and score-`8-0` share bounce geometry but require score guards because ungated high-down x146 regressed hidden score-`0-0` seeds.
   - Evidence source: Round 37 high-down score gating and new score-`8-0` floor.
   - Minimal diagnostic: test only score `8-0` before considering broader score-chain generalization.
   - Distinguishes safe score-chain extension from unsafe geometry-only rules.
   - If true: later induction should compare high-down score phases. If false: avoid extending score-gated bounce rules.

### Diagnostics Run

- Traced new score-`14` floor seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79 --max-steps 30000 --window 180 --tail 0 --jsonl diagnostics/point_traces_score14_all_v3_20_round46.jsonl`
- Summarized families:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score14_all_v3_20_round46.jsonl --output diagnostics/score14_floor_families_v3_20_round46.json`
- Summarized high-down tail offsets:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score14_all_v3_20_round46.jsonl --pattern loss/high_down/paddle_above_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/high_down_y165_tail_offsets_v3_20_round46.json`
- Result:
  - `22/22` score-`14` seeds are `repeated_high_down_y165`.
  - There are `154` loss records, all `loss/high_down/paddle_above_target`.
  - Final signature is always `x=202,y=165,dy=8,target=165,pad=146,error=-19`.
  - Loss score chain is `8-0,10-1,12-2,14-3,16-4,18-5,20-6`.
  - Tail offset `15` inspection showed the first repeated loss has a score-`8-0` dy=9 bounce at `x=146,y=53,pad=146,target=156.5,action=DOWN`.
- Added diagnostic-only variants:
  - `once_high_down_y165_bounce_score80_x146_down_to_noop_h1`
  - `once_high_down_y165_bounce_score80_x146_down_to_up_h1`
- Ran:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --variants baseline,once_high_down_y165_bounce_score80_x146_down_to_noop_h1,once_high_down_y165_bounce_score80_x146_down_to_up_h1 --max-steps 30000 --jsonl diagnostics/counterfactual_high_down_score80_bounce_x146_80seed_round46.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score80_bounce_x146_80seed_round46.jsonl --output diagnostics/high_down_score80_bounce_x146_deltas_round46.json`
- Counterfactual result:
  - Baseline v3.20: mean `17.14`, min `14`, max `19`.
  - score `8-0` dy=9 bounce `DOWN -> NOOP`: mean `17.41`, min `15`, max `19`; 22 improved, 0 worsened, 58 unchanged.
  - score `8-0` dy=9 bounce `DOWN -> UP`: mean `17.14`, min `14`, max `19`; no seed changed.

### Supported And Rejected Hypotheses

- Supported: after v3.20, the min floor moved from y86 to repeated high-down y165.
- Supported: the score-`8-0` dy=9 bounce is a safe causal point for the new floor when changed to `NOOP`.
- Rejected: `DOWN -> UP` at the same score-`8-0` bounce is useful; it was inert.

### Policy Change

- None in this round. This was diagnostic-only before solidification.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`

### Durable Assets

- Added:
  - `diagnostics/point_traces_score14_all_v3_20_round46.jsonl`
  - `diagnostics/score14_floor_families_v3_20_round46.json`
  - `diagnostics/high_down_y165_tail_offsets_v3_20_round46.json`
  - `diagnostics/counterfactual_high_down_score80_bounce_x146_80seed_round46.jsonl`
  - `diagnostics/high_down_score80_bounce_x146_deltas_round46.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - this progress log.

### Withdrawn

- No policy code was changed or withdrawn.
- `DOWN -> UP` at score `8-0` high-down bounce remains rejected/inert.

### New Structural Issues

- High-down now mirrors the y86 pattern: a repeated score chain is exposed one layer at a time by safe early phase corrections.
- The existing policy has now accepted multiple local rules since the last structural induction; an induction round is becoming due after this high-down extension is solidified.

### Next Automatic Exploration

- Solidify the score-`8-0` high-down bounce `DOWN -> NOOP` as a one-shot guarded rule, then run full 80-seed validation.

## Round 47 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_20_y86_score60_serve_initial_noop`.
- Formal 80-seed score: mean `17.1375`, min `14`, max `19`.
- Round 46 counterfactual supported a score-`8-0` high-down bounce `DOWN -> NOOP` rule.

### Hypothesis Map

1. Local failure-family hypothesis: one score-`8-0` high-down bounce correction should lift the current floor from `14` to `15`.
   - Explains: every current floor seed has the same first high-down loss at score `8-0`.
   - Evidence source: Round 46 point traces and counterfactual deltas.
   - Minimal policy change: add one-shot score-`8-0` dy=9 bounce `DOWN -> NOOP`.
   - Distinguishes from broader high-down geometry by using score and one-shot guards.
   - If true: formal v3.21 should match counterfactual. If false: withdraw code and keep diagnostics.

2. Basic model/action hypothesis: preserving bounce-frame phase with `NOOP` is the causal mechanism, as in score `4-0`.
   - Explains: `DOWN -> NOOP` improves while `DOWN -> UP` is inert.
   - Evidence source: Round 37 score-`4-0` and Round 46 score-`8-0` probes.
   - Minimal validation: unit tests for guard and full 80-seed formal eval.
   - If true: retain. If false: do not generalize high-down bounce rules.

3. Control-structure hypothesis: this is another score-chain phase rule and may need later induction with the score-`4-0` high-down rule.
   - Explains: same geometry at score `0-0` was unsafe, but score `4-0` and `8-0` are safe.
   - Evidence source: rejected ungated high-down x146 and accepted score-gated fixes.
   - Minimal validation: retain only if no seed regressions in the full 80-seed set.
   - If true: next induction should inspect whether a high-down phase marker can replace multiple score gates.

### Policy Change

- Accepted one minimal policy change:
  - score `8-0`, dy=9 high-down bounce, `x=144..148,y=50..56,paddle=143..150,target=154..162`, baseline `DOWN -> NOOP`, one use per episode.
- Added tests for narrow trigger, one-shot behavior, and applied policy action.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `43` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_21_high_down_score80_bounce_noop --notes "Round 47: solidify score 8-0 high-down bounce x144-148 y50-56 dy9 DOWN->NOOP after full 80-seed counterfactual guard; v3_20 mean 17.1375 min 14 max 19 to probe mean 17.41 min 15 max 19" --no-video`
  - Result: mean `17.4125`, min `15`, max `19`, env steps `717207`.
- Seed-level delta versus v3.20:
  - Improved: seeds `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79` by `+1`.
  - Worsened: none.
  - Unchanged: 58 seeds.

### Durable Assets

- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.

### New Structural Issues

- The high-down score-gated bounce rules now exist at score `4-0` and `8-0`; this likely deserves an induction pass before adding several more parallel score gates.

### Next Automatic Exploration

- Run an induction diagnostic: compare accepted local rules since the last induction and determine whether y86 and high-down score-gated rules can be represented by a smaller phase/family marker model.
- If induction is inconclusive, trace and split the new v3.21 score-`15` floor before adding another policy rule.

## Round 48 - 2026-05-10

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_21_high_down_score80_bounce_noop`.
- Formal 80-seed score: mean `17.4125`, min `15`, max `19`.
- New floor seeds: `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79`.

### Hypothesis Map

1. Local failure-family hypothesis: after v3.21, the min floor may have moved back from high-down y165 into a later y86/mid-down chain.
   - Explains: the same 22 seeds improved by `+1` under the score-`8-0` high-down correction, so the next repeated family should now be exposed.
   - Evidence source: v3.21 formal eval and Round 46 high-down floor trace.
   - Minimal diagnostic: trace all score-`15` floor seeds and summarize loss families, score chain, and tail offsets.
   - Distinguishes from residual high-down by final loss pattern and score-chain signatures.
   - If true: inspect y86 setup/history before counterfactuals. If false: continue high-down chain analysis.

2. Basic model/action hypothesis: the next y86 loss is likely a phase/action-history problem, not a final-frame target estimate problem.
   - Explains: previous y86 final-frame counterfactuals were inert or harmful, while accepted y86 fixes act earlier at setup, serve, or family-marker phases.
   - Evidence source: `failures.jsonl` y86 final-frame rejection, Round 43 x166 rejection, and Round 45 serve-initial success.
   - Minimal diagnostic: summarize tail offsets and identify whether the paddle is locally on target before late oscillation.
   - Distinguishes from target-model fixes by checking whether late target error appears only after a stable earlier alignment.
   - If true: next experiment should align point reset to first x166. If false: revisit target smoothing with broad validation.

3. Control-structure hypothesis: the accepted y86/high-down local rules share score-chain phase structure, but the evidence is not yet strong enough to replace them with one controller abstraction.
   - Explains: y86 and high-down rules are both score/phase dependent, while ungated geometry-based generalizations have caused collapses.
   - Evidence source: accepted v3.19/v3.20/v3.21 deltas and rejected y86 x166/high-down ungated probes.
   - Minimal diagnostic: write an induction artifact listing accepted rules, rejected generalizations, and the new floor family.
   - Distinguishes real structure from score-gate accumulation by requiring the marker model to also explain negative cases.
   - If true: later refactor can collapse flags into phase markers. If false: continue narrow guarded rules.

### Diagnostics Run

- Traced current floor seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_score15_all_v3_21_round48.jsonl`
- Summarized families:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score15_all_v3_21_round48.jsonl --output diagnostics/score15_floor_families_v3_21_round48.json`
- Summarized y86/mid-down tail offsets:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score15_all_v3_21_round48.jsonl --pattern loss/mid_down/near_target --offsets 1,2,3,4,5,6,7,8,10,12,14,15,16,20 --output diagnostics/mid_down_y86_tail_offsets_v3_21_round48.json`
- Counted loss score-chain pairs from the trace.
- Wrote induction artifact:
  - `diagnostics/rule_induction_round48.json`

### Results

- `22/22` floor seeds still finish at score `15`, with `21` wins and `6` losses each.
- The exposed floor is no longer high-down:
  - Loss pattern: `132` records of `loss/mid_down/near_target`.
  - Final signature: `x=202,y=86,dy=4,target=86,pad=79,error=-7`.
  - Loss score chain: `10-0,12-1,14-2,16-3,18-4,20-5`, each appearing once per floor seed.
- Tail offsets show stable earlier alignment followed by late oscillation:
  - offset `20`: `x=126,y=81,target=77,paddle=77,NOOP`.
  - offset `16`: `x=142,y=65,target=77,paddle=77,NOOP`.
  - offset `10`: `x=166,y=50,dy=5,target=82.5,paddle=78,DOWN`.
  - offset `3`: `x=194,y=78,target=78,paddle=89,NOOP,mode=mid_down_precontact_up_correction`.
  - offset `1`: `x=202,y=86,target=86,paddle=79,DOWN`.

### Supported And Rejected Hypotheses

- Supported: the new v3.21 min floor is a later y86/mid-down chain, not residual high-down y165.
- Supported: accepted local rules are score-chain/phase dependent, not pure coordinate corrections.
- Supported: a structural phase-marker model is plausible but not yet safe to implement because it must explain both accepted and rejected cases.
- Rejected for this round: adding another policy rule without a causal counterfactual. The score-`10-0` y86 chain has only a mechanism trace so far.

### Policy Change

- None. This was diagnostic and induction-only.

### Verification

- No policy code changed in this round.
- Diagnostic outputs were generated successfully.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score15_all_v3_21_round48.jsonl`
  - `diagnostics/score15_floor_families_v3_21_round48.json`
  - `diagnostics/mid_down_y86_tail_offsets_v3_21_round48.json`
  - `diagnostics/rule_induction_round48.json`
- Updated:
  - this progress log.

### Withdrawn

- No strategy change was made, so nothing was withdrawn.
- No negative result was deleted.

### New Structural Issues

- The retained policy now alternates between y86 and high-down score-chain floors as each earlier layer is fixed.
- A future controller abstraction should model point-family phase/history, but only after a diagnostic classifier can reproduce known safe and unsafe cases.

### Git Status Summary

- Working tree remains dirty with retained policy/test/docs/eval artifacts from v3.20/v3.21 and new Round 48 diagnostics.
- No unrelated files were reverted.

### Next Automatic Exploration

- Build or extend a setup-history diagnostic for the v3.21 score-`10-0` y86 chain, aligned from previous reward/reset through first x166 event.
- Compare this score-`10-0` chain against the accepted score-`6-0` serve-initial family and the rejected score-`6-0` x166 action probe before testing any new action override.

## Round 49 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_21_high_down_score80_bounce_noop`.
- Formal 80-seed score: mean `17.4125`, min `15`, max `19`.
- Current floor seeds from Round 48: `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79`.

### Hypothesis Map

1. Local failure-family hypothesis: the score-`15` floor is a score-`10-0` y86 reset/serve family analogous to the accepted score-`6-0` serve-initial family.
   - Explains: Round 48 showed every floor seed first loses the new chain at score `10-0`, with the same x166 and final y86 tail as prior pure y86 families.
   - Evidence source: `diagnostics/score15_floor_families_v3_21_round48.json` and `diagnostics/mid_down_y86_tail_offsets_v3_21_round48.json`.
   - Minimal diagnostic: align score-`10-0` from previous reward through first x166 and compare with Round 45 score-`6-0` setup.
   - Distinguishes from direct x166 action hypotheses because score-`6-0` x166 `DOWN -> UP` was harmful while serve-initial was safe.
   - If true: test only score-`10-0` serve-initial `DOWN -> NOOP/UP`. If false: inspect a different earlier anchor.

2. Basic model/action hypothesis: a one-frame reset/initial action can change later y86 phase even when x166 and final tail are locally identical.
   - Explains: the score-`10-0` chain reaches x166 116 steps after reward, matching the accepted score-`6-0` timing and geometry.
   - Evidence source: `diagnostics/y86_chain_setup_v3_19_round45.json` and new setup diagnostic.
   - Minimal experiment: full 80-seed counterfactual for score-`10-0` initial `DOWN -> NOOP/UP`.
   - Distinguishes from target smoothing because the intervention is before normal intercept mode begins.
   - If true: solidify the smallest safe action. If false: do not generalize serve-initial y86 fixes by score.

3. Control-structure hypothesis: y86 serve-initial fixes may be one repeated reset-phase rule, but for now they still need score/family guards.
   - Explains: score-`6-0` and score-`10-0` share entry geometry, while earlier score-`2-0` retained `UP`; different score phases can prefer different equivalent or non-equivalent actions.
   - Evidence source: accepted score-`2-0`, `6-0`, and current score-`10-0` serve-initial probes.
   - Minimal validation: accept only if 80-seed guard has no regressions, then leave broader unification for later induction.
   - Distinguishes from a broad geometry-only rule by preserving score guard and one-shot state.
   - If true: future induction can test whether `NOOP` is a safe default for later pure-y86 reset phases. If false: continue separate score guards.

### Diagnostics Run

- Ran score-`10-0` setup diagnostic:
  - `.venv/bin/python scripts/analyze_y86_chain_setup.py --seeds 5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79 --max-steps 5000 --output diagnostics/y86_score100_chain_setup_v3_21_round49.json`
- Added diagnostic-only counterfactual variants:
  - `once_y86_score100_serve_initial_down_to_noop_h1`
  - `once_y86_score100_serve_initial_down_to_up_h1`
- Verified diagnostic script:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
- Ran full 80-seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --variants baseline,once_y86_score100_serve_initial_down_to_noop_h1,once_y86_score100_serve_initial_down_to_up_h1 --max-steps 30000 --jsonl diagnostics/counterfactual_y86_score100_serve_initial_80seed_round49.jsonl`
- Summarized deltas:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_y86_score100_serve_initial_80seed_round49.jsonl --output diagnostics/y86_score100_serve_initial_deltas_round49.json`

### Diagnostic Results

- Setup diagnostic:
  - `22/22` floor seeds first hit y86 x166 at score `10-0`.
  - First right-moving reset frame: `x=126,y=126,paddle=101,target=126,action=DOWN,mode=initial`.
  - First high-down anchor: `x=142,y=169,dy=8,paddle=150,target=151,action=NOOP`.
  - First y86 approach anchor: `x=82,y=125,dy=-4,paddle=82,target=77,action=UP`.
  - First x166 bounce comes `116` steps after the last reward for all 22 seeds.
- Counterfactual:
  - Baseline v3.21: mean `17.41`, min `15`, max `19`.
  - score `10-0` serve-initial `DOWN -> NOOP`: mean `17.69`, min `16`, max `19`; 22 improved, 0 worsened, 58 unchanged.
  - score `10-0` serve-initial `DOWN -> UP`: mean `17.69`, min `16`, max `19`; 22 improved, 0 worsened, 58 unchanged.

### Supported And Rejected Hypotheses

- Supported: the score-`10-0` floor shares the same reset-to-x166 setup structure as the accepted pure-y86 score-`6-0` family.
- Supported: the causal point is again at serve-initial/reset phase, not the later x166 or final contact frame.
- Supported: `NOOP` and `UP` are equivalent on this 80-seed guard; choose `NOOP` as the smaller action change.
- Rejected for now: broad controller refactor. This result adds another score-guarded reset rule but does not yet prove a safe general family marker.

### Policy Change

- Accepted one minimal policy change:
  - score `10-0`, mode `initial`, `x=124..128,y=124..128,paddle=96..106`, baseline `DOWN -> NOOP`, one use per episode.
- Added guard tests for narrow trigger, one-shot behavior, and applied policy action.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `46` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_22_y86_score100_serve_initial_noop --notes "Round 49: solidify score 10-0 y86 serve-initial DOWN->NOOP after setup diagnostic and full 80-seed counterfactual guard; v3_21 mean 17.4125 min 15 max 19 to probe mean 17.69 min 16 max 19" --no-video`
  - Result: mean `17.6875`, min `16`, max `19`, env steps `720397`.
- Seed-level delta versus v3.21:
  - Improved: seeds `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79` by `+1`.
  - Worsened: none.
  - Unchanged: 58 seeds.

### Durable Assets

- Added:
  - `diagnostics/y86_score100_chain_setup_v3_21_round49.json`
  - `diagnostics/counterfactual_y86_score100_serve_initial_80seed_round49.jsonl`
  - `diagnostics/y86_score100_serve_initial_deltas_round49.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `report.html`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The alternative score-`10-0` `DOWN -> UP` probe is not retained because it had identical score deltas to `NOOP` but is a larger action change.

### New Structural Issues

- The pure-y86 reset-phase mechanism now has accepted score `6-0` and `10-0` variants, while score `2-0` retained `UP`.
- A future induction should test whether later pure-y86 reset phases can be represented by one family marker, but only after checking the next floor and known negative x166 cases.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.22 policy, tests, docs, eval logs, and diagnostic assets.
- No unrelated files were reverted.

### Next Automatic Exploration

- Trace the new v3.22 score-`16` floor seeds and split the next exposed family before adding any further rule.
- If the next floor is again pure y86, compare its reset score-chain entry with the accepted score `6-0` and `10-0` families before any counterfactual.

## Round 50 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_22_y86_score100_serve_initial_noop`.
- Formal 80-seed score: mean `17.6875`, min `16`, max `19`.
- Extracted score-`16` floor seeds from trial `43`: `2,5,7,8,12,14,15,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79`.

### Hypothesis Map

1. Local failure-family hypothesis: after v3.22, the main floor may have shifted back to the high-down y165 score-chain.
   - Explains: score-`10-0` y86 serve-initial lifted the previous 22 y86 floor seeds, so their next repeated loss family should be exposed.
   - Evidence source: v3.22 seed-level deltas and prior high-down score-chain behavior at scores `4-0` and `8-0`.
   - Minimal diagnostic: trace all score-`16` floor seeds and summarize loss families, score-chain pairs, and tail offsets.
   - Distinguishes from residual y86 by final signatures and loss pattern counts.
   - If true: test only the next high-down score-chain bounce. If false: inspect the mixed floor before policy changes.

2. Basic model/action hypothesis: score-`12-0` high-down bounce may reuse the same dy=9 `DOWN -> NOOP` causal point as score `4-0` and `8-0`.
   - Explains: Round 37 and Round 47 accepted the same bounce geometry with score guards, while `UP` variants were harmful or inert.
   - Evidence source: `diagnostics/high_down_y165_tail_offsets_v3_22_round50.json` and previous high-down deltas.
   - Minimal experiment: full 80-seed counterfactual for score-`12-0` bounce `DOWN -> NOOP/UP`.
   - Distinguishes from late-contact or x150 fixes because prior x150 and x166 high-down probes were inert or unsafe.
   - If true: add a one-shot score-`12-0` guard. If false: do not continue extending the high-down chain.

3. Control-structure hypothesis: high-down bounce rules are a repeated score-chain family, but geometry-only generalization remains unsafe.
   - Explains: score `4-0`, `8-0`, and now `12-0` share geometry, while ungated x146 previously regressed hidden score-`0-0` seeds.
   - Evidence source: accepted score-gated high-down rules and rejected ungated high-down probes.
   - Minimal validation: keep the score guard and one-shot limit; defer controller refactor until a family marker explains both safe and unsafe cases.
   - Distinguishes safe local extension from broad refactor by requiring no full-set regression.
   - If true: retain narrow rule and revisit induction after more accumulated high-down rules. If false: stop score-chain extension.

### Diagnostics Run

- Extracted v3.22 score-`16` seeds from `episodes.jsonl`.
- Traced all floor seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 2,5,7,8,12,14,15,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_score16_all_v3_22_round50.jsonl`
- Summarized floor families:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score16_all_v3_22_round50.jsonl --output diagnostics/score16_floor_families_v3_22_round50.json`
- Summarized high-down tail offsets:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score16_all_v3_22_round50.jsonl --pattern loss/high_down/paddle_above_target --offsets 1,2,3,4,5,6,7,8,10,12,14,15,16,20 --output diagnostics/high_down_y165_tail_offsets_v3_22_round50.json`
- Added diagnostic-only variants:
  - `once_high_down_y165_bounce_score120_x146_down_to_noop_h1`
  - `once_high_down_y165_bounce_score120_x146_down_to_up_h1`
- Verified diagnostic script:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
- Ran full 80-seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --variants baseline,once_high_down_y165_bounce_score120_x146_down_to_noop_h1,once_high_down_y165_bounce_score120_x146_down_to_up_h1 --max-steps 30000 --jsonl diagnostics/counterfactual_high_down_score120_bounce_x146_80seed_round50.jsonl`
- Summarized deltas:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score120_bounce_x146_80seed_round50.jsonl --output diagnostics/high_down_score120_bounce_x146_deltas_round50.json`

### Diagnostic Results

- Floor split:
  - 22 seeds have exactly 5 repeated `loss/high_down/paddle_above_target` losses.
  - Seeds `2` and `15` have mixed early losses: two mid-up, one mid-down, one low-up, and one high-down each.
- Dominant high-down signature:
  - `110` records at `x=202,y=165,dy=8,target=165,pad=146,error=-19`.
  - Tail offset `15`: `x=146,y=53,dy=9,target=156.5,paddle=146,action=DOWN`.
  - High-down score-chain for the 22 repeated seeds: `12-0,14-1,16-2,18-3,20-4`.
- Counterfactual:
  - Baseline v3.22: mean `17.69`, min `16`, max `19`.
  - score `12-0` high-down bounce `DOWN -> NOOP`: mean `17.96`, min `16`, max `19`; 22 improved, 0 worsened, 58 unchanged.
  - score `12-0` high-down bounce `DOWN -> UP`: mean `17.69`, min `16`, max `19`; no seed changed.

### Supported And Rejected Hypotheses

- Supported: the dominant floor after v3.22 is high-down y165 again, but seeds `2` and `15` are a separate mixed early-loss family.
- Supported: score-`12-0` high-down dy=9 bounce `DOWN -> NOOP` is safe and causal for the 22 repeated high-down seeds.
- Rejected: score-`12-0` `DOWN -> UP` as useful; it was score-inert.
- Rejected for this round: claiming the min floor is solved. Min remains `16` because seeds `2` and `15` are untouched.

### Policy Change

- Accepted one minimal policy change:
  - score `12-0`, dy=9 high-down bounce, `x=144..148,y=50..56,paddle=143..150,target=154..162`, baseline `DOWN -> NOOP`, one use per episode.
- Added guard tests for narrow trigger, one-shot behavior, and applied policy action.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `49` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_23_high_down_score120_bounce_noop --notes "Round 50: solidify score 12-0 high-down bounce x144-148 y50-56 dy9 DOWN->NOOP after score16 floor trace and full 80-seed counterfactual guard; v3_22 mean 17.6875 min 16 max 19 to probe mean 17.96 min 16 max 19" --no-video`
  - Result: mean `17.9625`, min `16`, max `19`, env steps `713027`.
- Seed-level delta versus v3.22:
  - Improved: seeds `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79` by `+1`.
  - Worsened: none.
  - Unchanged: 58 seeds, including min seeds `2` and `15`.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score16_all_v3_22_round50.jsonl`
  - `diagnostics/score16_floor_families_v3_22_round50.json`
  - `diagnostics/high_down_y165_tail_offsets_v3_22_round50.json`
  - `diagnostics/counterfactual_high_down_score120_bounce_x146_80seed_round50.jsonl`
  - `diagnostics/high_down_score120_bounce_x146_deltas_round50.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `report.html`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The score-`12-0` `DOWN -> UP` variant remains diagnostic-only and inert.

### New Structural Issues

- The high-down bounce chain is now accepted at scores `4-0`, `8-0`, and `12-0`, but ungated geometry is still known unsafe.
- The actual min floor is now the mixed early-loss seed family `2` and `15`, not the repeated high-down chain.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.23 policy, tests, docs, eval logs, and diagnostic assets.
- No unrelated files were reverted.

### Next Automatic Exploration

- Trace seeds `2` and `15` under v3.23 in detail and compare their five early losses against existing rejected/accepted low-up, mid-up, mid-down, and high-down families.
- Do not add another high-down chain rule until the mixed min floor is split and causally probed.

## Round 51 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_23_high_down_score120_bounce_noop`.
- Formal 80-seed score: mean `17.9625`, min `16`, max `19`.
- Current min seeds after v3.23: `2` and `15`.

### Hypothesis Map

1. Local failure-family hypothesis: the remaining min floor is a deterministic mixed early-loss sequence shared by seeds `2` and `15`.
   - Explains: v3.23 lifted the repeated high-down family to `17`, leaving only seeds `2` and `15` at `16`.
   - Evidence source: v3.23 formal eval and Round 50 floor split.
   - Minimal diagnostic: trace seeds `2` and `15` with wider point windows and summarize each loss tail.
   - Distinguishes from high-down score-chain by point indices and mixed loss patterns.
   - If true: test the earliest shared loss anchor before later low-up/mid-up/high-down losses. If false: re-extract floor from eval logs.

2. Basic model/action hypothesis: the opening score-`0-0` mid-down miss is caused by an overactive final `UP` while the paddle is already recovering downward too fast.
   - Explains: both min seeds share the first loss at step `124`, with final `x=203,y=154,target=154,paddle=166,action=UP` and previous frame `x=199,y=150,paddle=155`.
   - Evidence source: `diagnostics/mixed_seed2_15_mid_down_tail_offsets_v3_23_round51.json`.
   - Minimal experiment: full 80-seed counterfactual for the narrow score-`0-0` final approach `UP -> NOOP/DOWN`.
   - Distinguishes from target smoothing by testing only one final action replacement with a strong score/geometry/paddle-delta guard.
   - If true: add a one-shot `UP -> NOOP` guard. If false: inspect earlier opening trajectory.

3. Control-structure hypothesis: this mixed floor is not part of the y86/high-down score-chain controller; it needs a separate opening-family marker or one-shot guard.
   - Explains: the losses happen in the first seven points and include mid-down, mid-up, low-up, and high-down families.
   - Evidence source: point trace `diagnostics/point_traces_score16_mixed_seed2_15_v3_23_round51.jsonl`.
   - Minimal validation: accept only a guard that touches no unrelated seeds in full 80-seed counterfactual.
   - Distinguishes from broad controller changes by limiting the intervention to score `0-0` and one use.
   - If true: retain narrow opening-family guard. If false: avoid adding global mid-down behavior.

### Diagnostics Run

- Traced min seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 2,15 --max-steps 30000 --window 260 --tail 0 --jsonl diagnostics/point_traces_score16_mixed_seed2_15_v3_23_round51.jsonl`
- Summarized tails for each mixed loss family:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score16_mixed_seed2_15_v3_23_round51.jsonl --pattern loss/mid_down/paddle_below_target --offsets 1,2,3,4,5,6,8,10,12,16,20,24 --output diagnostics/mixed_seed2_15_mid_down_tail_offsets_v3_23_round51.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score16_mixed_seed2_15_v3_23_round51.jsonl --pattern loss/mid_up/paddle_below_target --offsets 1,2,3,4,5,6,8,10,12,16,20,24 --output diagnostics/mixed_seed2_15_mid_up_tail_offsets_v3_23_round51.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score16_mixed_seed2_15_v3_23_round51.jsonl --pattern loss/low_up/paddle_below_target --offsets 1,2,3,4,5,6,8,10,12,16,20,24 --output diagnostics/mixed_seed2_15_low_up_tail_offsets_v3_23_round51.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score16_mixed_seed2_15_v3_23_round51.jsonl --pattern loss/high_down/paddle_above_target --offsets 1,2,3,4,5,6,8,10,12,16,20,24 --output diagnostics/mixed_seed2_15_high_down_tail_offsets_v3_23_round51.json`
- Added diagnostic-only variants:
  - `once_score00_mid_down_y154_final_up_to_noop_h1`
  - `once_score00_mid_down_y154_final_up_to_down_h1`
- Verified diagnostic script:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
- Ran full 80-seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds $(seq -s, 0 79) --variants baseline,once_score00_mid_down_y154_final_up_to_noop_h1,once_score00_mid_down_y154_final_up_to_down_h1 --max-steps 30000 --jsonl diagnostics/counterfactual_score00_mid_down_y154_final_80seed_round51.jsonl`
- Summarized deltas:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_score00_mid_down_y154_final_80seed_round51.jsonl --output diagnostics/score00_mid_down_y154_final_deltas_round51.json`

### Diagnostic Results

- Seeds `2` and `15` have identical loss sequence:
  - point 1: `loss/mid_down/paddle_below_target`
  - point 2: `loss/mid_up/paddle_below_target`
  - point 5: `loss/low_up/paddle_below_target`
  - point 6: `loss/high_down/paddle_above_target`
  - point 7: `loss/mid_up/paddle_below_target`
- Earliest shared anchor:
  - offset `2`: `x=199,y=150,dy=4,target=150,paddle=155,action=UP`
  - offset `1`: `x=203,y=154,dy=4,target=154,paddle=166,action=UP`
  - counterfactual first intervention bucket: `score=0-0;x=199;y=150;pad=155;target=150.0;action=up->noop;step=123`.
- Counterfactual:
  - Baseline v3.23: mean `17.96`, min `16`, max `19`.
  - score `0-0` mid-down final `UP -> NOOP`: mean `18.01`, min `17`, max `19`; seeds `2` and `15` improved from `16` to `18`, no regressions.
  - score `0-0` mid-down final `UP -> DOWN`: mean `17.96`, min `16`, max `19`; no seed changed.

### Supported And Rejected Hypotheses

- Supported: the remaining min floor is the mixed early-loss seed family `2`/`15`.
- Supported: the first score-`0-0` mid-down final `UP` action is causal; changing it to `NOOP` removes enough downstream losses to raise both seeds by `+2`.
- Rejected: changing the same action to `DOWN`; it was inert.
- Rejected for this round: a broader mid-down or opening-controller rewrite. The successful trigger touched only two seeds under a narrow guard.

### Policy Change

- Accepted one minimal policy change:
  - score `0-0`, opening mid-down final approach, `dy=4,x=198..204,y=148..156,paddle=150..170,target=150..156`, `previous_paddle_delta >= 10`, baseline `UP -> NOOP`, one use per episode.
- Added guard tests for narrow trigger, one-shot behavior, and applied policy action.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `52` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_24_score00_mid_down_y154_noop --notes "Round 51: solidify score 0-0 mid-down y154 final UP->NOOP after mixed seed2/15 trace and full 80-seed counterfactual guard; v3_23 mean 17.9625 min 16 max 19 to probe mean 18.01 min 17 max 19" --no-video`
  - Result: mean `18.0125`, min `17`, max `19`, env steps `712891`.
- Seed-level delta versus v3.23:
  - Improved: seeds `2` and `15` by `+2`.
  - Worsened: none.
  - Unchanged: 78 seeds.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score16_mixed_seed2_15_v3_23_round51.jsonl`
  - `diagnostics/mixed_seed2_15_mid_down_tail_offsets_v3_23_round51.json`
  - `diagnostics/mixed_seed2_15_mid_up_tail_offsets_v3_23_round51.json`
  - `diagnostics/mixed_seed2_15_low_up_tail_offsets_v3_23_round51.json`
  - `diagnostics/mixed_seed2_15_high_down_tail_offsets_v3_23_round51.json`
  - `diagnostics/counterfactual_score00_mid_down_y154_final_80seed_round51.jsonl`
  - `diagnostics/score00_mid_down_y154_final_deltas_round51.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `report.html`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The score-`0-0` `UP -> DOWN` variant remains diagnostic-only and inert.

### New Structural Issues

- The current baseline crosses mean `18`, but many seeds now sit at score `17`; the next floor may be heterogeneous and should be split before any patch.
- The opening mixed-family fix is separate from the y86/high-down score-chain rules; do not use it as evidence for a broad controller rewrite.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.24 policy, tests, docs, eval logs, and diagnostic assets.
- No unrelated files were reverted.

### Next Automatic Exploration

- Extract all v3.24 score-`17` seeds from trial `45`, trace them, and classify whether the new floor is mostly repeated high-down/y86, inactive-tail artifacts, or heterogeneous early-loss families.
- Keep using counterfactuals before adding any new score-chain or opening-family guard.

## Round 52 - 2026-05-10

### Current Best Baseline

- Retained best policy: `ram_intercept_v3_24_score00_mid_down_y154_noop`.
- Formal 80-seed score: mean `18.0125`, min `17`, max `19`.
- Extracted score-`17` seeds from trial `45`: `0,5,7,8,9,12,14,17,20,23,25,27,32,37,41,43,45,47,48,50,54,57,58,61,63,64,67,71,72,74,75,77,79`.

### Hypothesis Map

1. Local failure-family hypothesis: the new score-`17` floor is heterogeneous, with a repeated y86 chain plus a smaller single low-up loss family.
   - Explains: many former score-`16` y86/high-down seeds are now at `17`, while several long-step seeds remain at `17` with only one loss.
   - Evidence source: v3.24 `episodes.jsonl` and point traces.
   - Minimal diagnostic: trace all score-`17` seeds and summarize loss families, tail offsets, and score-chain pairs.
   - Distinguishes from inactive-tail artifacts by counting actual losses and point windows.
   - If true: choose a family-specific next counterfactual, not a broad policy change. If false: improve floor extraction/classification.

2. Basic model/action hypothesis: repeated y86 still looks like a reset/phase problem, while low-up near-target may be a local damping/contact issue.
   - Explains: y86 final tail repeats the known `x=202,y=86,pad=79,error=-7` pattern; low-up has a single `x=204,y=120,pad=115,error=-5` miss.
   - Evidence source: `diagnostics/mid_down_y86_tail_offsets_v3_24_round52.json` and `diagnostics/low_up_near_tail_offsets_v3_24_round52.json`.
   - Minimal diagnostic: compare y86 score `14-0` reset history or test low-up near-target action replacement, but only one direction at a time.
   - Distinguishes from target smoothing by using counterfactual action probes before model changes.
   - If true: use narrow family guards. If false: collect more state-model evidence.

3. Control-structure hypothesis: after v3.22-v3.24, the policy has two score-chain mechanisms plus one opening exception, so a unifying controller is still premature.
   - Explains: v3.22 extended y86 reset chain, v3.23 extended high-down bounce chain, and v3.24 solved only seed `2/15` opening mixed family.
   - Evidence source: accepted recent deltas and known rejected ungated probes.
   - Minimal diagnostic: write a new induction artifact before any further policy change.
   - Distinguishes from patch accumulation by explicitly mapping accepted and rejected mechanisms.
   - If true: continue diagnostics with family-specific probes. If false: plan a guarded refactor only after marker predictions cover negative cases.

### Diagnostics Run

- Extracted v3.24 score-`17` seeds from `episodes.jsonl`.
- Traced all score-`17` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 0,5,7,8,9,12,14,17,20,23,25,27,32,37,41,43,45,47,48,50,54,57,58,61,63,64,67,71,72,74,75,77,79 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_score17_all_v3_24_round52.jsonl`
- Summarized floor families:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score17_all_v3_24_round52.jsonl --output diagnostics/score17_floor_families_v3_24_round52.json`
- Summarized y86 and low-up tails:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score17_all_v3_24_round52.jsonl --pattern loss/mid_down/near_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/mid_down_y86_tail_offsets_v3_24_round52.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score17_all_v3_24_round52.jsonl --pattern loss/low_up/near_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/low_up_near_tail_offsets_v3_24_round52.json`
- Counted loss score-chain pairs from the trace.
- Wrote induction artifact:
  - `diagnostics/rule_induction_round52.json`

### Results

- Score-`17` floor contains `33` seeds and two clear families:
  - `22` seeds have four repeated `loss/mid_down/near_target` losses.
  - `11` seeds have one `loss/low_up/near_target` loss.
- Repeated y86 family:
  - Seeds: `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79`.
  - Loss score-chain: `14-0,16-1,18-2,20-3`.
  - Final signature: `x=202,y=86,dy=4,target=86,pad=79,error=-7`.
  - Tail offset `10`: `x=166,y=50,dy=5,target=82.5,paddle=78,action=DOWN`.
- Low-up near-target family:
  - Seeds: `0,9,25,41,43,48,50,63,64,72,75`.
  - Loss score-chain: `1-0`.
  - Final signature: `x=204,y=120,dy=-8,target=120,pad=115,error=-5`.
  - Tail offset `2`: `x=200,y=128,target=128,paddle=131,action=NOOP`.
  - Tail offset `1`: `x=204,y=120,target=120,paddle=115,action=DOWN`.

### Supported And Rejected Hypotheses

- Supported: the new floor is not a single family; it is split between repeated y86 and single low-up near-target losses.
- Supported: this was the required induction point after three accepted local rules.
- Rejected for this round: making another immediate policy change without choosing between the two distinct families and running a counterfactual.

### Policy Change

- None. This was diagnostic and induction-only.

### Verification

- No policy code changed in this round.
- Diagnostic scripts completed and wrote outputs.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score17_all_v3_24_round52.jsonl`
  - `diagnostics/score17_floor_families_v3_24_round52.json`
  - `diagnostics/mid_down_y86_tail_offsets_v3_24_round52.json`
  - `diagnostics/low_up_near_tail_offsets_v3_24_round52.json`
  - `diagnostics/rule_induction_round52.json`
- Updated:
  - this progress log.

### Withdrawn

- No strategy change was made, so nothing was withdrawn.
- No negative result was deleted.

### New Structural Issues

- The y86 reset-chain now appears at score `14-0`, but the score-`17` floor also has a smaller low-up near-target family; improving mean and improving min may point to different next probes.
- A future refactor still needs a marker model that explains accepted y86 reset fixes, high-down bounce fixes, and the opening mixed-family exception.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.24 policy, tests, docs, eval logs, and diagnostic assets.
- No unrelated files were reverted.

### Next Automatic Exploration

- Choose the next counterfactual by information gain:
  - y86 option: align score `14-0` reset through first x166 and test whether the reset-phase pattern from score `6-0/10-0` continues.
  - low-up option: test a narrow score `1-0` low-up near-target final or offset-2 action replacement.
- Use full 80-seed guards before any policy change.

## Round 53 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_24_score00_mid_down_y154_noop`.
- Formal 80-seed score: mean `18.0125`, min `17`, max `19`.
- Known floor from Round 52:
  - `22` repeated y86 seeds with loss chain `14-0,16-1,18-2,20-3`.
  - `11` low-up near-target seeds with one score-`1-0` loss.

### Hypothesis Map

1. Local failure-family hypothesis: the repeated y86 score-`17` family is the next reset-chain instance at score `14-0`.
   - Explains: the same 22 seeds repeat the y86 final miss after the previous score `6-0` and `10-0` reset fixes.
   - Evidence source: `diagnostics/point_traces_score17_all_v3_24_round52.jsonl`, `diagnostics/score17_floor_families_v3_24_round52.json`, and the new setup diagnostic.
   - Minimal diagnostic: align the first post-reward right-moving reset frame for the 22 y86 seeds and test a one-frame action replacement at score `14-0`.
   - Distinguishes from local x166/final-tail control by intervening before the long chain develops.
   - If true: accept only a narrow score-`14-0` one-shot guard. If false: leave the y86 family unchanged and move to low-up diagnostics.

2. Basic model/action hypothesis: the policy's initial-mode `DOWN` on the score-`14-0` reset frame creates the later y86 phase error.
   - Explains: all 22 y86 seeds share `x=126,y=126,paddle=101,target=126,action=DOWN,mode=initial` after the point reset, then reach the later y86 miss.
   - Evidence source: `diagnostics/y86_score140_chain_setup_v3_24_round53.json`.
   - Minimal diagnostic: compare `DOWN -> NOOP` and `DOWN -> UP` one-frame counterfactuals over the full 80-seed set.
   - Distinguishes action magnitude from sign by comparing NOOP and UP while holding trigger timing fixed.
   - If true: prefer NOOP as the smaller action if it matches UP. If false: score-`14-0` reset timing is not causal.

3. Control-structure hypothesis: accepted y86 reset rules now form a score-spaced chain, but a broad phase model is still not justified.
   - Explains: score `6-0`, `10-0`, and `14-0` share reset-frame evidence, while score `2-0` and pure x166 variants were previously rejected or unsafe.
   - Evidence source: accepted deltas from Rounds 45, 49, and this round, plus rejected diagnostics retained in `diagnostics/`.
   - Minimal diagnostic: keep the score-guarded one-shot and defer controller consolidation until markers explain both positive and negative cases.
   - Distinguishes principled induction from patch stacking by requiring a future marker model before broadening.
   - If true: retain the narrow guard and next attack the low-up floor. If false: collect new negative/positive reset examples before refactor.

### Diagnostics Run

- Setup alignment for the 22 y86 seeds:
  - `.venv/bin/python scripts/analyze_y86_chain_setup.py --seeds 5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79 --max-steps 6000 --output diagnostics/y86_score140_chain_setup_v3_24_round53.json`
- Counterfactual probe:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --variant once_y86_score140_serve_initial_down_to_noop_h1 --episodes 80 --seed 0 --max-steps 30000 --no-video --output diagnostics/counterfactual_y86_score140_serve_initial_80seed_round53.jsonl`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --variant once_y86_score140_serve_initial_down_to_up_h1 --episodes 80 --seed 0 --max-steps 30000 --no-video --output diagnostics/counterfactual_y86_score140_serve_initial_80seed_round53.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_y86_score140_serve_initial_80seed_round53.jsonl --baseline-label baseline --output diagnostics/y86_score140_serve_initial_deltas_round53.json`

### Results

- Setup diagnostic:
  - All `22/22` y86 seeds first enter the chain at score `14-0`.
  - Shared first-right reset frame: `x=126,y=126,pad=101,target=126,action=DOWN,mode=initial`.
  - First `x=166` appears `116` steps after the reset reward, matching the prior y86 reset-chain shape.
- Counterfactual:
  - Baseline v3.24: mean `18.01`, min `17`, max `19`.
  - `DOWN -> NOOP`: mean `18.29`, min `17`, max `19`, `22` seeds improved, `0` worsened.
  - `DOWN -> UP`: same score result as NOOP, so it was not retained because it is a larger action.

### Supported And Rejected Hypotheses

- Supported: the repeated y86 score-`17` floor is a score-`14-0` reset-chain instance.
- Supported: the initial reset-frame action is causal enough to improve all 22 y86 seeds without hurting the 80-seed set.
- Not resolved: whether score-guarded y86 reset rules can be collapsed into a unified phase marker.
- Rejected for implementation: retaining the UP variant, since it adds no score benefit over NOOP.

### Policy Change

- Accepted one minimal strategy change in `pong_policy.py`:
  - At score `14-0`, in `initial` mode, when baseline action is `DOWN` and state is `x=124..128,y=124..128,paddle=96..106`, replace one frame with `NOOP`.
  - The guard is one-shot via `y86_score140_serve_initial_correction_used`.
- Added matching unit coverage in `tests/test_state_policy.py`.
- Added the score-`14-0` counterfactual variants to `scripts/probe_counterfactual_actions.py`.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `55` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_25_y86_score140_serve_initial_noop --notes "Round 53: solidify score 14-0 y86 serve-initial DOWN->NOOP after setup diagnostic and full 80-seed counterfactual guard; v3_24 mean 18.0125 min 17 max 19 to probe mean 18.29 min 17 max 19" --no-video`
  - Result: mean `18.2875`, min `17`, max `19`, env steps `713441`.
- Seed-level delta versus v3.24:
  - Improved: seeds `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79` by `+1`.
  - Worsened: none.
  - Unchanged: `58` seeds.

### Durable Assets

- Added:
  - `diagnostics/y86_score140_chain_setup_v3_24_round53.json`
  - `diagnostics/counterfactual_y86_score140_serve_initial_80seed_round53.jsonl`
  - `diagnostics/y86_score140_serve_initial_deltas_round53.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `failures.jsonl`
  - `report.html`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.
- The score-`14-0` UP replacement remains diagnostic-only.

### New Structural Issues

- The y86 chain now has three accepted reset-frame rules at scores `6-0`, `10-0`, and `14-0`; future work should test whether a marker model can explain why these work while earlier broad variants failed.
- The min remains `17`; after this change, the likely remaining floor is the low-up near-target family from seeds `0,9,25,41,43,48,50,63,64,72,75`.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.25 policy, tests, docs, eval logs, and diagnostic assets.
- No unrelated files were reverted.

### Next Automatic Exploration

- Extract v3.25 score-`17` seeds and confirm whether the remaining floor is now only the Round 52 low-up near-target family.
- If confirmed, run a narrow full-80-seed counterfactual around the score-`1-0` low-up final or offset-2 action, using the existing tail evidence before any strategy change.

## Round 54 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_25_y86_score140_serve_initial_noop`.
- Formal 80-seed score: mean `18.2875`, min `17`, max `19`.
- Score-`17` seeds from trial `46`: `0,9,25,41,43,48,50,63,64,72,75`.

### Hypothesis Map

1. Local failure-family hypothesis: after v3.25, the floor is only the single-loss low-up near-target family.
   - Explains: v3.25 lifted all repeated y86 seeds to `18`, leaving the 11 Round 52 low-up seeds at `17`.
   - Evidence source: `episodes.jsonl`, `diagnostics/point_traces_score17_low_up_v3_25_round54.jsonl`, and floor-family summary.
   - Minimal diagnostic: trace only score-`17` seeds and compare final signatures to Round 52.
   - Distinguishes from a hidden new y86/high-down floor by requiring every remaining loss to share one low-up signature.
   - If true: run a narrow low-up final-frame counterfactual. If false: split the new floor before policy changes.

2. Basic model/action hypothesis: the score-`1-0` low-up miss is caused by a final-frame overcorrection rather than the previous offset-2 NOOP.
   - Explains: all 11 losses end at `x=204,y=120,dy=-8,target=120,pad=115,error=-5` with baseline action `DOWN`; offset 2 is `x=200,y=128,pad=131,action=NOOP`.
   - Evidence source: `diagnostics/low_up_near_tail_offsets_v3_25_round54.json`.
   - Minimal diagnostic: compare final `DOWN -> NOOP`, final `DOWN -> UP`, and offset-2 `NOOP -> DOWN` over all 80 seeds.
   - Distinguishes final contact timing from earlier damping by testing the two adjacent frames independently.
   - If true: accept a final-frame guard only. If false: retain diagnostics and look earlier in the trajectory.

3. Control-structure hypothesis: this low-up correction is not part of the y86/high-down reset induction chain.
   - Explains: it affects score `1-0`, point 2, and a single low-up miss family; it should not broaden y86 reset or high-down bounce rules.
   - Evidence source: Round 52/53 induction plus this round's low-up-only floor.
   - Minimal diagnostic: keep the guard score/state/action-specific and one-shot; do not refactor controller.
   - Distinguishes family-specific fix from controller rewrite by requiring no changes outside this final low-up signature.
   - If true: retain as a local final-frame exception. If false: future traces should reveal shared controller-level timing failures.

### Diagnostics Run

- Extracted v3.25 score-`17` seeds:
  - `jq -r 'select(.trial_index==46 and .score==17) | .seed' episodes.jsonl`
- Traced remaining floor:
  - `.venv/bin/python scripts/trace_points.py --seeds 0,9,25,41,43,48,50,63,64,72,75 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_score17_low_up_v3_25_round54.jsonl`
- Summarized floor and tails:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score17_low_up_v3_25_round54.jsonl --output diagnostics/score17_floor_families_v3_25_round54.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score17_low_up_v3_25_round54.jsonl --pattern loss/low_up/near_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/low_up_near_tail_offsets_v3_25_round54.json`
- Added diagnostic-only counterfactual triggers for score-`1-0` low-up final and offset-2 frames.
- Counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --variants baseline,once_low_up_score10_final_down_to_noop_h1,once_low_up_score10_final_down_to_up_h1,once_low_up_score10_offset2_noop_to_down_h1 --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 --max-steps 30000 --jsonl diagnostics/counterfactual_low_up_score10_near_final_80seed_round54.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_low_up_score10_near_final_80seed_round54.jsonl --baseline-variant baseline --output diagnostics/low_up_score10_near_final_deltas_round54.json`

### Results

- Remaining floor:
  - `11/11` score-`17` seeds have exactly one `loss/low_up/near_target`.
  - Final signature: `x=204,y=120,dy=-8,target=120,pad=115,error=-5`.
  - Offset 2: `x=200,y=128,target=128,paddle=131,action=NOOP`.
- Counterfactual results:
  - Baseline v3.25: mean `18.29`, min `17`, max `19`.
  - Final `DOWN -> NOOP`: mean `18.70`, min `18`, max `20`, `11` improved, `0` worsened.
  - Final `DOWN -> UP`: mean `18.56`, min `18`, max `19`, `11` improved, `0` worsened.
  - Offset-2 `NOOP -> DOWN`: mean `18.29`, min `17`, max `19`, no seed-level change.

### Supported And Rejected Hypotheses

- Supported: v3.25 fully removed the y86 floor from score `17`; the remaining floor is one low-up family.
- Supported: the causal point is the final `DOWN` action, not the offset-2 NOOP.
- Rejected for implementation: final `DOWN -> UP`, because it is weaker than NOOP.
- Rejected for implementation: offset-2 `NOOP -> DOWN`, because it is inert.

### Policy Change

- Accepted one minimal strategy change in `pong_policy.py`:
  - At score `1-0`, for low-up final frame `dy=-8,x=202..204,y=118..122,paddle=113..117,target=118..122`, replace one baseline `DOWN` with `NOOP`.
  - Guard includes `previous_paddle_delta <= -10` and one-shot `low_up_score10_final_correction_used`.
- Added matching tests in `tests/test_state_policy.py`.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `58` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_26_low_up_score10_final_noop --notes "Round 54: solidify score 1-0 low-up final DOWN->NOOP after v3_25 floor trace and full 80-seed counterfactual guard; v3_25 mean 18.2875 min 17 max 19 to probe mean 18.70 min 18 max 20" --no-video`
  - Result: mean `18.7`, min `18`, max `20`, env steps `471695`.
- Seed-level delta versus v3.25:
  - Improved: seeds `0,9,25,41,43,48,50,63,64,72,75` by `+3`.
  - Worsened: none.
  - Unchanged: `69` seeds.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score17_low_up_v3_25_round54.jsonl`
  - `diagnostics/score17_floor_families_v3_25_round54.json`
  - `diagnostics/low_up_near_tail_offsets_v3_25_round54.json`
  - `diagnostics/counterfactual_low_up_score10_near_final_80seed_round54.jsonl`
  - `diagnostics/low_up_score10_near_final_deltas_round54.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `failures.jsonl`
  - `report.html`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.
- Final `DOWN -> UP` and offset-2 `NOOP -> DOWN` remain diagnostic-only negative/lesser results.

### New Structural Issues

- The current minimum is now `18`, and some seeds reach `20`; next exploration should identify whether remaining score-`18` seeds share one family or split across older y86/high-down/low-up mechanisms.
- Because v3.26 added another local rule after the Round 52 induction, future accumulated-rule review should revisit whether final-frame damping exceptions share a controller-level timing model.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.26 policy, tests, docs, eval logs, and diagnostic assets.
- No unrelated files were reverted.

### Next Automatic Exploration

- Extract v3.26 score-`18` seeds from trial `47` and classify them by trace family before any new policy change.
- Prefer a diagnostic split between remaining y86/high-down score-chain misses and any new single-loss final-frame families.

## Round 55 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_26_low_up_score10_final_noop`.
- Formal 80-seed score: mean `18.7`, min `18`, max `20`.
- Score distribution from trial `47`: `35` seeds at `18`, `34` at `19`, `11` at `20`.
- Score-`18` seeds: `2,3,4,5,6,7,8,12,14,15,17,18,20,23,24,27,32,33,37,40,42,44,45,47,49,54,57,58,61,66,67,71,74,77,79`.

### Hypothesis Map

1. Local failure-family hypothesis: the score-`18` floor is split, with a dominant repeated high-down y165 family and smaller early mixed families.
   - Explains: many former high-down score-chain seeds stayed at `18`, while seeds `2/15/33/44/66` and several low-up seeds have different early losses.
   - Evidence source: trial `47` in `episodes.jsonl` and the new score-`18` point trace.
   - Minimal diagnostic: trace all score-`18` seeds and summarize loss families, final signatures, score chains, and fixed tail offsets.
   - Distinguishes from a homogeneous floor by counting per-seed loss signatures.
   - If true: target only the dominant family with a score-gated counterfactual. If false: gather more trace data before any patch.

2. Basic model/action hypothesis: the dominant high-down y165 misses continue the accepted bounce-chain mechanism from scores `4-0`, `8-0`, and `12-0`.
   - Explains: the shared pre-loss anchor is again `x=146,y=53,dy=9,target=156.5,pad=146,action=DOWN`.
   - Evidence source: `diagnostics/high_down_y165_tail_offsets_v3_26_round55.json` and score-chain extraction.
   - Minimal diagnostic: compare score-gated `DOWN -> NOOP` at pre-loss scores `16-0`, `18-1`, and `20-2` over the full 80-seed set.
   - Distinguishes first causal link from later redundant links by testing each score gate separately.
   - If true: accept only the earliest link. If false: retain negative results and inspect earlier setup.

3. Control-structure hypothesis: high-down bounce rules are becoming a score-chain family, but broadening is still unsafe.
   - Explains: earlier ungated high-down probes regressed hidden score-`0-0` seeds; accepted fixes are score-gated and one-shot.
   - Evidence source: retained Round 37/47/50 deltas and rejected high-down x166/x150 diagnostics.
   - Minimal diagnostic: add only score-specific diagnostic variants and avoid a broad high-down controller rewrite.
   - Distinguishes induction from patch stacking by requiring full 80-seed no-regression evidence at the exact score state.
   - If true: add a narrow score-`16-0` guard. If false: do not generalize the chain.

### Diagnostics Run

- Traced all v3.26 score-`18` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 2,3,4,5,6,7,8,12,14,15,17,18,20,23,24,27,32,33,37,40,42,44,45,47,49,54,57,58,61,66,67,71,74,77,79 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_score18_all_v3_26_round55.jsonl`
- Summarized families and tails:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score18_all_v3_26_round55.jsonl --output diagnostics/score18_floor_families_v3_26_round55.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_all_v3_26_round55.jsonl --pattern loss/high_down/paddle_above_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/high_down_y165_tail_offsets_v3_26_round55.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_all_v3_26_round55.jsonl --pattern loss/low_up/paddle_below_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/low_up_paddle_below_tail_offsets_v3_26_round55.json`
- Extracted high-down score chain:
  - Loss pre-scores were `16-0`, `18-1`, and `20-2`; post-loss records were `16-1`, `18-2`, and `20-3`.
- Added diagnostic-only counterfactual variants for score `16-0`, `18-1`, and `20-2` high-down y165 bounce.
- Counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --variants baseline,once_high_down_y165_bounce_score160_x146_down_to_noop_h1,once_high_down_y165_bounce_score181_x146_down_to_noop_h1,once_high_down_y165_bounce_score202_x146_down_to_noop_h1 --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 --max-steps 30000 --jsonl diagnostics/counterfactual_high_down_score160_181_202_bounce_80seed_round55.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score160_181_202_bounce_80seed_round55.jsonl --baseline-variant baseline --output diagnostics/high_down_score160_181_202_bounce_deltas_round55.json`

### Results

- Score-`18` floor split:
  - `22` seeds have three repeated high-down y165 losses.
  - Dominant final signature: `loss/high_down/paddle_above_target;x=202;y=165;dy=8;target=165.0;pad=146;err=-19.0`, count `66`.
  - Remaining seeds are mixed early families, including low-up `x=205,y=118`, low-up y111, mid-up y164, and high-down y205/y171 signatures.
- High-down y165 tail:
  - Offset `16`: `x=142,y=44,dy=-8,target=148,pad=138,action=DOWN`.
  - Offset `15`: `x=146,y=53,dy=9,target=156.5,pad=146,action=DOWN`.
  - Final: `x=202,y=165,dy=8,target=165,pad=146,action=DOWN`.
- Counterfactuals:
  - Baseline v3.26: mean `18.70`, min `18`, max `20`.
  - Score `16-0` `DOWN -> NOOP`: mean `18.98`, min `18`, max `20`, `22` improved, `0` worsened.
  - Score `18-1` `DOWN -> NOOP`: same score result.
  - Score `20-2` `DOWN -> NOOP`: same score result.

### Supported And Rejected Hypotheses

- Supported: the dominant v3.26 score-`18` family is a high-down y165 bounce-chain continuation.
- Supported: each individual chain link is sufficient to improve the same 22 seeds by `+1` without regressions.
- Supported: the earliest score `16-0` link is the minimal retained implementation.
- Rejected for implementation this round: adding score `18-1` or `20-2` guards, because they are redundant with the earliest link for the observed score outcome.
- Rejected for implementation: broad high-down generalization; this round did not retest nor justify ungated behavior.

### Policy Change

- Accepted one minimal strategy change in `pong_policy.py`:
  - At score `16-0`, for high-down y165 bounce `dy=9,x=144..148,y=50..56,paddle=143..150,target=154..162`, replace one baseline `DOWN` with `NOOP`.
  - Guard is one-shot via `high_down_score160_bounce_correction_used`.
- Added matching tests in `tests/test_state_policy.py`.

### Verification

- Unit tests:
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - Result: `61` tests passed.
- Formal 80-seed eval:
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_27_high_down_score160_bounce_noop --notes "Round 55: solidify score 16-0 high-down y165 bounce DOWN->NOOP after v3_26 score18 floor trace and full 80-seed counterfactual guard; v3_26 mean 18.70 min 18 max 20 to probe mean 18.98 min 18 max 20" --no-video`
  - Result: mean `18.975`, min `18`, max `20`, env steps `466965`.
- Seed-level delta versus v3.26:
  - Improved: seeds `5,7,8,12,14,17,20,23,27,32,37,45,47,54,57,58,61,67,71,74,77,79` by `+1`.
  - Worsened: none.
  - Unchanged: `58` seeds.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score18_all_v3_26_round55.jsonl`
  - `diagnostics/score18_floor_families_v3_26_round55.json`
  - `diagnostics/high_down_y165_tail_offsets_v3_26_round55.json`
  - `diagnostics/low_up_paddle_below_tail_offsets_v3_26_round55.json`
  - `diagnostics/counterfactual_high_down_score160_181_202_bounce_80seed_round55.jsonl`
  - `diagnostics/high_down_score160_181_202_bounce_deltas_round55.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `summary.csv`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `failures.jsonl`
  - `report.html`
  - this progress log.

### Withdrawn

- No accepted code was withdrawn.
- Score `18-1` and `20-2` high-down bounce variants remain diagnostic-only redundant positives, not retained rules.

### New Structural Issues

- Current score distribution from trial `48`: `13` seeds at `18`, `56` at `19`, `11` at `20`.
- Remaining score-`18` seeds are `2,3,4,6,15,18,24,33,40,42,44,49,66`; these are not the repeated high-down y165 chain and should be traced separately.
- The high-down bounce chain now has accepted score `4-0`, `8-0`, `12-0`, and `16-0` links. A future induction round should test whether these can be represented by a smaller score-chain/phase marker, but not before separating the remaining mixed floor.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 policy, tests, docs, eval logs, and diagnostic assets.
- No unrelated files were reverted.

### Next Automatic Exploration

- Trace v3.27 score-`18` seeds `2,3,4,6,15,18,24,33,40,42,44,49,66` and split the remaining mixed floor.
- Do not add another high-down y165 chain rule until the remaining floor is classified; it is no longer the min-limiting family.

## Round 56 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Score distribution: `13` seeds at `18`, `56` at `19`, `11` at `20`.
- Remaining score-`18` seeds: `2,3,4,6,15,18,24,33,40,42,44,49,66`.

### Hypothesis Map

1. Local failure-family hypothesis: the remaining floor is a mixed early-loss set, with an 8-seed score-`0-0` low-up y118 family as the largest shared subfamily.
   - Explains: after removing the high-down y165 chain, the floor no longer repeats the accepted score-chain signature.
   - Evidence source: v3.27 trace `diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl` and `diagnostics/score18_mixed_floor_families_v3_27_round56.json`.
   - Minimal diagnostic: one-shot counterfactuals at the controllable offset-2/offset-3 frames of the 8-seed low-up y118 loss.
   - Distinguishes from final-frame low-up fixes by avoiding the inactive `x=205` post-miss frame and testing earlier actions.
   - If true: one offset intervention should improve the 8 seeds without hurting the other 72. If false: keep the negative result and inspect a different frame or family.

2. Basic model/action hypothesis: the y118 loss is not caused by insufficient upward pursuit at offset 2 or offset 3.
   - Explains: the trace shows `UP` at `x=201,y=126,pad=150,target=126` and `NOOP` at `x=197,y=134,pad=135,target=134`; changing these may disrupt a contact that is already locally correct.
   - Evidence source: `diagnostics/low_up_paddle_below_tail_offsets_v3_27_round56.json`.
   - Minimal diagnostic: compare `UP -> NOOP`, `UP -> DOWN`, and `NOOP -> DOWN` on the full 80-seed set.
   - Distinguishes action-direction error from earlier phase/target error by checking whether direct action edits help.
   - If true: direct late-action damping should improve. If false: action timing at these offsets is not the causal lever.

3. Control-structure hypothesis: the next improvement may require point/phase context rather than another late-frame one-shot.
   - Explains: accepted recent rules fixed exact score-chain or final-frame misses, while this family worsens when the same damping intuition is applied.
   - Evidence source: Round 54 positive low-up final `DOWN -> NOOP` versus this round's negative score-`0-0` low-up offset probes.
   - Minimal diagnostic: first reject/accept local action hypotheses, then inspect earlier serve/contact phase if all late probes fail.
   - Distinguishes structural need from patch stacking by requiring counterfactual evidence before any new policy rule.
   - If true: next round should analyze earlier trajectory setup or the smaller mid-up/mid-down subfamilies. If false: a narrow local action patch would be enough.

### Diagnostics Run

- Traced the v3.27 score-`18` seeds:
  - `.venv/bin/python scripts/trace_points.py --seeds 2,3,4,6,15,18,24,33,40,42,44,49,66 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl`
- Summarized families and tails:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --output diagnostics/score18_mixed_floor_families_v3_27_round56.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --pattern loss/low_up/paddle_below_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/low_up_paddle_below_tail_offsets_v3_27_round56.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --pattern loss/mid_up/paddle_below_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/mid_up_paddle_below_tail_offsets_v3_27_round56.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --pattern loss/mid_down/paddle_below_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20 --output diagnostics/mid_down_paddle_below_tail_offsets_v3_27_round56.json`
- Added diagnostic-only counterfactual variants for score-`0-0` low-up y118 offset-2 and offset-3 frames.
- Counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 --variants baseline,once_low_up_score00_y118_offset2_up_to_noop_h1,once_low_up_score00_y118_offset2_up_to_down_h1,once_low_up_score00_y118_offset3_noop_to_down_h1 --jsonl diagnostics/counterfactual_low_up_score00_y118_80seed_round56.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_low_up_score00_y118_80seed_round56.jsonl --output diagnostics/low_up_score00_y118_deltas_round56.json`

### Results

- Mixed score-`18` floor split:
  - Seeds `3,4,6,18,24,40,42,49`: `loss/low_up/paddle_below_target`, `loss/high_down/paddle_above_target`, `loss/low_up/near_target`.
  - Seeds `33,44,66`: `loss/low_up/paddle_below_target`, `loss/high_down/paddle_above_target`, `loss/mid_up/paddle_below_target`.
  - Seeds `2,15`: `loss/mid_down/paddle_below_target`, `loss/high_down/near_target`, `loss/mid_up/paddle_below_target`.
- Largest low-up y118 subfamily:
  - Final inactive/post-miss frame: `x=205,y=118,dy=-8,target=100,pad=148,action=UP`.
  - Offset 2: `x=201,y=126,target=126,pad=150,action=UP`.
  - Offset 3: `x=197,y=134,target=134,pad=135,action=NOOP`.
- Counterfactuals:
  - Baseline v3.27: mean `18.98`, min `18`, max `20`.
  - Offset-2 `UP -> NOOP`: mean `18.88`, min `17`, max `20`, `0` improved, `8` worsened.
  - Offset-2 `UP -> DOWN`: mean `18.98`, min `18`, max `20`, no seed-level change.
  - Offset-3 `NOOP -> DOWN`: mean `18.88`, min `17`, max `20`, `0` improved, `8` worsened.

### Supported And Rejected Hypotheses

- Supported: the remaining score-`18` floor is mixed, not the prior high-down y165 score-chain family.
- Supported: the 8-seed low-up y118 family is real and reproducible, but the tested late action changes do not explain the miss.
- Rejected: offset-2 upward action is too aggressive. Replacing it with `NOOP` worsens exactly the 8 target seeds.
- Rejected: offset-3 `NOOP` should become `DOWN`; it causes the same 8 regressions.
- Rejected for implementation: offset-2 `UP -> DOWN`; it is inert at score level and does not justify a policy rule.

### Policy Change

- No policy change accepted.
- `pong_policy.py` and tests remain at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps the score-`0-0` low-up y118 diagnostic variants so this negative result remains reproducible.

### Verification

- Diagnostic script syntax:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
- Full 80-seed counterfactual reproduced the v3.27 baseline and found no acceptable intervention.
- No formal `run_eval.py` was run because no strategy change was accepted.

### Durable Assets

- Added:
  - `diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl`
  - `diagnostics/score18_mixed_floor_families_v3_27_round56.json`
  - `diagnostics/low_up_paddle_below_tail_offsets_v3_27_round56.json`
  - `diagnostics/mid_up_paddle_below_tail_offsets_v3_27_round56.json`
  - `diagnostics/mid_down_paddle_below_tail_offsets_v3_27_round56.json`
  - `diagnostics/counterfactual_low_up_score00_y118_80seed_round56.jsonl`
  - `diagnostics/low_up_score00_y118_deltas_round56.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- The tested y118 local action hypotheses are retained as negative diagnostic results.

### New Structural Issues

- Late-frame action edits for the largest remaining low-up family are either harmful or inert, so the causal issue may be earlier contact phase, serve setup, or a different family.
- The 5-seed mid-up y164 family and 2-seed mid-down y154 family remain unresolved and may have clearer causal levers than y118.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Prefer an earlier-phase trace/counterfactual for the same y118 family before changing policy, or switch to the mid-up y164 subfamily if it yields a more direct controllable action hypothesis.
- Do not patch the score-`0-0` low-up y118 offset-2/offset-3 actions; the full 80-seed counterfactual rejected those interventions.

## Round 57 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Remaining score-`18` seeds remain `2,3,4,6,15,18,24,33,40,42,44,49,66`.

### Hypothesis Map

1. Local failure-family hypothesis: the 5-record mid-up y164 subfamily may expose a clearer controllable lever than the rejected y118 late-action probes.
   - Explains: seeds `2,15,33,44,66` share final `x=203,y=164,dy=-8,target=164,pad=180`.
   - Evidence source: `diagnostics/mid_up_paddle_below_tail_offsets_v3_27_round56.json`.
   - Minimal diagnostic: first test existing y164 diagnostic variants; if stale, add exact v3.27 precontact variants.
   - Distinguishes from y118 by targeting a different loss family and a named controller mode.
   - If true: a narrow precontact counterfactual should improve those 5 seeds. If false: y164 is not fixed by the obvious precontact action inversion.

2. Basic model/action hypothesis: current `mid_up_precontact_up_correction` may be over-suppressing upward motion at `x=187,y=196,pad=202,target=186`.
   - Explains: the final miss has the paddle below the target, while the precontact correction emits `NOOP` on two frames.
   - Evidence source: offsets 4 and 5 in `diagnostics/mid_up_paddle_below_tail_offsets_v3_27_round56.json`.
   - Minimal diagnostic: counterfactual `NOOP -> UP` for one frame and two frames, plus `NOOP -> DOWN` as a direction control.
   - Distinguishes over-suppression from unrelated phase error by directly undoing the correction.
   - If true: `NOOP -> UP` should improve. If false: this correction is not the causal miss point.

3. Control-structure hypothesis: current residual failures are becoming phase/history problems, not single-frame target-action mistakes.
   - Explains: y118 late probes were harmful/inert, and y164 may depend on score phase (`0-2` versus `2-2`) or contact history.
   - Evidence source: Round 56 y118 deltas and this round's split by first-intervention buckets.
   - Minimal diagnostic: retain negative action probes and next inspect earlier setup or family markers before a policy change.
   - Distinguishes structural need from local patch opportunity by requiring a clean seed-level improvement from direct counterfactuals.
   - If true: do not add another local rule; build an earlier-phase comparison.

### Diagnostics Run

- Re-tested existing y164 diagnostic variants on the full 80-seed v3.27 baseline:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 --variants baseline,repeat_mid_up_y164_late_noop_to_down_h1,repeat_mid_up_y164_late_noop_to_up_h1,repeat_mid_up_y164_pre_up_to_noop_h1,repeat_mid_up_y164_pre_up_to_down_h1 --jsonl diagnostics/counterfactual_mid_up_y164_80seed_round57.jsonl`
- Summarized stale-trigger deltas:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_mid_up_y164_80seed_round57.jsonl --output diagnostics/mid_up_y164_legacy_trigger_deltas_round57.json`
- Added diagnostic-only exact v3.27 trigger for `mid_up_precontact_up_correction` at `x=187..191,y=188..196,pad=200..205,target=184..188,action=NOOP`.
- Ran exact precontact counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 --variants baseline,once_mid_up_y164_precontact_noop_to_up_h1,once_mid_up_y164_precontact_noop_to_up_h2,once_mid_up_y164_precontact_noop_to_down_h1 --jsonl diagnostics/counterfactual_mid_up_y164_precontact_80seed_round57.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_mid_up_y164_precontact_80seed_round57.jsonl --output diagnostics/mid_up_y164_precontact_deltas_round57.json`
- Syntax check:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`

### Results

- Existing y164 variants are stale for v3.27:
  - All four legacy variants had `0` interventions and unchanged `18.98/18/20`.
- Exact v3.27 precontact trigger:
  - Hit exactly 5 seeds: `2,15` at score `0-2`, and `33,44,66` at score `2-2`.
  - First-intervention buckets:
    - `score=0-2;x=187;y=196;pad=202;target=186.0;step=311`, count `2`.
    - `score=2-2;x=187;y=196;pad=202;target=186.0;step=922`, count `3`.
- Counterfactuals:
  - Baseline v3.27: mean `18.98`, min `18`, max `20`.
  - `NOOP -> UP` horizon 1: mean `18.79`, min `15`, max `20`, `0` improved, `5` worsened by `-3`.
  - `NOOP -> UP` horizon 2: mean `18.98`, min `18`, max `20`, no seed-level change.
  - `NOOP -> DOWN` horizon 1: mean `18.98`, min `18`, max `20`, no seed-level change.

### Supported And Rejected Hypotheses

- Supported: legacy y164 diagnostic triggers are obsolete for the current v3.27 residual floor.
- Supported: the exact precontact family is reproducible and cleanly identifies seeds `2,15,33,44,66`.
- Rejected: `mid_up_precontact_up_correction` is too conservative in a way fixed by one-frame `NOOP -> UP`; it regresses all 5 target seeds to `15`.
- Rejected for implementation: horizon-2 `NOOP -> UP` and one-frame `NOOP -> DOWN`, because both are score-level inert.
- Supported structurally: the y164 miss is not a simple local action-direction error at the precontact correction point.

### Policy Change

- No policy change accepted.
- `pong_policy.py` and tests remain at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps the exact y164 precontact diagnostic variants as reproducible negative/inert evidence.

### Verification

- Full 80-seed counterfactuals reproduced the v3.27 baseline and found no acceptable y164 intervention.
- No formal `run_eval.py` was run because no policy change was accepted.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_mid_up_y164_80seed_round57.jsonl`
  - `diagnostics/mid_up_y164_legacy_trigger_deltas_round57.json`
  - `diagnostics/counterfactual_mid_up_y164_precontact_80seed_round57.jsonl`
  - `diagnostics/mid_up_y164_precontact_deltas_round57.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- The tested y164 precontact local-action hypotheses are retained as negative or inert diagnostics.

### New Structural Issues

- The y164 family appears score/phase-linked (`0-2` for seeds `2,15`, `2-2` for `33,44,66`) and is not fixed by reversing the named precontact correction.
- The remaining actionable path is likely earlier setup comparison, a family marker, or a different residual family such as low-up y111/y54 or high-down y205/y171.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56 and Round 57 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Compare earlier setup windows for the 5 y164 seeds around the first shared divergence, or inspect the 8-seed low-up y54/y118 chain for an earlier non-local phase marker.
- Do not patch y164 precontact `NOOP` actions; full 80-seed counterfactual rejected or failed to support those interventions.

## Round 58 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Remaining score-`18` seeds remain `2,3,4,6,15,18,24,33,40,42,44,49,66`.

### Hypothesis Map

1. Local failure-family hypothesis: after y118 and y164 late-action probes were rejected, the 8-seed `loss/low_up/near_target` y54 family may be a cleaner residual target.
   - Explains: setup-phase analysis shows this route is pure loss for seeds `3,4,6,18,24,40,42,49`, unlike high-down routes that are mixed with many wins.
   - Evidence source: `diagnostics/setup_phase_score18_mixed_v3_27_round58.json` and `diagnostics/low_up_near_wide_offsets_v3_27_round58.json`.
   - Minimal diagnostic: test exact score-`1-2` y54 final, offset-2, and offset-3 action replacements over all 80 seeds.
   - Distinguishes this family from y118 by targeting the later `low_serve` point with `dy=-4,target=50`, not the opening `dy=-8` miss.
   - If true: one exact intervention should improve the 8 seeds with no regressions. If false: this family is not fixed by local low-serve action edits.

2. Basic model/action hypothesis: the y54 miss may be an action-phase issue around the low-serve late correction.
   - Explains: the tail alternates `DOWN,DOWN,UP,UP`, with offset-2 using `low_serve_late_noop_correction` and final `UP` still missing near target.
   - Evidence source: low-up near tail: final `x=202,y=54,pad=55,target=50,UP`; offset-2 `x=198,y=58,pad=47,UP`; offset-3 `x=194,y=62,pad=39,DOWN`.
   - Minimal diagnostic: counterfactual final `UP -> NOOP/DOWN`, offset-2 `UP -> NOOP/DOWN`, and offset-3 `DOWN -> NOOP/UP`.
   - Distinguishes over-aggressive upward correction from necessary action phase by testing both weaker and opposite actions.
   - If true: a replacement improves. If false: current local action sequence is protective, and the miss is caused earlier or by collision modeling.

3. Control-structure hypothesis: the residual floor needs earlier route/phase modeling instead of more local frame edits.
   - Explains: multiple clean subfamilies now reject intuitive local action edits; paddle dynamics analysis shows action effects depend strongly on paddle phase/history.
   - Evidence source: `diagnostics/paddle_dynamics_score18_mixed_v3_27_round58.json` and `diagnostics/point_phase_score18_mixed_v3_27_round58.json`.
   - Minimal diagnostic: run setup/phase analyzers and use them to select future route-level probes rather than adding a policy rule now.
   - Distinguishes structural issue from missed local patch by requiring any local counterfactual to improve seed-level outcomes.
   - If true: keep diagnostics, no policy patch, next compare route-level success/failure or collision boundary assumptions.

### Diagnostics Run

- Wide offset summaries:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --pattern loss/low_up/paddle_below_target --offsets 24,28,32,36,40,48,56,64,72,80,96,112,128,144,160,176,192,208 --output diagnostics/low_up_paddle_below_wide_offsets_v3_27_round58.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --pattern loss/mid_up/paddle_below_target --offsets 24,28,32,36,40,48,56,64,72,80,96,112,128,144,160,176,192,208 --output diagnostics/mid_up_paddle_below_wide_offsets_v3_27_round58.json`
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --pattern loss/high_down/paddle_above_target --offsets 24,28,32,36,40,48,56,64,72,80,96,112,128,144,160,176,192,208 --output diagnostics/high_down_paddle_above_wide_offsets_v3_27_round58.json`
- Phase/setup analyzers:
  - `.venv/bin/python scripts/analyze_setup_phase.py --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --output diagnostics/setup_phase_score18_mixed_v3_27_round58.json --top 12`
  - `.venv/bin/python scripts/analyze_paddle_dynamics.py --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --output diagnostics/paddle_dynamics_score18_mixed_v3_27_round58.json --top 12`
  - `.venv/bin/python scripts/analyze_point_phase.py --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --output diagnostics/point_phase_score18_mixed_v3_27_round58.json --top 12`
- Low-up y54 wide tail:
  - `.venv/bin/python scripts/summarize_loss_tail_offsets.py diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --pattern loss/low_up/near_target --offsets 1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,36,40,48,56,64,72,80,96,112,128,144,160 --output diagnostics/low_up_near_wide_offsets_v3_27_round58.json`
- Added diagnostic-only exact score-`1-2` y54 counterfactual variants for final, offset-2, and offset-3 frames.
- Counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79 --variants baseline,once_low_up_score12_y54_final_up_to_noop_h1,once_low_up_score12_y54_final_up_to_down_h1,once_low_up_score12_y54_offset2_up_to_noop_h1,once_low_up_score12_y54_offset2_up_to_down_h1,once_low_up_score12_y54_offset3_down_to_noop_h1,once_low_up_score12_y54_offset3_down_to_up_h1 --jsonl diagnostics/counterfactual_low_up_score12_y54_80seed_round58.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_low_up_score12_y54_80seed_round58.jsonl --output diagnostics/low_up_score12_y54_deltas_round58.json`
- Syntax check:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`

### Results

- Setup/phase diagnostics:
  - `loss/low_up/near_target` has a pure-loss route: `pre_y=200..219;entry_y=200..219;right_y=0..19;entry_pad=180..199;right_pad=140..159`, count `8`, seeds `3,4,6,18,24,40,42,49`.
  - Paddle dynamics remains strongly phase/history dependent: action-only sign accuracy `0.533`; adding action+paddle_mod4+error_bin+previous_delta_sign raises sign accuracy to `0.911`.
  - Point-phase analysis again shows wins usually resolve near RAM `x=187..191`, while losses remain right-moving at `x=202..205`.
- Low-up y54 tail:
  - Final: `score=1-2,x=202,y=54,dy=-4,target=50,pad=55,action=UP,mode=low_serve`.
  - Offset 2: `x=198,y=58,pad=47,action=UP,mode=low_serve_late_noop_correction`.
  - Offset 3: `x=194,y=62,pad=39,action=DOWN,mode=low_serve`.
- Counterfactuals:
  - Baseline v3.27: mean `18.98`, min `18`, max `20`.
  - Final `UP -> NOOP`: mean `18.48`, min `13`, max `20`, `0` improved, `8` worsened by `-5`.
  - Final `UP -> DOWN`: mean `18.48`, min `13`, max `20`, `0` improved, `8` worsened by `-5`.
  - Offset-2 `UP -> NOOP`: mean `17.88`, min `7`, max `20`, `0` improved, `8` worsened by `-11`.
  - Offset-2 `UP -> DOWN`: mean `18.98`, min `18`, max `20`, no seed-level change.
  - Offset-3 `DOWN -> NOOP`: mean `18.98`, min `18`, max `20`, no seed-level change.
  - Offset-3 `DOWN -> UP`: mean `18.48`, min `13`, max `20`, `0` improved, `8` worsened by `-5`.

### Supported And Rejected Hypotheses

- Supported: low-up y54 is a clean reproducible 8-seed residual family with a pure-loss setup route.
- Rejected: final `UP` is too aggressive; weakening or reversing it collapses the same 8 seeds.
- Rejected: offset-2 `low_serve_late_noop_correction` is harmful for this family; replacing its `UP` with `NOOP` is catastrophic, and `DOWN` is inert.
- Rejected: offset-3 `DOWN` is the causal error; `NOOP` is inert and `UP` regresses.
- Supported structurally: even pure-looking residual routes are not fixed by local action substitution, so next probes should compare earlier route/collision states or state-model assumptions.

### Policy Change

- No policy change accepted.
- `pong_policy.py` and tests remain at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps exact score-`1-2` y54 diagnostic variants as reproducible negative/inert evidence.

### Verification

- Full 80-seed counterfactual reproduced the v3.27 baseline and found no acceptable y54 intervention.
- No formal `run_eval.py` was run because no policy change was accepted.

### Durable Assets

- Added:
  - `diagnostics/low_up_paddle_below_wide_offsets_v3_27_round58.json`
  - `diagnostics/mid_up_paddle_below_wide_offsets_v3_27_round58.json`
  - `diagnostics/high_down_paddle_above_wide_offsets_v3_27_round58.json`
  - `diagnostics/setup_phase_score18_mixed_v3_27_round58.json`
  - `diagnostics/paddle_dynamics_score18_mixed_v3_27_round58.json`
  - `diagnostics/point_phase_score18_mixed_v3_27_round58.json`
  - `diagnostics/low_up_near_wide_offsets_v3_27_round58.json`
  - `diagnostics/counterfactual_low_up_score12_y54_80seed_round58.jsonl`
  - `diagnostics/low_up_score12_y54_deltas_round58.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- The tested y54 local-action hypotheses are retained as negative or inert diagnostics.

### New Structural Issues

- The residual floor now has several clean families where local action replacements are harmful or inert; this increases evidence for a state/model or route-level phase issue.
- The setup-route tool identified pure-loss routes; next diagnostics should compare pure-loss routes with nearby pure-win routes before another action patch.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-58 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Use `setup_phase_score18_mixed_v3_27_round58.json` to compare pure-loss routes against nearby pure-win routes, especially low-up y54 versus low-up wins around `pre_y=40..59` or `pre_y=120..139`.
- Avoid final/offset2/offset3 local y54 patches; full 80-seed counterfactual rejected them.

## Round 59 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Remaining score-`18` seeds remain `2,3,4,6,15,18,24,33,40,42,44,49,66`.

### Hypothesis Map

1. Local failure-family hypothesis: the score-`1-2` low-up y54 family may be better explained by active setup route than by the last few contact actions.
   - Explains: seeds `3,4,6,18,24,40,42,49` share a pure-loss active route, while the same seeds also contain a nearby pure-win sibling route.
   - Evidence source: `diagnostics/route_overview_active_score18_mixed_round59.json` and `diagnostics/route_compare_low_up_y54_active_round59.json`.
   - Minimal diagnostic: compare active first-right routes and test route-distinguishing actions at x150 and x182 over all 80 seeds.
   - Distinguishes route correlation from causal action by requiring an action replacement to improve the target route instead of merely matching a sibling route's summary.
   - If true: an exact route-frame replacement should improve the 8 target seeds without harming others. If false: the route is predictive but not locally correctable by those actions.

2. Basic model/action hypothesis: the route analyzer must exclude inactive RAM x205/x0 frames before using setup buckets as evidence.
   - Explains: the first pass produced a misleading route keyed by inactive `x=205` frames rather than active right-moving approach.
   - Evidence source: the rejected first output `diagnostics/route_compare_low_up_y54_round59.json` and the corrected active-only script output.
   - Minimal diagnostic: require `dx>0`, `ball_x>=120`, `ball_y!=0`, `ball_x not in (0,205)`, and non-inactive mode for the first-right anchor.
   - Distinguishes real setup state from reset/inactive state by comparing the corrected route overview with point traces.
   - If true: future route diagnostics should use active-only anchors. If false: the old setup-route conclusions would remain usable, but this round rejected that assumption.

3. Control-structure hypothesis: remaining residuals may need a phase/family marker or collision-boundary model rather than more one-frame action patches.
   - Explains: multiple clean residual families now reject intuitive late or route-frame action substitutions.
   - Evidence source: Round 56 y118, Round 57 y164, Round 58 y54 tail, and Round 59 y54 route counterfactuals.
   - Minimal diagnostic: compare mixed-route wins/losses at contact and left-return boundary, especially routes that contain both many wins and a few losses.
   - Distinguishes structural control error from missed local patch by testing whether the same active route can be separated by contact-phase features.
   - If true: next assets should model phase or route features before any policy guard. If false: a future single-frame counterfactual should show clean 80-seed improvement.

### Diagnostics Run

- Added `scripts/compare_setup_routes.py` to summarize point routes relative to first active right-moving approach.
- Initial route comparison:
  - `.venv/bin/python scripts/compare_setup_routes.py --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --route 'pre_y=200..219;entry_y=200..219;right_y=0..19;entry_pad=180..199;right_pad=140..159' --route 'pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=100..119;right_pad=60..79' --route 'pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=100..119;right_pad=80..99' --output diagnostics/route_compare_low_up_y54_round59.json --offsets=-16,-12,-8,-4,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,20,24 --top 12`
- Corrected route overview after detecting inactive-frame anchoring:
  - `.venv/bin/python scripts/compare_setup_routes.py --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --output diagnostics/route_overview_active_score18_mixed_round59.json --offsets=-24,-20,-16,-12,-8,-4,0,4,8,12,16,20,24,28,32 --top 20`
- Active y54 route comparison:
  - `.venv/bin/python scripts/compare_setup_routes.py --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --route 'pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=100..119;right_pad=60..79' --route 'pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=100..119;right_pad=80..99' --route 'pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=60..79' --output diagnostics/route_compare_low_up_y54_active_round59.json --offsets=-16,-12,-8,-4,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,20,24 --top 12`
- Added diagnostic-only route-frame counterfactual variants for score-`1-2` low-up y54 x150 and x182.
- Full 80-seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --variants baseline,once_low_up_score12_y54_early_x150_down_to_noop_h1,once_low_up_score12_y54_early_x150_down_to_up_h1,once_low_up_score12_y54_mid_x182_up_to_noop_h1,once_low_up_score12_y54_mid_x182_up_to_down_h1 --jsonl diagnostics/counterfactual_low_up_score12_y54_route_80seed_round59.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_low_up_score12_y54_route_80seed_round59.jsonl --output diagnostics/low_up_score12_y54_route_deltas_round59.json`
- Syntax checks:
  - `.venv/bin/python -m py_compile scripts/compare_setup_routes.py scripts/probe_counterfactual_actions.py`

### Results

- The first route comparison was rejected as a diagnostic-tooling bug because it anchored on inactive `x=205` frames.
- Corrected active route overview found:
  - Mixed route count `130`: `pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119`, outcomes `122` wins and `8` high-down paddle-above losses.
  - Pure-win sibling count `127`: `pre_y=140..159;entry_y=140..159;right_y=180..199;entry_pad=180..199;right_pad=160..179`.
  - Pure y54 loss count `8`: `pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=100..119;right_pad=60..79`, seeds `3,4,6,18,24,40,42,49`.
  - Pure y54 win sibling count `8`: `pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=100..119;right_pad=80..99`, same seeds.
- Active y54 route comparison:
  - Loss route offset 0: `x=134,y=122,pad=115,action=UP,mode=low_serve`; offset 4: `x=150,y=106,pad=46,action=DOWN`; offset 12: `x=182,y=74,pad=61,action=UP`; offset 16: `x=198,y=58,pad=47,action=UP,mode=low_serve_late_noop_correction`.
  - Sibling win route offset 0: `x=134,y=122,pad=113,action=UP`; offset 4: `x=150,y=106,pad=65,action=UP`; offset 12: `x=182,y=74,pad=47,action=NOOP`; offset 16 already returned left.
- Counterfactuals:
  - Baseline v3.27: mean `18.98`, min `18`, max `20`.
  - x150 `DOWN -> NOOP`: `0` improved, `8` worsened, `72` unchanged; mean delta `-0.9`, min delta `-9`.
  - x150 `DOWN -> UP`: `0` improved, `8` worsened, `72` unchanged; mean delta `-3.7`, min delta `-37`.
  - x182 `UP -> NOOP`: `0` improved, `8` worsened, `72` unchanged; mean delta `-0.1`, min delta `-1`.
  - x182 `UP -> DOWN`: `0` improved, `8` worsened, `72` unchanged; mean delta `-0.5`, min delta `-5`.

### Supported And Rejected Hypotheses

- Supported: active route buckets are useful for explaining reproducible residual families, but only after excluding inactive frames.
- Supported: the y54 pure-loss and pure-win sibling routes expose a real phase/setup difference.
- Rejected: changing the y54 loss route to resemble sibling route actions at x150 or x182 is a causal repair; all tested variants harmed the exact target seeds.
- Rejected: another local y54 route-frame action patch is justified without a stronger contact/collision diagnostic.
- Supported structurally: route identity is predictive, but direct action substitution at sampled frames does not safely transform the route outcome.

### Policy Change

- No policy change accepted.
- `pong_policy.py` and tests remain at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps the route-frame counterfactual variants as reproducible negative evidence.

### Verification

- Full 80-seed counterfactual reproduced the v3.27 baseline and found no acceptable y54 route intervention.
- Syntax checks passed for `scripts/compare_setup_routes.py` and `scripts/probe_counterfactual_actions.py`.
- No formal `run_eval.py` was run because no policy change was accepted.

### Durable Assets

- Added:
  - `scripts/compare_setup_routes.py`
  - `diagnostics/route_compare_low_up_y54_round59.json`
  - `diagnostics/route_overview_active_score18_mixed_round59.json`
  - `diagnostics/route_compare_low_up_y54_active_round59.json`
  - `diagnostics/counterfactual_low_up_score12_y54_route_80seed_round59.jsonl`
  - `diagnostics/low_up_score12_y54_route_deltas_round59.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- The inactive-frame route conclusion from `diagnostics/route_compare_low_up_y54_round59.json` is explicitly rejected but kept as negative tooling evidence.
- The x150 and x182 y54 action replacements are rejected and retained only as diagnostics.

### New Structural Issues

- The y54 route difference is predictive but not locally causal under direct x150/x182 edits.
- The route tooling bug shows that setup analyzers must explicitly separate inactive/reset RAM states from active approach states.
- A mixed route with `122` wins and `8` losses is a better next discriminator than another pure-route local action edit, because it can test contact-phase features while holding coarse setup route fixed.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-59 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Compare the mixed active route `pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119` between its `122` wins and `8` losses, focusing on contact/left-return boundary features rather than immediate action swaps.
- Avoid further y54 x150/x182 local patches; full 80-seed counterfactual rejected them.

## Round 60 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Remaining score-`18` seeds remain `2,3,4,6,15,18,24,33,40,42,44,49,66`.

### Hypothesis Map

1. Local failure-family hypothesis: the mixed active route from Round 59 hides a score-`1-1` high-down y205 loss family.
   - Explains: the route has `122` wins and `8` losses, and the losses are all `loss/high_down/paddle_above_target` on seeds `3,4,6,18,24,40,42,49`.
   - Evidence source: `diagnostics/route_overview_active_score18_mixed_round59.json` and `diagnostics/mixed_route_high_down_outcome_compare_round60.json`.
   - Minimal diagnostic: compare win/loss offsets inside the same route, then test exact x186, x194, and final-frame actions across all 80 seeds.
   - Distinguishes coarse-route correlation from local action causality by checking whether one-frame replacements change score.
   - If true: one exact counterfactual should improve those 8 seeds without regressions. If false: retain the diagnostic and move earlier or to collision/phase modeling.

2. Basic model/action hypothesis: the loss may be caused by contact-boundary prediction around high y205, not by the immediately visible action.
   - Explains: at last active right frame the loss is `x=202,y=205,pad=169,target=196,DOWN`, while nearby earlier frames look reasonable or even near-target.
   - Evidence source: last-right offsets in `diagnostics/mixed_route_high_down_outcome_compare_round60.json`.
   - Minimal diagnostic: test x186 `NOOP`, x194 `UP`, and final `DOWN` action replacements before changing policy.
   - Distinguishes action error from boundary/model error by checking if alternative actions affect the episode score.
   - If true: action substitutions are inert or harmful; next compare collision/left-return state. If false: a replacement improves the exact family.

3. Control-structure hypothesis: repeated local failures after route comparison indicate the controller needs a stronger phase/family model before another guard.
   - Explains: y54 pure routes and high-down mixed routes are predictive but not repaired by sampled local action edits.
   - Evidence source: Round 58-60 counterfactual deltas and `failures.jsonl` negative notes.
   - Minimal diagnostic: split same-route outcomes by contact and post-contact features, or trace the earlier family marker before first active right.
   - Distinguishes missing guard from missing state model by requiring any proposed guard to have a causal counterfactual first.
   - If true: build a contact/return-boundary diagnostic. If false: a later counterfactual can still justify one narrow policy change.

### Diagnostics Run

- Added `scripts/compare_route_outcomes.py` to compare outcome groups within one active setup route using the same active-right filter as `scripts/compare_setup_routes.py`.
- Mixed route outcome comparison:
  - `.venv/bin/python scripts/compare_route_outcomes.py --jsonl diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl --route 'pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119' --output diagnostics/mixed_route_high_down_outcome_compare_round60.json --first-offsets=-8,-4,0,1,2,3,4,5,6,7,8,10,12,16,20,24,28,32,36,40 --last-offsets=-24,-20,-16,-12,-8,-6,-4,-2,0,1,2,3,4 --top 12`
- Added diagnostic-only counterfactual variants for score-`1-1` high-down y205:
  - x186 `NOOP -> DOWN/UP`
  - x194 `UP -> NOOP/DOWN`
  - final x202 `DOWN -> NOOP/UP`
- Full 80-seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --variants baseline,once_high_down_score11_y205_x186_noop_to_down_h1,once_high_down_score11_y205_x186_noop_to_up_h1,once_high_down_score11_y205_x194_up_to_noop_h1,once_high_down_score11_y205_x194_up_to_down_h1,once_high_down_score11_y205_final_down_to_noop_h1,once_high_down_score11_y205_final_down_to_up_h1 --jsonl diagnostics/counterfactual_high_down_score11_y205_80seed_round60.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score11_y205_80seed_round60.jsonl --output diagnostics/high_down_score11_y205_deltas_round60.json`
- Failure note:
  - `.venv/bin/python scripts/record_failure.py --failure-type high_down_score11_y205_local_actions_rejected ...`
- Syntax checks:
  - `.venv/bin/python -m py_compile scripts/compare_route_outcomes.py`
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`

### Results

- Within the mixed route, the `8` losses are all seeds `3,4,6,18,24,40,42,49` at score `1-1`.
- Loss contact sequence:
  - last offset `-4`: `x=186,y=173,dy=8,target=185,pad=184,action=NOOP`.
  - last offset `-2`: `x=194,y=189,dy=8,target=189,pad=197,action=UP`.
  - last offset `0`: `x=202,y=205,dy=8,target=196,pad=169,action=DOWN`.
- The same coarse route's wins mostly resolve earlier at last active-right `x=190,y=148,dy=-8,target=144,pad=123`, then return left; this confirms the mixed route bucket is too coarse to imply a common contact phase.
- Counterfactuals:
  - Baseline v3.27: mean `18.98`, min `18`, max `20`.
  - x186 `NOOP -> DOWN`: unchanged all 80 seeds, `8` interventions.
  - x186 `NOOP -> UP`: unchanged all 80 seeds, `8` interventions.
  - x194 `UP -> NOOP`: unchanged all 80 seeds, `8` interventions.
  - x194 `UP -> DOWN`: unchanged all 80 seeds, `8` interventions.
  - final x202 `DOWN -> NOOP`: unchanged all 80 seeds, `8` interventions.
  - final x202 `DOWN -> UP`: `0` improved, `8` worsened from `18` to `13`; mean `18.48`, min `13`, max `20`.

### Supported And Rejected Hypotheses

- Supported: the mixed route contains a clean score-`1-1` high-down y205 subfamily, and outcome splitting is more informative than route buckets alone.
- Rejected: x186 `NOOP`, x194 `UP`, or final `DOWN` are individually sufficient causal fixes for this family.
- Rejected: reversing final `DOWN` is safe; it regresses the exact 8 target seeds to score `13`.
- Supported structurally: the failure needs earlier contact/phase or collision-boundary explanation before any policy guard.

### Policy Change

- No policy change accepted.
- `pong_policy.py` and tests remain at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps the high-down score-`1-1` variants as reproducible negative/inert evidence.

### Verification

- Full 80-seed counterfactual reproduced the v3.27 baseline and found no acceptable high-down score-`1-1` intervention.
- Syntax checks passed for `scripts/compare_route_outcomes.py` and `scripts/probe_counterfactual_actions.py`.
- No formal `run_eval.py` was run because no policy change was accepted.

### Durable Assets

- Added:
  - `scripts/compare_route_outcomes.py`
  - `diagnostics/mixed_route_high_down_outcome_compare_round60.json`
  - `diagnostics/counterfactual_high_down_score11_y205_80seed_round60.jsonl`
  - `diagnostics/high_down_score11_y205_deltas_round60.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- The x186/x194/final score-`1-1` high-down action hypotheses are rejected or inert and retained only as diagnostics.

### New Structural Issues

- Coarse active setup routes can mix fundamentally different contact outcomes; route comparison should split by last active-right or post-contact phase.
- The y205 loss appears insensitive to one-step edits at the sampled contact frames, so future diagnostics should inspect earlier pre-contact phase markers or post-contact collision boundary rather than another immediate action replacement.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-60 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Build or run a contact-boundary diagnostic that compares the high-down score-`1-1` y205 loss against successful high-down contacts with similar `score=1-1` or similar `x=186..202` states.
- Avoid score-`1-1` high-down x186/x194/final action patches; full 80-seed counterfactual rejected or failed to support them.

## Round 61 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- The full 80-seed point trace generated this round reproduces the same seed score distribution: `13` seeds at `18`, `56` at `19`, `11` at `20`.

### Hypothesis Map

1. Local failure-family hypothesis: the Round 60 y205 family may be one high-down subfamily, not the dominant high-down failure.
   - Explains: score-`18` mixed traces over-emphasized seeds `3,4,6,18,24,40,42,49`, but full 80-seed data may expose larger high-down clusters.
   - Evidence source: `diagnostics/point_traces_score18_mixed_v3_27_round56.jsonl` versus the new full trace `diagnostics/point_traces_80seed_v3_27_round61.jsonl`.
   - Minimal diagnostic: generate all 80 point traces and group high-down losses by final contact state and score-after point.
   - Distinguishes residual-floor subfamily from global high-down issue by comparing family counts across all seeds.
   - If true: do not over-prioritize y205. If false: y205 remains the main high-down target.

2. Basic model/action hypothesis: high-down failures are contact-boundary families with different final states, so single sampled action edits cannot generalize.
   - Explains: Round 60 x186/x194/final edits were inert or harmful even though the local trace looked plausible.
   - Evidence source: `diagnostics/high_down_score11_y205_deltas_round60.json` and `diagnostics/high_down_contact_boundary_80seed_round61.json`.
   - Minimal diagnostic: group final x/y/dx/dy/score/pattern and inspect offsets around last active-right frame.
   - Distinguishes action-phase error from contact-boundary classification by finding whether multiple high-down families share the same action-control point.
   - If true: next counterfactual should target a larger repeated family only after its own boundary diagnostic. If false: one contact action might cover several families.

3. Control-structure hypothesis: a family-level controller or phase marker is more appropriate than adding another score-specific high-down rule now.
   - Explains: high-down has at least four loss clusters with different scores and final states; accepted high-down fixes so far were score-chain guarded at `4-0`, `8-0`, `12-0`, `16-0`.
   - Evidence source: current policy history plus full high-down boundary grouping.
   - Minimal diagnostic: compare the dominant `0-1` y173 family against already accepted high-down bounce families before any new rule.
   - Distinguishes reusable mechanism from rule stacking by checking whether the accepted score-chain bounce mechanism applies or is already exhausted.
   - If true: use induction/phase diagnostics before a patch. If false: a narrow `0-1` family counterfactual may be justified next.

### Diagnostics Run

- Full 80-seed point trace:
  - `.venv/bin/python scripts/trace_points.py --seeds 0..79 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_80seed_v3_27_round61.jsonl`
- High-down contact-boundary grouping over the new full trace:
  - Generated `diagnostics/high_down_contact_boundary_80seed_round61.json` from point-record final states and offsets around last active-right frame.

### Results

- Full trace score distribution matches retained v3.27 expectations:
  - `13` score-`18` seeds: `2,3,4,6,15,18,24,33,40,42,44,49,66`.
  - `56` score-`19` seeds.
  - `11` score-`20` seeds.
- High-down loss families over all 80 seeds:
  - Count `26`: `final_x=204,final_y=173,dx=4,dy=8,score_after=0-1,pattern=loss/high_down/paddle_above_target`; seeds `1,10,11,16,19,21,22,26,28,29,30,34,35,36,38,39,46,51,52,53,55,56,59,60,76,78`.
  - Count `8`: `final_x=202,final_y=205,dx=4,dy=8,score_after=1-2,pattern=loss/high_down/paddle_above_target`; seeds `3,4,6,18,24,40,42,49`.
  - Count `3`: `final_x=203,final_y=171,dx=4,dy=8,score_after=2-2,pattern=loss/high_down/paddle_above_target`; seeds `33,44,66`.
  - Count `2`: `final_x=203,final_y=203,dx=4,dy=4,score_after=0-2,pattern=loss/high_down/near_target`; seeds `2,15`.
- The Round 60 y205 family is real but not the dominant high-down family; the larger high-down cluster is the opening `0-1` y173 family across 26 score-`19` seeds.

### Supported And Rejected Hypotheses

- Supported: full 80-seed point traces are needed before prioritizing a residual family; the score-18-only trace biased attention toward y205.
- Supported: high-down failures split into multiple score/contact clusters, so the next high-information experiment should compare the 26-seed y173 cluster with accepted high-down bounce mechanics.
- Rejected: y205 should be the sole next high-down target after Round 60; it is smaller and already rejected under local action edits.
- Unresolved: whether the 26-seed opening y173 family has a safe causal action point or is another inert final-contact boundary.

### Policy Change

- No policy change accepted.
- `pong_policy.py` and tests remain at v3.27.

### Verification

- Full 80-seed point trace completed with the retained baseline score distribution.
- No formal `run_eval.py` was run because no policy change was accepted.

### Durable Assets

- Added:
  - `diagnostics/point_traces_80seed_v3_27_round61.jsonl`
  - `diagnostics/high_down_contact_boundary_80seed_round61.json`
- Updated:
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- The priority of the y205 local-action family is downgraded; it remains a negative diagnostic target, not a patch candidate.

### New Structural Issues

- The largest visible high-down family is not currently the score floor because it occurs in score-`19` seeds; improving it could raise mean/max distribution but must not harm score-`18` floor families.
- Any stateful or score-chain high-down change must be validated on all 80 seeds because the same geometry has historically hidden score/phase splits.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-61 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Diagnose the 26-seed opening `0-1` high-down y173 family against accepted high-down bounce mechanisms and successful nearby first-point outcomes.
- Do not patch it yet; first test whether a counterfactual at its contact/bounce candidate is causal and whether it preserves the 13 score-`18` seeds.

## Round 62 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Full 80-seed trace from Round 61 remains the current diagnostic base for this round.

### Hypothesis Map

1. Local failure-family hypothesis: the 26-seed opening `0-1` high-down y173 family might have a reusable local action correction.
   - Explains: this is the largest high-down cluster in the full 80-seed trace, all at opening score `0-0` before the lost point.
   - Evidence source: `diagnostics/high_down_contact_boundary_80seed_round61.json` and `diagnostics/high_down_outcome_compare_80seed_round62.json`.
   - Minimal diagnostic: counterfactually test the exact x144 bounce `NOOP`, x148 `UP`, x164 `DOWN`, and final `DOWN` actions over all 80 seeds.
   - Distinguishes safe local repair from correlated action signatures by requiring score improvement without hidden-seed collapse.
   - If true: one exact trigger improves the 26 target seeds and preserves score-`18` floor seeds. If false: do not patch this opener.

2. Basic model/action hypothesis: this family may look like accepted high-down bounce mechanics but have the opposite action phase.
   - Explains: accepted high-down fixes replaced score-chain bounce `DOWN->NOOP`, while this opening family has mostly bounce `NOOP` at `x=144,y=53,target=161,pad=163`.
   - Evidence source: `scripts/compare_high_down_history.py` output in `diagnostics/high_down_history_x150_80seed_round62.json`.
   - Minimal diagnostic: test bounce `NOOP->DOWN/UP` and downstream x148/x164/final replacements.
   - Distinguishes mechanism reuse from hidden phase split by checking whether the accepted bounce idea transfers to `0-0`.
   - If true: bounce replacement helps. If false: accepted score-chain correction should not be generalized backward to opening score.

3. Control-structure hypothesis: opening losses may be part of a favorable downstream score/phase chain rather than an isolated failure to remove.
   - Explains: changing a first-point loss can alter subsequent deterministic score chains and may reduce final score despite apparently fixing the first miss.
   - Evidence source: prior score-chain hazards plus this round's full-episode counterfactual scores.
   - Minimal diagnostic: full 80-seed counterfactual, not a first-point-only success metric.
   - Distinguishes first-point improvement from final-score improvement by evaluating completed episodes.
   - If true: local opener patches regress final score and should be recorded as negative. If false: final score should improve.

### Diagnostics Run

- High-down outcome anchors over all 80 v3.27 point traces:
  - `.venv/bin/python scripts/compare_high_down_outcomes.py diagnostics/point_traces_80seed_v3_27_round61.jsonl --output diagnostics/high_down_outcome_compare_80seed_round62.json`
- High-down x150 history comparison:
  - `.venv/bin/python scripts/compare_high_down_history.py diagnostics/point_traces_80seed_v3_27_round61.jsonl --history 16 --output diagnostics/high_down_history_x150_80seed_round62.json`
- Added diagnostic-only counterfactual variants for opening y173:
  - x144 bounce `NOOP -> DOWN/UP`
  - x148 `UP -> NOOP/DOWN`
  - x164 `DOWN -> NOOP/UP`
  - final x204 `DOWN -> NOOP/UP`
- Full 80-seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --variants baseline,once_high_down_score00_y173_bounce_noop_to_down_h1,once_high_down_score00_y173_bounce_noop_to_up_h1,once_high_down_score00_y173_x148_up_to_noop_h1,once_high_down_score00_y173_x148_up_to_down_h1,once_high_down_score00_y173_x164_down_to_noop_h1,once_high_down_score00_y173_x164_down_to_up_h1,once_high_down_score00_y173_final_down_to_noop_h1,once_high_down_score00_y173_final_down_to_up_h1 --jsonl diagnostics/counterfactual_high_down_score00_y173_80seed_round62.jsonl`
- Delta summary:
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score00_y173_80seed_round62.jsonl --output diagnostics/high_down_score00_y173_deltas_round62.json`
- Failure note:
  - `.venv/bin/python scripts/record_failure.py --failure-type high_down_score00_y173_local_actions_rejected ...`
- Syntax check:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`

### Results

- Outcome comparison:
  - x150/y61 anchor has `26` opening high-down losses, all score `0-0`, action `UP`, mean paddle error `23.308`.
  - The same anchor appears in `508` wins, mostly later scores, with action distribution dominated by `NOOP`; this is a correlated phase signature, not sufficient evidence for a patch.
- History comparison:
  - The opening loss family has a highly repeatable first-point oscillation ending at x144 bounce `NOOP` then x148 `UP`.
  - Accepted score-chain high-down modes appear in win histories, confirming this opener is not the same as the accepted `4-0/8-0/12-0/16-0` bounce correction.
- Counterfactuals:
  - Baseline v3.27: mean `18.98`, min `18`, max `20`.
  - x144 bounce `NOOP -> DOWN`: `0` improved, `22` worsened, mean delta `-3.025`, min delta `-11`, min score `8`.
  - x144 bounce `NOOP -> UP`: `0` improved, `22` worsened, mean delta `-3.025`, min delta `-11`, min score `8`.
  - x148 `UP -> NOOP`: `0` improved, `28` worsened, mean delta `-3.625`, min delta `-11`, min score `8`.
  - x148 `UP -> DOWN`: `0` improved, `26` worsened, mean delta `-3.575`, min delta `-11`, min score `8`.
  - x164 `DOWN -> NOOP`: `0` improved, `26` worsened, mean delta `-3.575`, min delta `-11`, min score `8`.
  - x164 `DOWN -> UP`: `0` improved, `26` worsened, mean delta `-3.575`, min delta `-11`, min score `8`.
  - final x204 `DOWN -> NOOP`: `0` improved, `26` worsened, mean delta `-3.575`, min delta `-11`, min score `8`.
  - final x204 `DOWN -> UP`: `0` improved, `26` worsened, mean delta `-3.575`, min delta `-11`, min score `8`.

### Supported And Rejected Hypotheses

- Supported: opening y173 is a real, reproducible, large high-down family.
- Rejected: the accepted high-down bounce mechanism transfers to opening `0-0`; bounce `NOOP` replacement collapses touched seeds.
- Rejected: x148, x164, or final-frame local action edits are safe repairs; every tested replacement regresses final score.
- Supported structurally: the opening loss appears tied to downstream deterministic score-chain stability; changing it can reduce final score sharply.

### Policy Change

- No policy change accepted.
- `pong_policy.py` and tests remain at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps opening y173 variants as negative diagnostics only.

### Verification

- Full 80-seed counterfactual reproduced the v3.27 baseline and rejected all opening y173 local-action interventions.
- Syntax check passed for `scripts/probe_counterfactual_actions.py`.
- No formal `run_eval.py` was run because no policy change was accepted.

### Durable Assets

- Added:
  - `diagnostics/high_down_outcome_compare_80seed_round62.json`
  - `diagnostics/high_down_history_x150_80seed_round62.json`
  - `diagnostics/counterfactual_high_down_score00_y173_80seed_round62.jsonl`
  - `diagnostics/high_down_score00_y173_deltas_round62.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- Opening y173 local-action hypotheses are rejected and retained only as negative evidence.

### New Structural Issues

- Not every recurring loss should be removed; some first-point losses are embedded in later score-chain behavior that yields high final scores.
- Future probes should prefer score-`18` floor families or structural phase markers over improving score-`19` seeds' first point.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-62 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Return to the score-`18` floor families with the full 80-seed trace now available.
- Prioritize families that affect final min or score-`18` seeds, not high-count first-point losses in score-`19` seeds.

## Round 63 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Full 80-seed trace source: `diagnostics/point_traces_80seed_v3_27_round61.jsonl`.

### Hypothesis Map

1. Local failure-family hypothesis: the current score-`18` floor is not one family but a small set of deterministic loss chains.
   - Explains: the 13 score-`18` seeds all lose exactly 3 points, but prior local probes hit only some endpoints.
   - Evidence source: Round 61 point trace plus new `diagnostics/score18_loss_chains_round63.json`.
   - Minimal diagnostic: summarize ordered loss signatures for final score-`18` seeds and mark which signatures already have accepted or rejected probes.
   - Distinguishes repeated endpoint failures from chain-level residuals by grouping complete seed-level loss order, not just final-contact geometry.
   - If true: focus on uncovered chain nodes. If false: return to broader full-trace family counts.

2. Basic model/action hypothesis: the uncovered score-`2-1` high-down y171 miss for seeds `33,44,66` might have a causal one-frame action point.
   - Explains: y171 is the only uncovered node in the 3-seed chain after accepted y111 and rejected y164 work.
   - Evidence source: `score18_loss_chains_round63.json`, `high_down_y171_route_compare_round63.json`, and `high_down_y171_neighbor_routes_round63.json`.
   - Minimal diagnostic: small seed counterfactual over x155 `DOWN`, x187 `UP`, and final x203 `DOWN` replacements before any full 80-seed expansion.
   - Distinguishes local action causality from correlated route phase by requiring improvement on seeds `33,44,66`.
   - If true: expand the best variant to full 80 seeds. If false: do not add a y171 guard.

3. Control-structure hypothesis: the residual floor is now mostly phase/chain structure, not isolated contact-frame errors.
   - Explains: the 8-seed family is fully covered by negative local probes, while the 3-seed and 2-seed families each have one uncovered middle high-down node.
   - Evidence source: accumulated negative counterfactuals plus the new chain coverage map.
   - Minimal diagnostic: compare neighboring active routes and only probe action points that the route diagnostic isolates.
   - Distinguishes patchable contact misses from structural chain state by showing whether local action changes are inert/harmful despite exact triggers.
   - If true: next work should compare family markers or route/phase setup across the 3-seed and 2-seed chains. If false: a targeted counterfactual should raise score above `18`.

### Diagnostics Run

- Score-18 floor family summary:
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_80seed_v3_27_round61.jsonl --output diagnostics/score18_floor_families_80seed_v3_27_round63.json`
- Added reusable chain diagnostic:
  - `scripts/summarize_loss_chains.py`
  - `.venv/bin/python scripts/summarize_loss_chains.py diagnostics/point_traces_80seed_v3_27_round61.jsonl --final-score 18 --output diagnostics/score18_loss_chains_round63.json`
- y171 route diagnostics:
  - `.venv/bin/python scripts/compare_route_outcomes.py --jsonl diagnostics/point_traces_80seed_v3_27_round61.jsonl --route 'pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=120..139;right_pad=100..119' --output diagnostics/high_down_y171_route_compare_round63.json --first-offsets=-12,-8,-4,0,4,8,12,16,20,24,28,32,36,40 --last-offsets=-24,-20,-16,-12,-8,-4,0,1,2,3,4 --top 12`
  - Neighbor route comparison written to `diagnostics/high_down_y171_neighbor_routes_round63.json`.
- Added diagnostic-only y171 variants:
  - x155 `DOWN -> NOOP/UP`
  - x187 `UP -> NOOP/DOWN`
  - final x203 `DOWN -> NOOP/UP`
- Small seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --variants baseline,once_high_down_score21_y171_x155_down_to_noop_h1,once_high_down_score21_y171_x155_down_to_up_h1,once_high_down_score21_y171_x187_up_to_noop_h1,once_high_down_score21_y171_x187_up_to_down_h1,once_high_down_score21_y171_final_down_to_noop_h1,once_high_down_score21_y171_final_down_to_up_h1 --jsonl diagnostics/counterfactual_high_down_score21_y171_probe_round63.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score21_y171_probe_round63.jsonl --output diagnostics/high_down_score21_y171_probe_deltas_round63.json`
- Failure note:
  - `high_down_score21_y171_local_actions_rejected_probe`
- Syntax check:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py scripts/summarize_loss_chains.py`

### Results

- Score-18 loss-chain split:
  - 8 seeds `3,4,6,18,24,40,42,49`: `low_up y118 -> high_down y205 -> low_up y54`. All three nodes already have local/route negative probes.
  - 3 seeds `33,44,66`: `low_up y111 -> high_down y171 -> mid_up y164`. y111 was accepted earlier, y164 local actions were rejected, and y171 was uncovered before this round.
  - 2 seeds `2,15`: `mid_down y154 -> high_down y203 -> mid_up y164`. y154 was accepted earlier, y164 local actions were rejected, and y203 remains uncovered.
- y171 route comparison:
  - The y171 route `pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=120..139;right_pad=100..119` contains only 3 losses, all seeds `33,44,66`.
  - Neighboring routes show nearby paddle-phase buckets can be wins or different low-up losses, so the route is a phase marker but not enough for a policy change.
- y171 small counterfactual:
  - Baseline subset: mean `18.20`, min `18`, max `20`.
  - x155 `DOWN -> NOOP`: 0 improved, 0 worsened, unchanged.
  - x155 `DOWN -> UP`: 0 improved, 3 worsened; seeds `33,44,66` drop `18 -> 16`.
  - x187 `UP -> NOOP`: 0 improved, 0 worsened, unchanged.
  - x187 `UP -> DOWN`: 0 improved, 3 worsened; seeds `33,44,66` drop `18 -> 14`.
  - final `DOWN -> NOOP`: 0 improved, 3 worsened; seeds `33,44,66` drop `18 -> 14`.
  - final `DOWN -> UP`: 0 improved, 0 worsened, unchanged.

### Supported And Rejected Hypotheses

- Supported: the score-`18` floor has exactly three deterministic ordered loss chains.
- Supported: the 8-seed chain is already covered by negative local probes and should not be re-probed at the same nodes.
- Rejected for this round: y171 x155/x187/final one-frame action replacements are useful; they are either inert or harmful on the target seeds.
- Still unresolved: whether the 2-seed y203 near-target node is patchable or just another chain-phase artifact.
- Supported structurally: the residual floor increasingly looks like a route/phase controller problem, not independent final-frame action mistakes.

### Policy Change

- No policy change accepted.
- `pong_policy.py` remains at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps the y171 variants as diagnostic-only negative probes.

### Verification

- Syntax checks passed for the edited diagnostic scripts.
- The small counterfactual reproduced the expected subset baseline.
- No formal `run_eval.py` was run because no candidate improved even the target small seed set.

### Durable Assets

- Added:
  - `scripts/summarize_loss_chains.py`
  - `diagnostics/score18_floor_families_80seed_v3_27_round63.json`
  - `diagnostics/score18_loss_chains_round63.json`
  - `diagnostics/high_down_y171_route_compare_round63.json`
  - `diagnostics/high_down_y171_neighbor_routes_round63.json`
  - `diagnostics/counterfactual_high_down_score21_y171_probe_round63.jsonl`
  - `diagnostics/high_down_score21_y171_probe_deltas_round63.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- y171 local-action variants are rejected as policy candidates and retained only as diagnostics.

### New Structural Issues

- The remaining uncovered y203 node for seeds `2,15` may be the next highest information-gain local probe, but the y171 result warns that middle high-down nodes can be inert or harmful when changed directly.
- The three residual floor chains may share a hidden phase marker after earlier accepted rules; an induction/phase diagnostic is becoming more valuable than adding more one-off endpoint probes.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-63 diagnostic additions.
- New Round 63 files are diagnostic assets only; no unrelated files were reverted.

### Next Automatic Exploration

- Diagnose the remaining 2-seed score-`18` chain node: `high_down/near_target;x=203;y=203;dy=4;score_after=0-2` for seeds `2,15`.
- Before adding any policy guard, compare its route/phase history against nearby wins and the rejected y171 family to see whether a shared structural marker exists.

## Round 64 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- Diagnostic base remains the Round 61 full 80-seed point trace and the Round 63 score-18 chain map.

### Hypothesis Map

1. Local failure-family hypothesis: the remaining 2-seed score-`18` chain node y203 might be the last patchable local failure.
   - Explains: seeds `2,15` share `mid_down y154 -> high_down y203 -> mid_up y164`, and only y203 was uncovered after Round 63.
   - Evidence source: `diagnostics/score18_loss_chains_round63.json`.
   - Minimal diagnostic: isolate the y203 route and test the smallest x151/x167/final counterfactuals on score-18 and neighbor seeds.
   - Distinguishes y203 from the rejected y171 family by using its own route and dy=4 contact sequence.
   - If true: a small probe should improve seeds `2,15` before any 80-seed run. If false: do not patch y203.

2. Basic model/action hypothesis: the y203 loss may be caused by staying in `DOWN` too long before a high dy=4 contact.
   - Explains: sibling win routes show x152/x168 use `UP` with the paddle already near target, while y203 has x151/x167 `DOWN` and the paddle still below the target.
   - Evidence source: `diagnostics/high_down_y203_neighbor_routes_round64.json`.
   - Minimal diagnostic: replace x151/x167 `DOWN` with `NOOP` or `UP`, and replace final `UP` with `NOOP` or `DOWN` as a guard against terminal-action causality.
   - Distinguishes correlated route phase from causal action by requiring completed-episode score improvement.
   - If true: one replacement raises seeds `2,15`. If false: the action difference is another phase correlate.

3. Control-structure hypothesis: the residual floor chains require an upstream phase/family marker rather than more local endpoint edits.
   - Explains: Rounds 63 and 64 both found pure route loss families, but local counterfactuals were inert or harmful.
   - Evidence source: y171 and y203 route/counterfactual diagnostics.
   - Minimal diagnostic: compare shared chain setup markers across the 8/3/2 seed families after earlier accepted rules.
   - Distinguishes structural family-state issues from local policy holes by showing both uncovered middle nodes reject direct action edits.
   - If true: next round should be an induction/phase diagnostic. If false: a local action variant would have improved at least one residual family.

### Diagnostics Run

- y203 route isolation:
  - `.venv/bin/python scripts/compare_route_outcomes.py --jsonl diagnostics/point_traces_80seed_v3_27_round61.jsonl --route 'pre_y=120..139;entry_y=120..139;right_y=140..159;entry_pad=60..79;right_pad=120..139' --output diagnostics/high_down_y203_route_compare_round64.json --first-offsets=-12,-8,-4,0,4,8,12,16,20,24,28,32,36,40 --last-offsets=-24,-20,-16,-12,-8,-4,0,1,2,3,4 --top 12`
- Neighbor route comparison:
  - `diagnostics/high_down_y203_neighbor_routes_round64.json`
- Added diagnostic-only y203 variants:
  - x151 `DOWN -> NOOP/UP`
  - x167 `DOWN -> NOOP/UP`
  - final x203 `UP -> NOOP/DOWN`
- Small seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --variants baseline,once_high_down_score01_y203_x151_down_to_noop_h1,once_high_down_score01_y203_x151_down_to_up_h1,once_high_down_score01_y203_x167_down_to_noop_h1,once_high_down_score01_y203_x167_down_to_up_h1,once_high_down_score01_y203_final_up_to_noop_h1,once_high_down_score01_y203_final_up_to_down_h1 --jsonl diagnostics/counterfactual_high_down_score01_y203_probe_round64.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score01_y203_probe_round64.jsonl --output diagnostics/high_down_score01_y203_probe_deltas_round64.json`
- Failure note:
  - `high_down_score01_y203_local_actions_rejected_probe`
- Syntax check:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`

### Results

- y203 route comparison:
  - The route `pre_y=120..139;entry_y=120..139;right_y=140..159;entry_pad=60..79;right_pad=120..139` contains only 2 losses, seeds `2,15`.
  - Neighboring routes with the same pre/entry/right-y buckets are either wins or the known y164 mid-up loss family, separated by entry/right paddle phase buckets.
  - Compared with the 26-seed sibling win route, y203 stays low with `DOWN` at x151/x167 before reaching a near-target final miss.
- y203 small counterfactual:
  - Baseline subset: mean `18.20`, min `18`, max `20`.
  - x151 `DOWN -> NOOP`: 0 improved, 2 worsened; seeds `2,15` drop `18 -> 16`.
  - x151 `DOWN -> UP`: 0 improved, 2 worsened; seeds `2,15` drop `18 -> 16`.
  - x167 `DOWN -> NOOP`: 0 improved, 2 worsened; seeds `2,15` drop `18 -> 16`.
  - x167 `DOWN -> UP`: 0 improved, 2 worsened; seeds `2,15` drop `18 -> 16`.
  - final `UP -> NOOP`: 0 improved, 2 worsened; seeds `2,15` collapse `18 -> -21`.
  - final `UP -> DOWN`: 0 improved, 2 worsened; seeds `2,15` collapse `18 -> -21`.

### Supported And Rejected Hypotheses

- Supported: y203 is a real, reproducible, pure 2-seed route family.
- Rejected: x151/x167 `DOWN` replacement is a safe fix; the sibling win action difference is not causal in this local form.
- Rejected: final-frame y203 `UP` replacement is safe; changing it causes full collapse for the target seeds.
- Supported structurally: after y171 and y203, all remaining score-18 chain endpoints with obvious local-action candidates are now either accepted earlier or rejected as local patches.

### Policy Change

- No policy change accepted.
- `pong_policy.py` remains at v3.27.
- `scripts/probe_counterfactual_actions.py` keeps y203 variants as diagnostic-only negative probes.

### Verification

- Syntax check passed for `scripts/probe_counterfactual_actions.py`.
- Small counterfactual reproduced the expected subset baseline.
- No formal `run_eval.py` was run because no candidate improved the target seeds.

### Durable Assets

- Added:
  - `diagnostics/high_down_y203_route_compare_round64.json`
  - `diagnostics/high_down_y203_neighbor_routes_round64.json`
  - `diagnostics/counterfactual_high_down_score01_y203_probe_round64.jsonl`
  - `diagnostics/high_down_score01_y203_probe_deltas_round64.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.
- y203 local-action variants are rejected as policy candidates and retained only as diagnostics.

### New Structural Issues

- The score-18 floor no longer has an obvious untested one-frame action repair.
- The next useful step should be an induction/phase diagnostic across the 8/3/2 residual chains, asking whether a shared upstream state marker can replace more brittle endpoint rules.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-64 diagnostic additions.
- New Round 64 files are diagnostic assets only; no unrelated files were reverted.

### Next Automatic Exploration

- Run a structural induction round over the score-18 chains:
  - compare pre-chain reset/serve/entry phase for the 8-seed, 3-seed, and 2-seed families;
  - identify whether family markers are already present before the first loss in each chain;
  - only after that decide whether a phase model, family marker, target smoothing, or state-machine simplification is warranted.

## Round 65 - 2026-05-10

### Current Best Baseline

- Starting and retained baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Formal 80-seed score from trial `48`: mean `18.975`, min `18`, max `20`.
- This was an induction/diagnostic round only; no policy candidate was evaluated with `run_eval.py`.

### Hypothesis Map

1. Local failure-family hypothesis: the three score-`18` residual chains may be identifiable before their first loss.
   - Explains: Rounds 63-64 showed all obvious y171/y203 endpoint action edits are inert or harmful, so useful leverage may require detecting the family before the endpoint.
   - Evidence source: `diagnostics/score18_loss_chains_round63.json` and new phase-marker diagnostics.
   - Minimal diagnostic: summarize reset, first active frame, first right route, previous point chain, and first-loss setup for all final score-`18` seeds.
   - Distinguishes pre-chain family markers from post-hoc loss labels by checking features available before or at first active route entry.
   - If true: a family-marker or phase-model diagnostic is justified. If false: residual chains are not separable before outcome and should not drive new guards.

2. Basic model/action hypothesis: the residual chains may share one underlying physics/action model error.
   - Explains: all chains eventually contain late contact misses, but the first-loss setup may reveal whether they share the same wrong target/action phase.
   - Evidence source: first active and first/last right offset buckets in `diagnostics/score18_chain_phase_induction_round65.json`.
   - Minimal diagnostic: compare first-loss route and paddle phase across the 8/3/2 chains.
   - Distinguishes a common model error from separate seed families by testing whether the same precontact route appears across groups.
   - If true: a unified controller change may be possible. If false: family-specific markers are more plausible than one target tweak.

3. Control-structure hypothesis: accepted local rules and rejected endpoint probes are symptoms of hidden family state, so a phase/family marker may be a cleaner controller abstraction.
   - Explains: endpoint counterfactuals repeatedly reject direct changes, while seed families are deterministic and route-specific.
   - Evidence source: Rounds 56-64 negative probes and this round's marker specificity checks.
   - Minimal diagnostic: test marker specificity across all 80 seeds without using final loss signatures.
   - Distinguishes a real family marker from overfitting to score-18 seeds by checking whether non-floor seeds share the same pre-outcome marker.
   - If true: next experiments can target marker-level counterfactuals. If false: avoid adding marker state.

### Diagnostics Run

- Added structural induction script:
  - `scripts/analyze_score18_chain_phase.py`
  - `.venv/bin/python -m py_compile scripts/analyze_score18_chain_phase.py`
- Score-18 phase induction:
  - `.venv/bin/python scripts/analyze_score18_chain_phase.py diagnostics/point_traces_80seed_v3_27_round61.jsonl --final-score 18 --output diagnostics/score18_chain_phase_induction_round65.json`
- Full 80-seed marker specificity:
  - `diagnostics/score18_chain_marker_specificity_round65.json`
- Full 80-seed pre-outcome marker specificity:
  - `diagnostics/score18_chain_preoutcome_marker_specificity_round65.json`

### Results

- 8-seed chain `3,4,6,18,24,40,42,49`:
  - First loss point: point `1`, before score `0-0`.
  - First-loss routes split into three opening paddle buckets, but the union of before-score + first-active + route matches exactly these 8 seeds in all 80.
  - The broader before-score + route marker alone matches 14 seeds, including 4 score-`19` seeds and the 2-seed chain, so first-active paddle state is required.
- 3-seed chain `33,44,66`:
  - First two points are wins, then first loss at before score `2-0`.
  - First active marker `x=125,y=125,pad=77,target=125,action=DOWN` plus route matches exactly these 3 seeds in all 80.
  - This chain is not an opening family; it is a post-two-wins phase family.
- 2-seed chain `2,15`:
  - First loss point: point `1`, before score `0-0`.
  - First active marker `x=125,y=131,pad=85,target=131,action=DOWN` plus route matches exactly seeds `2,15` in all 80.
  - Before-score + route alone also matches seeds `40,42`, so first-active paddle state separates it from the 8-seed chain.

### Supported And Rejected Hypotheses

- Supported: all three residual score-`18` chains have pre-outcome family markers that are specific across the 80-seed trace when first-active paddle state is included.
- Rejected: one common first-loss route/model error explains all residual chains; the first active ball coordinates, score phase, and routes differ by family.
- Supported structurally: a family-marker or phase-model diagnostic is now better supported than another endpoint action patch.
- Not yet supported: any concrete policy action based on these markers. Marker specificity is necessary but not causal.

### Policy Change

- No policy change accepted.
- `pong_policy.py` remains at v3.27.
- No new strategy code was added.

### Verification

- Syntax check passed for `scripts/analyze_score18_chain_phase.py`.
- Marker specificity was checked against all 80 traced seeds, not just known floor seeds.
- No formal `run_eval.py` was run because no policy change was made.

### Durable Assets

- Added:
  - `scripts/analyze_score18_chain_phase.py`
  - `diagnostics/score18_chain_phase_induction_round65.json`
  - `diagnostics/score18_chain_marker_specificity_round65.json`
  - `diagnostics/score18_chain_preoutcome_marker_specificity_round65.json`
- Updated:
  - this progress log.

### Withdrawn

- No accepted policy code was withdrawn because no policy code was changed this round.

### New Structural Issues

- The current residual floor can be classified before the first loss, but no causally useful marker-level intervention has been tested.
- A future controller abstraction should not merge these chains into one geometry rule; they are distinct family states.
- The next high-information experiment should test marker-level counterfactuals at the first active or early route-entry phase, not the already rejected y171/y203 endpoints.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.27 code/docs/logs plus Round 56-65 diagnostic additions.
- New Round 65 files are diagnostic assets only; no unrelated files were reverted.

### Next Automatic Exploration

- Choose one marker-level counterfactual probe before any policy change:
  - either the 2-seed opening marker `x=125,y=131,pad=85` because it is smallest and cleanest;
  - or the 3-seed post-two-wins marker `x=125,y=125,pad=77` because it tests whether family state after two wins can be disrupted early.
- Run it first on score-18 plus neighbor seeds only; expand to all 80 only if the target seeds improve without collateral damage.

## Round 66 - 2026-05-10

### Current Best Baseline

- Starting baseline: `ram_intercept_v3_27_high_down_score160_bounce_noop`.
- Baseline trial `48`: mean `18.975`, min `18`, max `20`.
- Accepted new baseline: `ram_intercept_v3_28_chain2_score00_initial_up`.
- Formal trial `49`: mean `19.025`, min `18`, max `20`, env steps `466333`.

### Hypothesis Map

1. Local failure-family hypothesis: the 2-seed residual chain can be disrupted at its pre-outcome opening marker.
   - Explains: seeds `2,15` were the only score-18 family whose first active marker was `0-0,x=125,y=131,pad=85`.
   - Evidence source: Round 65 marker specificity plus Round 64 y203 endpoint rejection.
   - Minimal diagnostic: test only the marker frame `DOWN -> NOOP/UP` on score-18 plus neighbor seeds, then expand to all 80 only if improved.
   - Distinguishes marker-level causality from endpoint causality by acting before the first y154/y203/y164 chain losses.
   - If true: seeds `2,15` improve without collateral damage. If false: marker specificity is descriptive only.

2. Basic model/action hypothesis: the baseline first active `DOWN` sends the paddle into the bad opening phase for this family.
   - Explains: prior y154 final `UP -> NOOP` improved this family but did not eliminate it; the remaining chain may be set earlier by the initial paddle movement.
   - Evidence source: `diagnostics/score18_chain_preoutcome_marker_specificity_round65.json` and new counterfactuals.
   - Minimal diagnostic: compare `DOWN -> NOOP` versus `DOWN -> UP` at the exact initial marker.
   - Distinguishes direction change from simple damping by testing both replacements.
   - If true: `UP` should improve while `NOOP` may be inert. If false: both are inert or harmful.

3. Control-structure hypothesis: family-marker corrections can be safer than endpoint patches when the marker is specific across all 80 seeds.
   - Explains: y171/y203 endpoint changes were harmful, but the first active marker is specific and upstream.
   - Evidence source: Rounds 63-65 and this round's 80-seed guard.
   - Minimal diagnostic: full 80-seed counterfactual and formal full 80-seed eval before accepting code.
   - Distinguishes safe family marker from overfit seed patch by requiring zero regressions across all 80.
   - If true: retain a one-shot guard. If false: withdraw the policy edit.

### Diagnostics Run

- Small marker counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --variants baseline,once_chain2_score00_initial_down_to_noop_h1,once_chain2_score00_initial_down_to_up_h1 --jsonl diagnostics/counterfactual_chain2_score00_initial_probe_round66.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_chain2_score00_initial_probe_round66.jsonl --output diagnostics/chain2_score00_initial_probe_deltas_round66.json`
- Full 80-seed counterfactual:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0..79 --variants baseline,once_chain2_score00_initial_down_to_noop_h1,once_chain2_score00_initial_down_to_up_h1 --jsonl diagnostics/counterfactual_chain2_score00_initial_80seed_round66.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_chain2_score00_initial_80seed_round66.jsonl --output diagnostics/chain2_score00_initial_80seed_deltas_round66.json`
- Policy/test changes:
  - Added `is_chain2_score00_initial_down_correction_state` and a one-shot `chain2_score00_initial_down_correction` policy guard.
  - Added tests for the narrow guard and policy action.
- Verification:
  - `.venv/bin/python -m py_compile pong_policy.py tests/test_state_policy.py scripts/probe_counterfactual_actions.py scripts/analyze_score18_chain_phase.py`
  - `.venv/bin/python -m unittest tests/test_state_policy.py`
  - `.venv/bin/python scripts/run_eval.py --episodes 80 --seed 0 --max-steps 30000 --policy-name ram_intercept_v3_28_chain2_score00_initial_up --notes 'Round 66 accepted marker-level chain2 score0-0 initial DOWN->UP guard after 80-seed counterfactual mean 19.025 min 18 max 20; expected seeds 2 and 15 improve 18->20 with no regressions.' --no-video`
- New v3.28 diagnostic trace:
  - `.venv/bin/python scripts/trace_points.py --seeds 0..79 --max-steps 30000 --window 220 --tail 0 --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl`
  - `.venv/bin/python scripts/summarize_loss_chains.py diagnostics/point_traces_80seed_v3_28_round66.jsonl --final-score 18 --output diagnostics/score18_loss_chains_v3_28_round66.json`
  - `.venv/bin/python scripts/summarize_floor_families.py diagnostics/point_traces_80seed_v3_28_round66.jsonl --output diagnostics/score18_floor_families_v3_28_round66.json`

### Results

- Small marker counterfactual:
  - Baseline subset: mean `18.20`, min `18`, max `20`.
  - `DOWN -> NOOP`: unchanged, 0 improved, 0 worsened.
  - `DOWN -> UP`: seeds `2,15` improve `18 -> 20`, no other seed changes; subset mean `18.47`, min `18`, max `20`.
- Full 80-seed counterfactual:
  - Baseline: mean `18.975`, min `18`, max `20`.
  - `DOWN -> NOOP`: unchanged.
  - `DOWN -> UP`: mean `19.025`, min `18`, max `20`; improved seeds `2,15`; no regressions.
- Formal v3.28 eval:
  - Trial `49`: mean `19.025`, min `18`, max `20`, env steps `466333`.
  - Scores match the counterfactual expectation: seeds `2` and `15` are now `20`; no hidden collapse.
- New v3.28 floor:
  - Score-18 seeds are now `3,4,6,18,24,33,40,42,44,49,66`.
  - The former 2-seed `y154/y203/y164` chain is removed from the score-18 floor.
  - Remaining floor chains:
    - 8 seeds `3,4,6,18,24,40,42,49`: `low_up y118 -> high_down y205 -> low_up y54`, already covered by negative local/route probes.
    - 3 seeds `33,44,66`: `low_up y111 -> high_down y171 -> mid_up y164`, with y171 still the only uncovered signature and prior y171 local-action probe rejected.

### Supported And Rejected Hypotheses

- Supported: the 2-seed opening marker is causal when acted on early with `UP`.
- Supported: marker-level intervention can work where endpoint y203 local actions were harmful.
- Rejected: `DOWN -> NOOP` is sufficient at the marker; it was inert.
- Supported: the exact guard is safe over seeds `0..79` in the current deterministic evaluation.
- Still unresolved: how to improve the remaining 8-seed and 3-seed floor chains without repeating rejected endpoint probes.

### Policy Change

- Accepted one minimal policy change:
  - one-shot `score=0-0`, `mode=initial`, `x=125`, `y=131`, `paddle=83..87`, `target=129..133`, `previous_paddle_delta=-23..-19`, baseline `DOWN -> UP`.
- `pong_policy.py` is now v3.28 behavior.
- `README.md`, `trials.jsonl`, `episodes.jsonl`, `summary.csv`, `report.html`, diagnostics, and tests were updated.

### Verification

- Unit tests: `63` passed.
- Formal 80-seed eval passed the acceptance gate: mean improved, min did not drop, max preserved, and no seed-level regression appeared.
- New v3.28 point trace reproduced the accepted trial distribution and refreshed the floor-family assets.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_chain2_score00_initial_probe_round66.jsonl`
  - `diagnostics/chain2_score00_initial_probe_deltas_round66.json`
  - `diagnostics/counterfactual_chain2_score00_initial_80seed_round66.jsonl`
  - `diagnostics/chain2_score00_initial_80seed_deltas_round66.json`
  - `diagnostics/point_traces_80seed_v3_28_round66.jsonl`
  - `diagnostics/score18_loss_chains_v3_28_round66.json`
  - `diagnostics/score18_floor_families_v3_28_round66.json`
- Updated:
  - `pong_policy.py`
  - `tests/test_state_policy.py`
  - `scripts/probe_counterfactual_actions.py`
  - `README.md`
  - `trials.jsonl`
  - `episodes.jsonl`
  - `summary.csv`
  - `report.html`
  - this progress log.

### Withdrawn

- No policy code was withdrawn.
- The diagnostic `DOWN -> NOOP` marker variant remains a negative result only.

### New Structural Issues

- The min floor remains `18` and is now held by only two chain families.
- The 8-seed family has three already rejected local/route nodes, suggesting it needs a new upstream marker or a broader controller abstraction.
- The 3-seed family still has a specific post-two-wins marker, but y171 endpoint replacements were rejected; a marker-level probe analogous to the accepted chain2 fix may be more informative than another y171 contact edit.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 code/docs/logs plus Round 56-66 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Diagnose the 3-seed post-two-wins marker `score=2-0,x=125,y=125,pad=77` for seeds `33,44,66`.
- First run marker-level counterfactuals at the initial frame, not y171/y164 endpoints.
- Expand only if the target seeds improve and the remaining score-18/neighbor seeds do not regress.

## Round 67 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- v3.28 score-18 floor seeds remain `3,4,6,18,24,33,40,42,44,49,66`.
- Remaining floor chains:
  - 8 seeds `3,4,6,18,24,40,42,49`: `low_up y118 -> high_down y205 -> low_up y54`.
  - 3 seeds `33,44,66`: `low_up y111 -> high_down y171 -> mid_up y164`.

### Hypothesis Map

1. Local failure-family hypothesis: the 3-seed `33/44/66` family is controlled by the post-two-wins first active marker.
   - Explains: why the family shares `score=2-0,x=125,y=125,pad=77,target=125,action=DOWN,mode=initial` before the `y111/y171/y164` loss chain.
   - Evidence: `diagnostics/score18_chain_phase_induction_round65.json` shows this marker for exactly the three seeds; y171 endpoint action probes in Round 63 did not improve the family.
   - Minimal validation: replace the marker `DOWN` with `NOOP` or `UP` before any endpoint edit.
   - Distinguishes from endpoint hypothesis: if marker action helps while y171 local edits fail, the causal point is upstream; if inert, marker specificity is observational.
   - Meaning if true: add a narrow one-shot score-2-0 initial guard only after broad validation. Meaning if false: avoid this marker as a policy rule and search downstream phase evolution.

2. Basic model/action hypothesis: short-horizon action replacement at an initial frame may be too weak to alter paddle phase.
   - Explains: a one-frame marker edit could be inert even if the point setup is phase-sensitive.
   - Evidence: prior accepted rules sometimes worked at bounce/setup anchors, while several final-frame replacements were inert; paddle movement is history-dependent.
   - Minimal validation: test the same marker with horizon `2` and `3` before treating the marker as non-causal.
   - Distinguishes from non-causal marker: if h2/h3 moves scores but h1 is inert, action duration matters; if all are inert, the marker action itself is not the lever.
   - Meaning if true: diagnose held-action duration safely. Meaning if false: do not add a marker action guard.

3. Control-structure hypothesis: the remaining floor needs a phase/controller abstraction rather than another local action rule.
   - Explains: the 8-seed chain has multiple rejected local/route nodes, and the 3-seed y171 endpoint and initial marker probes are now rejected or inert.
   - Evidence: Round 60/63/64 local probes rejected y205/y171/y203 actions; Round 67 tests whether an upstream marker behaves like the accepted v3.28 chain2 fix.
   - Minimal validation: if marker action is inert, compare trajectory deltas after the marker or build a phase evolution diagnostic instead of adding a new rule.
   - Distinguishes from local-rule hypothesis: local-rule hypothesis predicts seed-level score movement from one guarded action replacement; structure hypothesis predicts no score movement from local replacements but informative trajectory divergence markers.
   - Meaning if true: next asset should be a route/phase comparison after the marker. Meaning if false: a smaller guard may still be enough.

### Diagnostics Run

- Added diagnostic-only trigger and variants in `scripts/probe_counterfactual_actions.py`:
  - trigger: `score=2-0`, `mode=initial`, `x=125`, `y=125`, `pad=77`, `target=125`, baseline `DOWN`.
  - variants: `DOWN -> NOOP` and `DOWN -> UP` with horizons `1`, `2`, and `3`.
- Commands:
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --max-steps 30000 --variants baseline,once_chain3_score20_initial_down_to_noop_h1,once_chain3_score20_initial_down_to_up_h1 --jsonl diagnostics/counterfactual_chain3_score20_initial_probe_round67.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_chain3_score20_initial_probe_round67.jsonl --output diagnostics/chain3_score20_initial_probe_deltas_round67.json`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --max-steps 30000 --variants baseline,once_chain3_score20_initial_down_to_noop_h2,once_chain3_score20_initial_down_to_noop_h3,once_chain3_score20_initial_down_to_up_h2,once_chain3_score20_initial_down_to_up_h3 --jsonl diagnostics/counterfactual_chain3_score20_initial_horizon_probe_round67.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_chain3_score20_initial_horizon_probe_round67.jsonl --output diagnostics/chain3_score20_initial_horizon_probe_deltas_round67.json`
- Small 15-seed probe result:
  - Baseline subset: mean `18.47`, min `18`, max `20`.
  - h1 `DOWN -> NOOP`: mean `18.47`, min `18`, max `20`, 3 interventions, 0 seed deltas.
  - h1 `DOWN -> UP`: mean `18.47`, min `18`, max `20`, 3 interventions, 0 seed deltas.
  - h2/h3 `DOWN -> NOOP`: unchanged, 3 interventions each, 0 seed deltas.
  - h2/h3 `DOWN -> UP`: unchanged, 3 interventions each, 0 seed deltas.

### Supported And Rejected Hypotheses

- Supported: the `score=2-0,x=125,y=125,pad=77` marker is family-specific but not a causal short-horizon action replacement point.
- Rejected: a chain2-style initial-frame `DOWN -> UP` correction fixes the `33/44/66` family.
- Rejected: short hold duration was the missing factor for this marker; h2/h3 were also inert.
- Still unresolved: whether the causal difference appears later in the route after the marker, before y171, or requires a broader phase/controller representation.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- `scripts/probe_counterfactual_actions.py` was extended only for diagnostics.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py` passed.
- No formal `run_eval.py` acceptance run was needed because every tested counterfactual was inert on the discovery set.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_chain3_score20_initial_probe_round67.jsonl`
  - `diagnostics/chain3_score20_initial_probe_deltas_round67.json`
  - `diagnostics/counterfactual_chain3_score20_initial_horizon_probe_round67.jsonl`
  - `diagnostics/chain3_score20_initial_horizon_probe_deltas_round67.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No policy code was added, so nothing was withdrawn.
- The score-2-0 initial marker action variants are retained as negative diagnostic evidence only.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 67 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Build or run a trajectory-delta diagnostic for seeds `33,44,66` comparing baseline against the inert h3 marker variants to see whether trajectories diverge but reconverge, or never diverge meaningfully.
- If trajectories are identical/reconvergent, pivot to a later pre-y171 marker between the accepted y111 loss and the y171 miss; avoid endpoint y171 action replacements already rejected in Round 63.

## Round 68 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- No policy change from Round 67 was retained; the `33/44/66` score-2-0 initial marker action probes were all inert.

### Hypothesis Map

1. Local failure-family hypothesis: the score-2-0 marker override changes paddle phase but reconverges before the point-relevant route.
   - Explains: Round 67 variants hit the marker and changed actions but produced zero seed-level deltas.
   - Evidence: counterfactual logs only show score outcomes, not whether RAM states diverged.
   - Minimal validation: compare baseline and h3 counterfactual RAM trajectories from a few frames before the marker through the later route.
   - Distinguishes from no-physical-effect hypothesis: if paddle y never diverges, the action is swallowed; if it diverges and reconverges, the downstream controller absorbs it.
   - Meaning if true: do not use this marker as a policy lever; search after the reconvergence point.

2. Basic model/action hypothesis: the environment action does affect `ram[51]`, but the current controller re-centers into the same phase.
   - Explains: short action replacement can be real yet score-inert because center/intercept logic restores the old orbit.
   - Evidence: paddle dynamics diagnostics already show action effects are stateful; Round 67 did not inspect state trajectories.
   - Minimal validation: compute first physical divergence and first reconvergence for NOOP/UP h3 variants.
   - Distinguishes from wrong-RAM/action model: a real `self_paddle_y` divergence supports the action/RAM mapping; immediate reconvergence supports controller absorption.
   - Meaning if true: later phase markers are more valuable than stronger initial-frame action patches.

3. Control-structure hypothesis: the residual floor is a stable orbit of the controller, not an isolated bad action.
   - Explains: the 3-seed chain survives initial marker perturbations and endpoint y171 perturbations.
   - Evidence: Round 63 rejected y171 local actions; Round 67 rejected initial marker actions.
   - Minimal validation: refresh v3.28 chain phase assets and identify whether the next discriminating marker is later than the reconvergence window.
   - Distinguishes from local-rule hypothesis: if all local perturbations reconverge into the same orbit, a rule must target a later non-absorbed phase or a broader controller state.
   - Meaning if true: next work should analyze the second loss or route phase, not add a score-2-0 initial guard.

### Diagnostics Run

- Added `scripts/compare_counterfactual_trajectories.py`.
  - It finds the trigger step for counterfactual variants, reruns baseline and variants from aligned seeds, compares physical/control divergence, and records first reconvergence.
- Commands:
  - `.venv/bin/python -m py_compile scripts/compare_counterfactual_trajectories.py`
  - `.venv/bin/python scripts/compare_counterfactual_trajectories.py --seeds 33,44,66 --max-steps 30000 --pre-steps 4 --trace-len 140 --variants baseline,once_chain3_score20_initial_down_to_noop_h3,once_chain3_score20_initial_down_to_up_h3 --output diagnostics/chain3_score20_initial_trajectory_compare_round68.json`
  - `.venv/bin/python scripts/analyze_score18_chain_phase.py diagnostics/point_traces_80seed_v3_28_round66.jsonl --final-score 18 --output diagnostics/score18_chain_phase_induction_v3_28_round68.json`
- Trajectory comparison:
  - For seeds `33,44,66`, both h3 variants first change control at step `506`.
  - `DOWN -> NOOP h3`: first physical divergence at step `507` (`pad 92 -> 86`), physical reconvergence at step `526`, 19 divergent physical offsets.
  - `DOWN -> UP h3`: first physical divergence at step `507` (`pad 92 -> 80`), physical reconvergence at step `515`, 8 divergent physical offsets.
  - All three seeds have identical divergence/reconvergence structure and still score `18`.
- Refreshed v3.28 phase induction:
  - 3-seed chain first-loss marker remains `score=2-0,x=125,y=125,pad=77,target=125,action=DOWN`.
  - The first-loss last-right offsets show the later absorbed route: x155/y204 DOWN, x171/y175 UP, x187/y143 `low_up_y111_pre_up_correction`, final x203/y111 UP.

### Supported And Rejected Hypotheses

- Supported: the Round 67 marker actions do physically change `self_paddle_y`.
- Supported: the existing controller absorbs both NOOP and UP perturbations before the first loss route; trajectories reconverge by steps `515` or `526`.
- Rejected: the inert score result happened because the action override had no RAM effect.
- Supported: next diagnostics should start after the reconvergence window or at the second-loss setup, not at the initial marker.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.

### Verification

- `.venv/bin/python -m py_compile scripts/compare_counterfactual_trajectories.py` passed.
- No formal eval was needed because this round created diagnostics only.

### Durable Assets

- Added:
  - `scripts/compare_counterfactual_trajectories.py`
  - `diagnostics/chain3_score20_initial_trajectory_compare_round68.json`
  - `diagnostics/score18_chain_phase_induction_v3_28_round68.json`
- Updated:
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 67-68 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Build a second-loss marker diagnostic for the 3-seed chain: analyze the point after the first `y111` loss, before the `score=2-1` high-down `y171` miss.
- The first candidate should be a pre-y171 route/phase marker, not x155/x187/final y171 local actions already rejected in Round 63.

## Round 69 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- No policy change from Rounds 67-68 was retained.
- Relevant unresolved chain: seeds `33,44,66` score `18` via `y111 -> y171 -> y164`; y171 endpoint actions and the score-2-0 initial marker are now rejected or inert.

### Hypothesis Map

1. Local failure-family hypothesis: the `score=2-1` y171 miss is controlled by earlier low-serve setup, before x155/x187/final y171.
   - Explains: Round 63 endpoint probes failed, while Round 68 showed earlier marker perturbations reconverge before first loss; the second-loss point may have a later non-absorbed setup anchor.
   - Evidence: `diagnostics/high_down_y171_route_compare_round63.json` shows deterministic pre-y171 low-serve frames at x135, x167, and x183 for seeds `33,44,66`.
   - Minimal validation: one-frame action replacements at x135 UP, x167 NOOP, and x183 DOWN on the 15-seed discovery set.
   - Distinguishes from endpoint-only failure: if early setup changes improve while endpoint changes do not, the lever is upstream in the low-serve setup.
   - Meaning if true: expand to 80 seeds before adding a narrow guard. Meaning if false: avoid more one-frame y171 setup patches.

2. Basic model/action hypothesis: wrong-direction perturbations at the low-serve setup should reveal whether baseline actions are stabilizing or merely inert.
   - Explains: replacing UP with DOWN or DOWN with UP can test whether the low-serve controller is already near a fragile stable orbit.
   - Evidence: prior action probes often produced asymmetric outcomes: one direction inert, the other collapse.
   - Minimal validation: test both alternate actions at each setup anchor where possible.
   - Distinguishes from no-op absorption: if all changes are inert, setup is absorbed; if opposite signs regress, baseline setup is necessary but not sufficient for improvement.
   - Meaning if true: local action signs should not be changed casually; future work needs trajectory or controller phase analysis.

3. Control-structure hypothesis: the residual y171 route is a closed controller orbit that survives local perturbations but collapses under wrong phase changes.
   - Explains: y171 endpoint, initial marker, and now low-serve setup probes all lack positive deltas.
   - Evidence: Round 68 showed reconvergence after early perturbation; Round 69 tests whether a later setup action can escape the orbit.
   - Minimal validation: if x135/x167/x183 replacements are either inert or harmful, move to route-level comparison with nearby non-floor seeds or a state-machine diagnosis.
   - Distinguishes from single-rule hypothesis: repeated local failures indicate a missing representation of phase/family rather than a missing one-frame guard.
   - Meaning if true: next experiment should compare full y171 route geometry/phase against score-19/20 neighbors, not add local substitutions.

### Diagnostics Run

- Added diagnostic-only triggers and variants in `scripts/probe_counterfactual_actions.py`:
  - `score=2-1`, low-serve x135/y121/pad136/target50, baseline `UP -> NOOP/DOWN`.
  - `score=2-1`, low-serve x167/y89/pad48/target50, baseline `NOOP -> UP/DOWN`.
  - `score=2-1`, low-serve x183/y73/pad39/target50, baseline `DOWN -> NOOP/UP`.
- Commands:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --max-steps 30000 --variants baseline,once_high_down_score21_y171_setup_x135_up_to_noop_h1,once_high_down_score21_y171_setup_x135_up_to_down_h1,once_high_down_score21_y171_setup_x167_noop_to_up_h1,once_high_down_score21_y171_setup_x167_noop_to_down_h1,once_high_down_score21_y171_setup_x183_down_to_noop_h1,once_high_down_score21_y171_setup_x183_down_to_up_h1 --jsonl diagnostics/counterfactual_high_down_score21_y171_setup_probe_round69.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_high_down_score21_y171_setup_probe_round69.jsonl --output diagnostics/high_down_score21_y171_setup_probe_deltas_round69.json`
- Small 15-seed result:
  - Baseline subset: mean `18.47`, min `18`, max `20`.
  - x135 `UP -> NOOP`: unchanged, 3 interventions.
  - x135 `UP -> DOWN`: seeds `33,44,66` regress `18 -> 14`; subset mean `17.67`, min `14`.
  - x167 `NOOP -> UP`: seeds `33,44,66` regress `18 -> 17`; subset mean `18.27`, min `17`.
  - x167 `NOOP -> DOWN`: unchanged, 3 interventions.
  - x183 `DOWN -> NOOP`: unchanged, 3 interventions.
  - x183 `DOWN -> UP`: seeds `33,44,66` regress `18 -> 14`; subset mean `17.67`, min `14`.

### Supported And Rejected Hypotheses

- Rejected: a one-frame pre-y171 low-serve setup replacement improves the 3-seed floor.
- Supported: baseline x135/x167/x183 action signs are stabilizing enough that opposite-direction edits can make the same family collapse earlier or deeper.
- Supported: the y171 issue is not a simple local action-sign error at initial marker, low-serve setup, x155, x187, or final contact.
- Still unresolved: what structural feature distinguishes this floor chain from score-19/20 neighboring seeds.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- `scripts/probe_counterfactual_actions.py` was extended only for diagnostics.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py` passed.
- No formal `run_eval.py` acceptance run was needed because every setup variant was inert or regressive.

### Durable Assets

- Added:
  - `diagnostics/counterfactual_high_down_score21_y171_setup_probe_round69.jsonl`
  - `diagnostics/high_down_score21_y171_setup_probe_deltas_round69.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.
- The y171 setup action variants are retained as negative diagnostic evidence.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 67-69 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Stop adding one-frame y171 action substitutions for now.
- Compare full y171-route history against nearby score-19/20 seeds or build a phase/state-machine diagnostic that can identify why the same controller orbit reaches a stable score-18 chain.

## Round 70 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- Round 69 found no safe pre-y171 one-frame action replacement.

### Hypothesis Map

1. Local failure-family hypothesis: the y171 route may have same-route win neighbors that expose a small phase difference.
   - Explains: if the same active setup route sometimes wins, a route-offset comparison could identify a narrow causal feature without new policy probing.
   - Evidence: Round 63 route comparison found the y171 route; Round 69 showed local anchors are not enough.
   - Minimal validation: search v3.28 traces for high-down y160..185 events and compare exact y171 setup route outcomes.
   - Distinguishes from route-unique hypothesis: same-route wins would support small phase/offset comparison; pure-loss route suggests no direct win control group.
   - Meaning if true: compare win/loss offset rows. Meaning if false: move to coarser route/controller structure.

2. Basic model/trajectory hypothesis: y171 is not the same as the common opening y173 high-down loss.
   - Explains: many seeds have high-down y173 losses but still finish 19/20, while y171 appears inside a later deterministic chain.
   - Evidence: Round 62 showed opening y173 local action edits are unsafe; y171 occurs at score 2-1 after the accepted y111 point.
   - Minimal validation: bucket high-down y160..185 events by score_after and final paddle error.
   - Distinguishes from geometry-only hypothesis: if y173 and y171 have different score chain, error, and route, geometry alone is not enough.
   - Meaning if true: do not transfer y173 conclusions into y171 policy patches.

3. Control-structure hypothesis: the y171 floor route is a rare deterministic branch with no same-route positive examples.
   - Explains: repeated local counterfactuals fail because the branch is not represented by a single local action correction.
   - Evidence: x155/x187/final, initial marker, and low-serve setup probes are all rejected or inert.
   - Minimal validation: exact route overview should show whether the branch is pure `33/44/66`.
   - Distinguishes from local-rule hypothesis: a pure route with no wins pushes next work toward route-entry state-machine/family marker induction.
   - Meaning if true: next diagnostic should compare coarser-route siblings or controller phase, not exact-route local actions.

### Diagnostics Run

- Commands:
  - `jq -c 'select(.final_dy==8 and .final_ball_y>=160 and .final_ball_y<=185 and .final_ball_x>=180) | {...}' diagnostics/point_traces_80seed_v3_28_round66.jsonl | tee diagnostics/high_down_y160_185_events_v3_28_round69.jsonl`
  - `jq -s 'map({seed, point_index, score_after, final_ball_y, final_self_paddle_y, final_paddle_error}) | group_by(.score_after) | map({score_after: .[0].score_after, count: length, seeds: map(.seed), final_errors: (map(.final_paddle_error)|unique)})' diagnostics/high_down_y160_185_events_v3_28_round69.jsonl > diagnostics/high_down_y160_185_event_summary_v3_28_round69.json`
  - `.venv/bin/python scripts/compare_setup_routes.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --output diagnostics/high_down_y160_185_route_overview_v3_28_round69.json --offsets=-16,-12,-8,-4,0,4,8,12,16,20 --top 20`
  - `.venv/bin/python scripts/compare_setup_routes.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --route 'pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=120..139;right_pad=100..119' --output diagnostics/high_down_y171_setup_route_v3_28_round69.json --offsets=-16,-12,-8,-4,0,4,8,12,16,20,24,28,32,36,40,44,48,52 --top 12`
- Findings:
  - High-down y160..185 events split into:
    - 26 opening `score_after=0-1` y173 losses with final error `-10`.
    - 3 `score_after=2-2` y171 losses for seeds `33,44,66` with final error `-28`.
  - Exact y171 route `pre_y=120..139;entry_y=120..139;right_y=100..119;entry_pad=120..139;right_pad=100..119` appears only 3 times in v3.28 traces and all are losses from seeds `33,44,66`.
  - The route offset sequence is deterministic for all three seeds from inactive setup through low-serve, center, and later intercept frames.

### Supported And Rejected Hypotheses

- Rejected: exact y171 route has same-route win neighbors in the current 80-seed trace.
- Supported: y171 is structurally distinct from the common opening y173 loss despite similar high-down geometry.
- Supported: exact-route comparison cannot provide a win/loss local delta; next diagnostics must use coarser siblings or a phase/controller abstraction.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.

### Verification

- No code was changed in Round 70.
- Diagnostics were generated from existing v3.28 point traces.

### Durable Assets

- Added:
  - `diagnostics/high_down_y160_185_events_v3_28_round69.jsonl`
  - `diagnostics/high_down_y160_185_event_summary_v3_28_round69.json`
  - `diagnostics/high_down_y160_185_route_overview_v3_28_round69.json`
  - `diagnostics/high_down_y171_setup_route_v3_28_round69.json`
- Updated:
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 67-70 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Compare coarser sibling routes around y171, especially the large route with many wins and 8 high-down losses: `pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119`.
- Goal is to identify a controller-level phase feature shared by residual score-18 chains rather than another exact-route action patch.

## Round 71 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`, env steps `466333`.
- Current unresolved score-18 floor seeds remain:
  - 8-seed chain: `3,4,6,18,24,40,42,49` via `low_up y118 -> high_down y205 -> low_up y54`.
  - 3-seed chain: `33,44,66` via `low_up y111 -> high_down y171 -> mid_up y164`.

### Hypothesis Map

1. Local failure-family hypothesis: the coarse route found after Round 70 is actually the 8-seed score-1-1 high-down y205 family, not the 3-seed y171 family.
   - Explains: the route `pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119` contains exactly seeds `3,4,6,18,24,40,42,49` as losses and many win siblings.
   - Evidence: `diagnostics/coarse_route_0_120_100_highdown_siblings_round71.json` groups `loss/high_down/paddle_above_target` count `8` with those seeds and `win` count `286`.
   - Minimal validation: inspect route offsets and test the shared score-1-1 initial marker before the y205 point.
   - Distinguishes from y171 route hypothesis: the loss score and seed set differ from the pure y171 route in Round 70.
   - Meaning if true: target the 8-seed chain separately; do not merge it with the 3-seed y171 failure.

2. Basic model/action hypothesis: the score-1-1 initial marker at x126/y126/pad85/target126 is a real family phase entry, but changing the local action may be absorbed or harmful.
   - Explains: the accepted v3.28 chain2 rule succeeded at an initial marker, but prior y205 local endpoint actions were rejected in Round 60.
   - Evidence: Round 71 offset rows show every 8-seed loss passes through `score=1-1`, mode `initial`, `x=126,y=126,pad=85,target=126`, baseline `DOWN`, before the later y205 miss.
   - Minimal validation: one-shot `DOWN->NOOP/UP` with horizons `1..3` on the 15-seed discovery set.
   - Distinguishes from non-causal marker: a real physical change followed by no score change means absorption; no physical change would imply the trigger/action did not affect RAM.
   - Meaning if true: keep the marker as a diagnostic family identifier, but do not turn it into a policy guard without a safe downstream target.

3. Control-structure hypothesis: the 8-seed chain is governed by a controller phase that can reconverge after small perturbations and collapse under stronger ones.
   - Explains: NOOP/UP h1-h2 mostly leave score unchanged while UP h3 collapses the same family to score 10.
   - Evidence: `diagnostics/chain8_score11_initial_trajectory_compare_round71.json` shows NOOP h2 physically diverges but reconverges around steps `305..311`; UP h3 does not reconverge in the traced window.
   - Minimal validation: compare later route offsets after reconvergence or search for controller state/phase features around the x152..x186 high-down setup.
   - Distinguishes from single-action fix: if all short-horizon initial edits are inert or regressive, a local initial replacement is not the missing rule.
   - Meaning if true: next work should be phase/controller diagnostics, not another short-horizon initial patch.

### Diagnostics Run

- Coarse sibling route comparison:
  - `.venv/bin/python scripts/compare_route_outcomes.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --route 'pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119' --first-offsets=-16,-12,-8,-4,0,4,8,12,16,20,24,28,32,36,40,44,48,52 --last-offsets=-28,-24,-20,-16,-12,-8,-4,0,1,2,3,4 --top 12 --output diagnostics/coarse_route_0_120_100_highdown_siblings_round71.json`
- Added diagnostic-only trigger and variants in `scripts/probe_counterfactual_actions.py`:
  - Trigger: `score=1-1`, mode `initial`, `x=126,y=126,pad=85,target=126`, baseline `DOWN`.
  - Variants: `DOWN->NOOP` h1/h2/h3 and `DOWN->UP` h1/h2/h3.
- Commands:
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --max-steps 30000 --variants baseline,once_chain8_score11_initial_down_to_noop_h1,once_chain8_score11_initial_down_to_noop_h2,once_chain8_score11_initial_down_to_noop_h3,once_chain8_score11_initial_down_to_up_h1,once_chain8_score11_initial_down_to_up_h2,once_chain8_score11_initial_down_to_up_h3 --jsonl diagnostics/counterfactual_chain8_score11_initial_probe_round71.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_chain8_score11_initial_probe_round71.jsonl --output diagnostics/chain8_score11_initial_probe_deltas_round71.json`
  - `.venv/bin/python scripts/compare_counterfactual_trajectories.py --seeds 3,4,24 --variants baseline,once_chain8_score11_initial_down_to_noop_h2,once_chain8_score11_initial_down_to_up_h3 --max-steps 30000 --pre-steps 4 --trace-len 160 --output diagnostics/chain8_score11_initial_trajectory_compare_round71.json`

### Results

- Coarse route result:
  - `loss/high_down/paddle_above_target`: count `8`, seeds `3,4,6,18,24,40,42,49`.
  - `win`: count `286`, with many repeated sibling wins.
- 15-seed score-1-1 initial probe:
  - Baseline subset: mean `18.47`, min `18`, max `20`.
  - `DOWN->NOOP h1`: mean `17.93`, min `17`; all 8 target seeds regress `18 -> 17`.
  - `DOWN->NOOP h2/h3`: unchanged, mean `18.47`, min `18`.
  - `DOWN->UP h1/h2`: unchanged, mean `18.47`, min `18`.
  - `DOWN->UP h3`: mean `14.20`, min `10`; all 8 target seeds regress `18 -> 10`.
- Trajectory comparison:
  - NOOP h2 first physical divergence changes `self_paddle_y` from `74` to `69`, then reconverges at offsets `47` around steps `305..311`.
  - UP h3 changes `self_paddle_y` from `74` to `63` and does not physically reconverge in the 160-step trace; score falls to `10`.

### Supported And Rejected Hypotheses

- Supported: the coarse sibling route is an 8-seed y205 diagnostic family, not a y171 same-route control group.
- Supported: the score-1-1 initial marker is a real physical phase marker for those 8 seeds.
- Rejected: a short-horizon score-1-1 initial `DOWN` replacement is a safe policy correction.
- Supported: the controller can absorb moderate initial perturbations but collapses under stronger phase changes, so the causal lever is later or more structural.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- `scripts/probe_counterfactual_actions.py` was extended only for diagnostics.

### Verification

- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py` passed.
- No formal `run_eval.py` acceptance run was needed because every score-1-1 initial variant was inert or regressive.

### Durable Assets

- Added:
  - `diagnostics/coarse_route_0_120_100_highdown_siblings_round71.json`
  - `diagnostics/counterfactual_chain8_score11_initial_probe_round71.jsonl`
  - `diagnostics/chain8_score11_initial_probe_deltas_round71.json`
  - `diagnostics/chain8_score11_initial_trajectory_compare_round71.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.
- The score-1-1 initial action variants are retained as negative diagnostic evidence.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 71 diagnostic additions.
- Modified files include `README.md`, `episodes.jsonl`, `failures.jsonl`, `pong_policy.py`, `progress.md`, `report.html`, `scripts/probe_counterfactual_actions.py`, `summary.csv`, `tests/test_state_policy.py`, and `trials.jsonl`.
- New Round 71 diagnostic outputs are untracked under `diagnostics/`.
- No unrelated files were reverted.

### Next Automatic Exploration

- Stop testing score-1-1 initial action substitutions for the 8-seed chain.
- Compare the 8-seed y205 loss route against its win siblings at later offsets after the initial perturbation reconvergence window, especially around offsets `24..44`, where action/phase alternates between win-like and loss-like rows.
- Prefer diagnostics that distinguish target-model error, paddle dynamics, and controller phase before any new policy guard.

## Round 72 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- Round 71 rejected score-1-1 initial short-horizon action replacements for the 8-seed y205 family.

### Hypothesis Map

1. Local failure-family hypothesis: the 8-seed score-1-1 y205 route has same-seed win siblings at later score phases.
   - Explains: cross-seed win siblings may mix families, but same-seed later wins can expose the actual phase difference for seeds `3,4,6,18,24,40,42,49`.
   - Evidence: `diagnostics/route_score_phase_0_120_100_highdown_round72.json` groups score `1-1` as the only loss for those 8 seeds, while the same route appears as wins for the same seed set at later `3-3,5-3,...,19-3` phases.
   - Minimal validation: compare score-phase offset summaries and test the earliest clean phase split.
   - Distinguishes from route-unique hypothesis: the route is not pure-loss for the same seeds; only the early score phase loses.
   - Meaning if true: diagnose paddle/controller phase, not route geometry alone.

2. Basic model/action hypothesis: the earliest clean win/loss split at x106 is a correlated paddle phase, not a safe action replacement.
   - Explains: score `1-1` loss has `x106,y97,pad152,target151,NOOP`; later same-route wins have `x106,y97,pad170,target151,UP`.
   - Evidence: first-right offset `24` differs before the route realigns at offset `44`; changing x106 alone may be too late to recreate the winning paddle cycle.
   - Minimal validation: one-shot x106 `NOOP->UP` and `NOOP->DOWN` on the 15-seed discovery set.
   - Distinguishes from action-causal hypothesis: if x106 `UP` improves, a narrow action guard is plausible; if it regresses/inerts, x106 is a phase signature.
   - Meaning if false: do not add x106 action guards; inspect preceding oscillator.

3. Control-structure hypothesis: the controller has a score/phase-dependent paddle oscillator whose phase is already wrong before x106.
   - Explains: later same-route wins use a different `pad` cycle around offsets `24..40` and resolve the point at x190/y148, while score `1-1` proceeds into a second right approach ending at y205.
   - Evidence: Round 72 score-phase summary shows same-seed later wins with alternating `pad170 -> 138 -> 135 -> 170 -> 138`, while the loss uses `pad152 -> 166 -> 156 -> 139 -> 168`.
   - Minimal validation: derive a paddle-cycle/phase diagnostic before x106 and compare `1-1` loss to `3-3` wins.
   - Distinguishes from target-model-only error: target values are similar at x106, but paddle cycle and action differ before the final outcome.
   - Meaning if true: a state-machine or phase marker may be needed before any safe rule.

### Diagnostics Run

- Added reusable route score-phase diagnostic:
  - `scripts/analyze_route_score_phase.py`
- Commands:
  - `.venv/bin/python -m py_compile scripts/analyze_route_score_phase.py`
  - `.venv/bin/python scripts/analyze_route_score_phase.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --route 'pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119' --output diagnostics/route_score_phase_0_120_100_highdown_round72.json`
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --max-steps 30000 --variants baseline,once_chain8_score11_phase_x106_noop_to_up_h1,once_chain8_score11_phase_x106_noop_to_down_h1 --jsonl diagnostics/counterfactual_chain8_score11_phase_x106_probe_round72.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_chain8_score11_phase_x106_probe_round72.jsonl --output diagnostics/chain8_score11_phase_x106_probe_deltas_round72.json`

### Results

- Score-phase route summary:
  - `1-1:loss:loss/high_down/paddle_above_target`: count `8`, seeds `3,4,6,18,24,40,42,49`, final y `205`, final paddle error `-27`.
  - Same route later wins for the same seed family at `3-3,5-3,7-3,...,19-3`, with count `11` including the 8-seed family plus seeds `33,44,66`, final y `148`, final paddle error `-21`.
  - Earliest clean phase split: offset `24`, loss `x106/y97/pad152/target151/NOOP`; later wins `x106/y97/pad170/target151/UP`.
- 15-seed x106 probe:
  - Baseline subset: mean `18.47`, min `18`, max `20`.
  - `NOOP->UP h1`: mean `17.93`, min `17`; all 8 target seeds regress `18 -> 17`.
  - `NOOP->DOWN h1`: unchanged, mean `18.47`, min `18`.

### Supported And Rejected Hypotheses

- Supported: the y205 family has same-seed later win siblings, so the route is score/phase-dependent rather than pure-loss.
- Rejected: the x106 win-like `UP` action is a safe local repair.
- Supported: the x106 action/paddle difference is a phase signature; the causal phase is already set before x106.
- Still unresolved: what earlier paddle-cycle or controller state makes the score `1-1` route enter x106 at pad `152` while later wins enter at pad `170`.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- `scripts/probe_counterfactual_actions.py` and `scripts/analyze_route_score_phase.py` were extended only for diagnostics.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_route_score_phase.py` passed.
- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py` passed.
- No formal `run_eval.py` acceptance run was needed because x106 variants were inert or regressive.

### Durable Assets

- Added:
  - `scripts/analyze_route_score_phase.py`
  - `diagnostics/route_score_phase_0_120_100_highdown_round72.json`
  - `diagnostics/counterfactual_chain8_score11_phase_x106_probe_round72.jsonl`
  - `diagnostics/chain8_score11_phase_x106_probe_deltas_round72.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.
- The score-1-1 x106 action variants are retained as negative diagnostic evidence.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 71-72 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Stop testing x106 one-frame action substitutions.
- Build a paddle-cycle phase diagnostic for the same route, comparing score `1-1` losses to `3-3` same-seed wins from the inactive/initial frames through x106.
- Use that diagnostic to decide whether the missing structure is a phase model, a family marker, or a target smoothing/controller issue.

## Round 73 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- Round 72 rejected the x106 score-1-1 one-frame action split as a policy repair.

### Hypothesis Map

1. Local failure-family hypothesis: the score-1-1 y205 loss family enters the x106 route in the wrong paddle-cycle phase before x106, not at x106 itself.
   - Explains: x106 `NOOP->UP` regressed despite later wins showing UP at x106; the entering paddle cycle may already be different.
   - Evidence: `diagnostics/route_paddle_cycle_x106_highdown_round73.json` shows deterministic `1-1` loss cycle versus deterministic later `3-3/5-3` win cycle.
   - Minimal validation: compare offsets from inactive/initial through x106, then test the earliest upstream phase marker.
   - Distinguishes from x106-local action hypothesis: if the phase split begins before x106, x106 action is only a symptom.
   - Meaning if true: stop local x106 edits and inspect upstream phase/controller state.

2. Basic model/action hypothesis: the later winning route begins about 3-4 RAM pixels higher at inactive/initial frames; shifting one upstream inactive action might or might not recreate that phase.
   - Explains: `1-1` loss has initial `pad85`, while later wins have initial `pad89`; this small phase offset persists through x106 and flips the later paddle oscillator.
   - Evidence: `diagnostics/route_paddle_cycle_x106_inactive_highdown_round73.json` shows loss `pad85 -> 120 -> 89 -> 85` around inactive/initial, while later wins are `pad88/92/89` before the same x106 geometry.
   - Minimal validation: test the earliest clean inactive marker `score=1-1,x52,y0,pad85,target100,DOWN` with NOOP/UP horizons.
   - Distinguishes from pure observational phase marker: if a small inactive action change improves, it is causal; if inert/regressive, the phase is set before this window.
   - Meaning if false: do not add inactive action guards; move to preceding-point or structural phase analysis.

3. Control-structure hypothesis: the residual 8-seed family is a closed paddle oscillator where short perturbations are absorbed and stronger perturbations collapse.
   - Explains: Round 71 initial marker, Round 72 x106, and Round 73 inactive marker all show no improvements, with h1/h3 regressions and h2-style absorption.
   - Evidence: repeated counterfactuals on the same family: initial `DOWN` replacements, x106 `NOOP` replacements, and inactive pad85 `DOWN` replacements are inert or harmful.
   - Minimal validation: summarize accepted/rejected local rules for this family and identify a state-machine diagnostic or preceding-point cause.
   - Distinguishes from missing single guard: if multiple ordered anchors fail, the issue is not one local action sign.
   - Meaning if true: next useful asset should model controller phase or preceding point outcome, not add another one-frame substitution.

### Diagnostics Run

- Added reusable paddle-cycle route diagnostic:
  - `scripts/analyze_route_paddle_cycle.py`
- Commands:
  - `.venv/bin/python -m py_compile scripts/analyze_route_paddle_cycle.py`
  - `.venv/bin/python scripts/analyze_route_paddle_cycle.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --route 'pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119' --anchor-x 106 --anchor-y 97 --anchor-dx 4 --anchor-dy 8 --offsets=-32,-28,-24,-20,-16,-12,-8,-4,0,4,8,12,16,20,24,28 --sequence-offsets=-24,-20,-16,-12,-8,-4,0 --output diagnostics/route_paddle_cycle_x106_highdown_round73.json`
  - `.venv/bin/python scripts/analyze_route_paddle_cycle.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --route 'pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119' --anchor-x 106 --anchor-y 97 --anchor-dx 4 --anchor-dy 8 --offsets=-44,-40,-36,-32,-28,-24,-20,-16,-12,-8,-4,0 --sequence-offsets=-44,-40,-36,-32,-28,-24,-20,-16,-12,-8,-4,0 --output diagnostics/route_paddle_cycle_x106_inactive_highdown_round73.json`
  - `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py`
  - `.venv/bin/python scripts/probe_counterfactual_actions.py --seeds 0,1,2,3,4,6,15,18,24,33,40,42,44,49,66 --max-steps 30000 --variants baseline,once_chain8_score11_inactive_pad85_down_to_noop_h1,once_chain8_score11_inactive_pad85_down_to_noop_h2,once_chain8_score11_inactive_pad85_down_to_noop_h3,once_chain8_score11_inactive_pad85_down_to_up_h1,once_chain8_score11_inactive_pad85_down_to_up_h2,once_chain8_score11_inactive_pad85_down_to_up_h3 --jsonl diagnostics/counterfactual_chain8_score11_inactive_pad85_probe_round73.jsonl`
  - `.venv/bin/python scripts/summarize_counterfactual_deltas.py diagnostics/counterfactual_chain8_score11_inactive_pad85_probe_round73.jsonl --output diagnostics/chain8_score11_inactive_pad85_probe_deltas_round73.json`

### Results

- Paddle-cycle summary:
  - `1-1` loss cycle through x106: `pad85 -> 132 -> 97 -> 129 -> 132 -> 137 -> 152`, ending at x106 `NOOP`.
  - Later same-route win cycle: `pad89 -> 136 -> 101 -> 132 -> 136 -> 135 -> 170`, ending at x106 `UP`.
  - The split is already visible in inactive/initial frames: loss initial `pad85`, later win initial `pad89`.
- 15-seed inactive pad85 probe:
  - Baseline subset: mean `18.47`, min `18`, max `20`.
  - `DOWN->NOOP h1`: mean `17.93`, min `17`; all 8 target seeds regress `18 -> 17`.
  - `DOWN->NOOP h2/h3`: unchanged.
  - `DOWN->UP h1/h2`: unchanged.
  - `DOWN->UP h3`: mean `13.13`, min `8`; all 8 target seeds regress `18 -> 8`.

### Supported And Rejected Hypotheses

- Supported: x106 is not the first divergence; the bad phase is present by inactive/initial frames.
- Rejected: a one-frame or short-horizon inactive pad85 action replacement safely fixes the family.
- Supported: the 8-seed y205 family behaves like a closed controller oscillator: small perturbations reconverge, stronger ones collapse.
- Unresolved: whether the preceding point outcome sets this four-pixel phase offset, or whether a structural controller phase model can steer it without local action substitutions.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- `scripts/analyze_route_paddle_cycle.py` and new `scripts/probe_counterfactual_actions.py` variants are diagnostic-only.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_route_paddle_cycle.py` passed.
- `.venv/bin/python -m py_compile scripts/probe_counterfactual_actions.py` passed.
- No formal `run_eval.py` acceptance run was needed because every inactive pad85 variant was inert or regressive.

### Durable Assets

- Added:
  - `scripts/analyze_route_paddle_cycle.py`
  - `diagnostics/route_paddle_cycle_x106_highdown_round73.json`
  - `diagnostics/route_paddle_cycle_x106_inactive_highdown_round73.json`
  - `diagnostics/counterfactual_chain8_score11_inactive_pad85_probe_round73.jsonl`
  - `diagnostics/chain8_score11_inactive_pad85_probe_deltas_round73.json`
- Updated:
  - `scripts/probe_counterfactual_actions.py`
  - `failures.jsonl`
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.
- The inactive pad85 variants are retained as negative diagnostic evidence.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 71-73 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Stop local action substitutions on the 8-seed score-1-1 y205 chain unless a new causal discriminator appears.
- Diagnose whether the preceding point outcome/serve reset determines the four-pixel inactive/initial paddle offset.
- If preceding outcome is deterministic and not locally controllable, start an induction round for the accepted local rules and residual oscillator failures: determine whether a smaller phase model or state-machine abstraction can replace further one-off patches.

## Round 74 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- Rounds 71-73 rejected local action substitutions at score-1-1 initial, x106 phase split, and upstream inactive pad85 for the 8-seed y205 family.

### Hypothesis Map

1. Local failure-family hypothesis: the four-pixel score-1-1 initial paddle offset is set by the preceding point's terminal state.
   - Explains: all later same-route wins reset to initial `pad89`, while the score-1-1 loss resets to `pad85`.
   - Evidence: `diagnostics/route_preceding_points_0_120_100_highdown_round74.json` links route records to their previous points.
   - Minimal validation: compare previous final ball/paddle/target/error for `1-1` loss versus same-route win phases.
   - Distinguishes from current-point local action error: if the offset is already present at reset and tied to previous terminal state, current-point one-frame edits are late.
   - Meaning if true: search upstream at the previous point or build a phase model; avoid more local current-point substitutions.

2. Basic model/action hypothesis: the preceding point outcome class is not enough; the terminal geometry and reset phase matter.
   - Explains: every phase group has previous `win:win`, but only one terminal geometry yields current initial pad `85`.
   - Evidence: Round 74 shows `1-1` loss previous point has deterministic final `x191/y63/dx2/dy-4/pad60/target50/err+10`; `1-2` wins have final `y146/err-5`; `3-3` same-seed wins often have final `y144/err-11` or other non-y63 terminal rows.
   - Minimal validation: group reset initial pad by previous final terminal bucket across all route records.
   - Distinguishes from score-only gate: same `previous outcome=win` leads to both pad85 and pad89, so score/outcome alone is insufficient.
   - Meaning if true: any policy gate must include terminal phase or a generalized controller phase marker.

3. Control-structure hypothesis: the residual y205 chain is a reset-phase/oscillator problem whose local anchors are downstream signatures.
   - Explains: initial, inactive, and x106 interventions all fail because the reset phase is already wrong and the controller's oscillation either reconverges or collapses.
   - Evidence: Rounds 71-73 counterfactuals plus Round 74 preceding-point link.
   - Minimal validation: run an induction/structure diagnostic over accepted local rules and rejected oscillator families to see whether a phase model is the common missing abstraction.
   - Distinguishes from another one-off rule: a shared reset/phase explanation should cover multiple accepted score-gated rules and residual failures.
   - Meaning if true: prioritize state-machine or phase-controller diagnostics before any further policy guard.

### Diagnostics Run

- Added reusable preceding-point link diagnostic:
  - `scripts/analyze_route_preceding_points.py`
- Commands:
  - `.venv/bin/python -m py_compile scripts/analyze_route_preceding_points.py`
  - `.venv/bin/python scripts/analyze_route_preceding_points.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --route 'pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119' --output diagnostics/route_preceding_points_0_120_100_highdown_round74.json`
  - `jq '.phase_groups["1-1:loss:loss/high_down/paddle_above_target"] | {count,seeds,current_initial_paddle_y,previous_final_ball_y,previous_final_paddle_error, previous_patterns, examples}' diagnostics/route_preceding_points_0_120_100_highdown_round74.json`
  - `jq '.phase_groups["1-2:win:win"] | {count,seeds,current_initial_paddle_y,previous_final_ball_y,previous_final_paddle_error, previous_patterns, examples: .examples[0:3]}' diagnostics/route_preceding_points_0_120_100_highdown_round74.json`
  - `jq '.phase_groups["3-3:win:win"] | {count,seeds,current_initial_paddle_y,previous_final_ball_y,previous_final_paddle_error, previous_patterns, examples: .examples[0:3]}' diagnostics/route_preceding_points_0_120_100_highdown_round74.json`

### Results

- For `1-1:loss:loss/high_down/paddle_above_target`:
  - Count `8`, seeds `3,4,6,18,24,40,42,49`.
  - Current initial paddle y is deterministically `85`.
  - Previous point is always `win:win`, with final `x191/y63/dx2/dy-4/pad60/target50/err+10`.
- For route win phases:
  - Current initial paddle y is deterministically `89`.
  - `1-2` wins have previous final `x191/y146/dx2/dy-6/pad138/target143/err-5`.
  - `3-3` same-seed wins include previous final `x187/y144/dx4/dy8/pad143/target154/err-11` for the 8-seed family examples.
- Previous point outcome/pattern alone is not discriminative; previous terminal geometry/reset phase is.

### Supported And Rejected Hypotheses

- Supported: the score-1-1 bad phase is already present at reset and is linked to the preceding point's terminal geometry.
- Rejected: previous point `win` outcome alone explains the offset; all compared phases have previous `win`.
- Supported: current-point local action anchors are downstream signatures.
- Still unresolved: whether the previous terminal state can be safely steered, or whether the controller needs an explicit reset-phase model.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- Added diagnostics only.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_route_preceding_points.py` passed.
- No eval acceptance run was needed because no policy behavior changed.

### Durable Assets

- Added:
  - `scripts/analyze_route_preceding_points.py`
  - `diagnostics/route_preceding_points_0_120_100_highdown_round74.json`
- Updated:
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 71-74 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Do not test more local current-point actions for the 8-seed y205 family.
- Inspect the previous point terminal row `x191/y63/dy-4/pad60/target50/err+10`: determine whether it has same-terminal win/reset siblings or whether it is a unique reset-phase source.
- If unique and prior local edits have already failed, start a structural induction round over accepted score-gated rules plus residual phase failures to identify a more unified phase/controller abstraction.

## Round 75 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- Round 74 identified the preceding terminal row for the 8-seed y205 family: `x191/y63/dx2/dy-4/pad60/target50/err+10`.

### Hypothesis Map

1. Local failure-family hypothesis: the exact preceding terminal row is a unique reset-phase source for the 8-seed y205 route.
   - Explains: next point initial pad is always `85`, and next route is always the score-1-1 y205 loss route.
   - Evidence: `diagnostics/terminal_reset_phase_overview_v3_28_round75.json` selected the terminal key and found count `8`, seeds `3,4,6,18,24,40,42,49`, next initial pad `85`, next route `pre_y=0..19;entry_y=120..139;right_y=100..119;entry_pad=80..99;right_pad=100..119`, next outcome y205 loss.
   - Minimal validation: compare exact terminal key against global terminal-reset overview.
   - Distinguishes from sibling-terminal hypothesis: no same exact terminal key leads to pad89 or a win in the current v3.28 trace.
   - Meaning if true: the current-point local action search is exhausted; any next action probe must target the preceding point terminal source or use a phase classifier.

2. Basic model/action hypothesis: previous terminal geometry, not previous point outcome alone, determines reset paddle phase.
   - Explains: Round 74 showed all compared previous outcomes are `win`, but reset pad differs; Round 75 shows exact terminal keys map to deterministic next initial pads.
   - Evidence: selected key maps to next pad85; top terminal overview shows common win terminal `x187/y144/dy8/pad143/target154/err-11` maps to next pad89 in 272 records.
   - Minimal validation: build a read-only reset-phase classifier using previous terminal key and next initial pad.
   - Distinguishes from score-only or outcome-only gating: identical previous outcome class can map to different reset phases.
   - Meaning if true: policy needs a reset/phase feature before any safe behavior change.

3. Control-structure hypothesis: recent accepted rules and residual failures now justify a structural diagnostic pass.
   - Explains: accepted rules cluster around score-chain phase corrections and reset/initial corrections; residual failures reject local one-frame guards at multiple anchors.
   - Evidence: `diagnostics/rule_induction_v3_28_round75.json`.
   - Minimal validation: no policy refactor yet; first create/read a classifier that predicts floor families from phase/reset markers.
   - Distinguishes from patch accumulation: a classifier must predict failures before action edits.
   - Meaning if true: next work is diagnostic feature induction, not `pong_policy.py` edits.

### Diagnostics Run

- Added global terminal-to-reset diagnostic:
  - `scripts/analyze_terminal_reset_phase.py`
- Commands:
  - `.venv/bin/python -m py_compile scripts/analyze_terminal_reset_phase.py`
  - `.venv/bin/python scripts/analyze_terminal_reset_phase.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --output diagnostics/terminal_reset_phase_overview_v3_28_round75.json --terminal-key 'x=191;y=63;dx=2;dy=-4;pad=60;target=50.0;err=10.0;outcome=win;pattern=win' --top 20`
  - `.venv/bin/python scripts/analyze_terminal_reset_phase.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --output diagnostics/terminal_reset_phase_top_v3_28_round75.json --top 12`
  - `.venv/bin/python -m json.tool diagnostics/rule_induction_v3_28_round75.json >/dev/null`
- Added structural induction asset:
  - `diagnostics/rule_induction_v3_28_round75.json`

### Results

- Exact terminal key `x=191;y=63;dx=2;dy=-4;pad=60;target=50.0;err=10.0;outcome=win;pattern=win`:
  - Count `8`, seeds `3,4,6,18,24,40,42,49`.
  - Current score after previous point always `1-1`.
  - Next initial pad always `85`.
  - Next route always the y205 coarse route.
  - Next outcome always `loss/high_down/paddle_above_target`.
- Common terminal-reset examples:
  - `x=187;y=144;dx=4;dy=8;pad=143;target=154;err=-11;win` maps to next pad `89` in `272` linked records.
  - Exact y63 terminal has no same-terminal win/reset sibling in the current trace.
- Induction decision:
  - Recent accepted rules cluster into score-chain phase corrections and reset/initial corrections.
  - Residual y205 and y171 families have repeatedly rejected local action substitutions.
  - Next minimal experiment should be a read-only reset-phase classifier, not another local policy patch.

### Supported And Rejected Hypotheses

- Supported: the 8-seed y205 bad phase has a unique preceding terminal-reset source in v3.28 traces.
- Rejected: same exact terminal row has both safe and unsafe reset siblings.
- Supported: previous terminal geometry is a stronger predictor than previous outcome class.
- Supported: current evidence justifies a structural diagnostic pass before more policy edits.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- Added diagnostics only.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_terminal_reset_phase.py` passed.
- `.venv/bin/python -m json.tool diagnostics/rule_induction_v3_28_round75.json >/dev/null` passed.
- No eval acceptance run was needed because no policy behavior changed.

### Durable Assets

- Added:
  - `scripts/analyze_terminal_reset_phase.py`
  - `diagnostics/terminal_reset_phase_overview_v3_28_round75.json`
  - `diagnostics/terminal_reset_phase_top_v3_28_round75.json`
  - `diagnostics/rule_induction_v3_28_round75.json`
- Updated:
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 71-75 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Build a read-only reset-phase classifier that maps previous terminal geometry and next initial paddle phase.
- Evaluate whether the classifier predicts the 8-seed y205 chain and helps label the 3-seed y171 chain in v3.28 traces.
- Only after classifier evidence should a policy change be considered.

## Round 76 - 2026-05-10

### Current Best Baseline

- Best retained policy remains `ram_intercept_v3_28_chain2_score00_initial_up`.
- Trial `49` over seeds `0..79`: mean `19.025`, min `18`, max `20`.
- Round 75 structural induction recommended a read-only reset-phase classifier before any further behavior change.

### Hypothesis Map

1. Local failure-family hypothesis: reset-phase classifier keys predict the residual y205 and y171 floor chains before the current loss point resolves.
   - Explains: the residual chains are deterministic once a previous terminal geometry and next initial pad are observed.
   - Evidence: `diagnostics/reset_phase_classifier_v3_28_round75.json` has exact groups for y205 and y171.
   - Minimal validation: group all point records by previous terminal key plus current initial state and inspect loss/floor counts.
   - Distinguishes from route-only diagnosis: classifier key includes previous terminal/reset phase, not only current route geometry.
   - Meaning if true: reset-phase is a useful diagnostic feature; policy edits still require counterfactual evidence.

2. Basic model/action hypothesis: a previous-terminal/reset feature explains more than score and instantaneous ball geometry.
   - Explains: y205 key maps count `8` to y205 loss; y171 key maps count `3` to y171 loss; later chain nodes are also visible as classifier groups.
   - Evidence: y205 key includes previous terminal `x191/y63/dy-4/pad60/target50/err+10` and current initial `pad85`; y171 key includes previous low-up y111 loss terminal and current initial `pad113`.
   - Minimal validation: compare classifier groups by loss count and floor18 count.
   - Distinguishes from outcome-only gating: previous terminal geometry and initial pad are required.
   - Meaning if true: add no action yet; use classifier to choose a causal upstream counterfactual.

3. Control-structure hypothesis: residual score-18 chains can be represented as reset-phase state transitions.
   - Explains: the 8-seed y205 chain appears as y205 loss, then y54 loss groups; the 3-seed y171 chain appears as y111 -> y171 -> y164 groups.
   - Evidence: `ranked_by_loss` in classifier output lists the residual chain nodes with exact seed sets.
   - Minimal validation: inspect whether the upstream terminal action at the first reset-phase source can safely change the next reset phase.
   - Distinguishes from direct policy patching: classifier is read-only and only proposes where causality might be tested.
   - Meaning if true: next probe, if any, should target the preceding terminal source, not current y205/y171 anchors.

### Diagnostics Run

- Added read-only reset-phase classifier:
  - `scripts/analyze_reset_phase_classifier.py`
- Commands:
  - `.venv/bin/python -m py_compile scripts/analyze_reset_phase_classifier.py`
  - `.venv/bin/python scripts/analyze_reset_phase_classifier.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --output diagnostics/reset_phase_classifier_v3_28_round75.json --top 20`
  - `.venv/bin/python scripts/analyze_reset_phase_classifier.py --jsonl diagnostics/point_traces_80seed_v3_28_round66.jsonl --output diagnostics/reset_phase_classifier_coarse_v3_28_round75.json --coarse-terminal --top 20`
  - `jq '.y171_groups[0] | {classifier_key,count,seeds,current_labels,current_routes,current_scores_before,seed_final_scores,current_initial_paddle_y,examples}' diagnostics/reset_phase_classifier_v3_28_round75.json`
  - `jq '.y205_groups[0] | {classifier_key,count,seeds,current_labels,current_routes,current_scores_before,seed_final_scores,current_initial_paddle_y,examples}' diagnostics/reset_phase_classifier_v3_28_round75.json`
  - `jq '.ranked_by_loss[0:10] | map({key:.classifier_key,count,loss_count,floor18_count,seeds,current_labels,current_initial_paddle_y})' diagnostics/reset_phase_classifier_v3_28_round75.json`

### Results

- Classifier groups:
  - Exact group count: `39`.
  - Coarse group count: `39`.
- y205 group:
  - Key: previous terminal `x=191;y=63;dx=2;dy=-4;pad=60;target=50;err=10;win` plus current initial `x126/y126/pad85`.
  - Count `8`, seeds `3,4,6,18,24,40,42,49`.
  - Current label: `loss/high_down/paddle_above_target;final_y=205;score_after=1-2`.
  - Seed final score: all `18`.
- y171 group:
  - Key: previous terminal `x=203;y=111;dx=4;dy=-8;pad=135;target=111;err=24;loss/low_up/paddle_below_target` plus current initial `x131/y125/pad113`.
  - Count `3`, seeds `33,44,66`.
  - Current label: `loss/high_down/paddle_above_target;final_y=171;score_after=2-2`.
  - Seed final score: all `18`.
- Ranked loss groups also recover downstream chain nodes:
  - y205 -> y54 for seeds `3,4,6,18,24,40,42,49`.
  - y111 -> y171 -> y164 for seeds `33,44,66`.

### Supported And Rejected Hypotheses

- Supported: reset-phase classifier keys predict residual y205 and y171 loss nodes with exact seed families in current traces.
- Supported: terminal/reset phase is a reusable diagnostic abstraction across residual chains.
- Rejected: current route geometry alone is the highest-information next diagnostic; reset phase adds earlier discrimination.
- Still unresolved: whether any upstream terminal action can safely alter reset phase without harming score or downstream chains.

### Policy Change

- No retained policy change was made.
- `pong_policy.py` remains v3.28 behavior.
- Added diagnostics only.

### Verification

- `.venv/bin/python -m py_compile scripts/analyze_reset_phase_classifier.py` passed.
- No eval acceptance run was needed because no policy behavior changed.

### Durable Assets

- Added:
  - `scripts/analyze_reset_phase_classifier.py`
  - `diagnostics/reset_phase_classifier_v3_28_round75.json`
  - `diagnostics/reset_phase_classifier_coarse_v3_28_round75.json`
- Updated:
  - this progress log.

### Withdrawn

- No policy code was added or withdrawn.

### Git Status Summary

- Working tree remains dirty with retained v3.20-v3.28 assets plus Round 71-76 diagnostic additions.
- No unrelated files were reverted.

### Next Automatic Exploration

- Inspect the upstream terminal source action/window for the y205 key `x191/y63/dy-4/pad60/target50/err+10`.
- Only if there is a narrow causal counterfactual that changes next reset phase without losing the preceding point should a policy edit be considered.
- Otherwise keep reset-phase classifier as a diagnostic feature and move to higher-level controller design.
