STATE: Session 75 — weather modulation composed onto park; venue gap still open

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCNgGSt5qvcLxaeQqa7Zpj
This commit is contained in:
Kev
2026-07-21 01:43:41 -04:00
parent 9f60ceba10
commit 9086951852
+65 -1
View File
@@ -1,5 +1,5 @@
# VYNDR — STATE OF THE WORLD
### As of `f33091d` (main, DEPLOYED + fingerprinted live), 2026-07-21. This file opens every future session. **Start with the CURRENT STATUS + OPEN ITEMS block below.**
### As of `9f60ceb` (main, DEPLOYED), 2026-07-21. This file opens every future session. **Start with the CURRENT STATUS + OPEN ITEMS block below.**
---
@@ -131,6 +131,70 @@ exist locally; harmless, the data restores completely.
`specs/snapshot-retention.md` (Phase 2 report + schema) ·
`specs/audit-data/grade-collapse.md` · `specs/audit-data/gate-simulation.md`.
---
# 🟡 SESSION 75 — WEATHER MODULATION (2026-07-21)
**Built + tested + deployed. NOT reaching live props** (same venue gap as park).
`effective_environment = park_base × weather_mod`.
## Phase 0 — both feeds free + keyless
statsapi `/venues?hydrate=location` → lat/lon for every park, ONE call.
Open-Meteo hourly (temp / wind speed / wind direction), **available hours before
first pitch** = when we project. Verified live.
## 🔑 THE SPINE — two weather values, NEVER crossed
| Value | Purpose | Lands in |
|---|---|---|
| **FORECAST** at projection time | **drives** the live adjustment **AND is what the instrument measures** | `ledger_entries.wx_forecast` + `env_*` |
| **ACTUAL** at game time | future SELF-DERIVED weather factors ONLY | `game_context.wx_*` |
Using the actual to measure tonight = **scoring ourselves on information we
didn't have**. The actual comes from Open-Meteo's **ARCHIVE** endpoint — asking
the *forecast* endpoint after the fact returns a re-forecast, not what happened.
## 🔴 WIND SIGN — the easiest error in the file
`wind_direction` is METEOROLOGICAL (where it comes FROM). Blowing OUT to centre
= arriving FROM the opposite bearing → **`(from + 180) % 360`** before comparing
to the CF bearing. **Getting this backwards inverts every wind adjustment.**
Pinned by a test on straight-out / straight-in / crosswind.
`CF_BEARING` (30 parks) is **public geometry**, same class as the dome list. A
park missing from it gets **no wind effect** (keeps temperature).
## Three honest do-nothing states — all 1.0, distinct
- **`dome_na`** — weather doesn't apply; **the PARK factor STILL does** (verified:
domed venue keeps its sub-1.0 base while weather stands down).
- **`forecast_absent`** — none available for this park/time.
- **`sub_threshold`** — real forecast below a meaningful bar (a 0.3% nudge on a
light breeze is false precision).
Plus `not_applicable` for stats weather says nothing about (strikeouts).
## Conservative + ledger-tunable
Every magnitude is an env var (`WX_WIND_PER_MPH` .006 · `WX_TEMP_PER_DEG` .003 ·
thresholds 5 mph / 8°F), total **capped ±12%**. Nothing asserted — a nominated
challenger that earns its place or is cut.
## Induced (Wrigley, CF bearing 32°)
`wind FROM 212° @15mph → 15mph straight OUT · wx 1.12 × park 1.06 = 1.187 ·
δ +0.043` · under mirrors · **pitcher HR-allowed moves WITH hitter HR** (both are
P(over) on a ball leaving the park) · wind IN → 0.955 · calm/dome/no-forecast all
→ 1.0 by three different routes, park base still applying.
## ⚠️ ORDER CORRECTION (recorded)
The order described wind-out as helping the hitter and **hurting** the pitcher's
HR-allowed as "opposite sides." In PROP terms both go the **same** way — the
HR-allowed OVER is also more likely. **The sign lives in the STAT**, as already
established for park. Implementation follows that, not the phrasing.
## 🔴 STILL OPEN (unchanged)
1. **Neither park nor weather reaches live props**`snapshotService` still
doesn't attach venue+first-pitch per prop and pass `environment` into
`attachChallenger`. Everything else is ready; this one link is missing.
2. **No settled row carries `p_win`** — all six adjusters remain unmeasurable.
```sql
select count(p_win) champion, count(p_win_challenger) challenger
from ledger_entries where user_id is null;
```
---
# 🟢 SESSION 74 — PARK BASE (public, pluggable) + GAME-LEVEL CAPTURE (2026-07-21)