Skip to content

Deepen the sota-v3 simulator with ZenGM-derived mechanics (contracts, pick slots, in-season dynamics) #89

Description

@nedcut

Follow-on to #84. GM-Bench's decision loop was modelled on ZenGM Hockey; this issue closes the gaps that a read of the ZenGM engine (GameSim.hockey, developSeason.hockey.ts, ValueChangeCalculator, player/value.ts) makes visible in ours.

Why now, and only now

Every item below moves contract_fingerprint. Today that costs nothing: sota-v3 is open, no paid panel has been bought, and the seven checkpoints under data/model_checkpoints/ are already invalidated by #88. The moment a paid sota-v3 panel runs, all of this becomes unaffordable — a docstring edit to a _CONTRACT_SOURCES file is enough to invalidate the rows. This is the window.

Pre-change reference (fingerprint 8a4eb422d548317a, seeds 11-18 x 5 seasons):

agent mean_score
pick-trader 411.62
strategic 402.02
scaffold-view 401.95
shrewd 371.77
value 354.62
win-now 275.83
conservative 139.03
rebuild 138.75
exploit 137.13
random 96.72

The diagnosis

The benchmark contests exactly one valuation: hidden true_potential. Difficulty in a management benchmark comes from assets whose value is disputed between the agent's model and the environment's model, and we currently have a single such asset. Everything else is priced identically for both sides, which is a large part of why a 120-line scripted pick-trader beats every frontier model we ran.

Each item below converts an unpriced asset into a contested one.

P1 - Contract economics (revives #24, closed with #62 unmerged)

#62 was closed 2026-07-16 purely to protect the sota-v2 publication lane, not on quality. Its concepts should be reimplemented on current main.

  • Dead cap on release. simulator.py:_release currently sets salary = 0.0 with no charge. Any bad contract is erasable at will above the 18-player floor, so the cap_room score term measures no skill. ZenGM keeps released salary on the books.
  • Incumbent extension rights. Expiring players drop straight into the shared pool with no advantage to the team that developed them. In ZenGM, re-sign-vs-let-walk is the central GM decision, and it is the one decision GM-Bench never asks.
  • Market inflation + term premium so the 1-5 years dial has economic content.

#62 found a real exploit here — sign 1 year, immediately extend 5 at the loyalty discount — and tuned the discount 8% -> 3% to keep INC5 above FA1. Both must be preserved.

P2 - Standings-linked pick values

pick_value is 12.0 * 0.8^n regardless of whose pick it is, so a contender's pick and a cellar-dweller's are interchangeable to both the trade market and score_components.future_pick_assets. ZenGM's ValueChangeCalculator estimates the actual slot from projected records and regresses toward the mean with distance. This is the most likely single explanation for pick-trader's dominance.

P3 - In-season dynamics

simulate_season computes ratings once (simulator.py:330) and draws all 33 games from frozen numbers, so deadline moves cannot affect the standings they were made for. Recompute at the midseason break so the trade deadline is causally connected to the second leg.

P4 - morale is dead

Generated, updated by _update_morale_from_standings, published in Player.public_dict — and read by nothing. It is a pure token tax on agents inside a 4,096-token lane. Either wire it into free-agent/extension willingness (ZenGM's mood system: loyalty / money / playing time / winning) or drop it from the observation.

Constraints

  • Validity canaries in validate-contract encode baseline orderings (strategic > shrewd > value > exploit). If a change inverts one, that is a finding to reason about, not a threshold to loosen.
  • The exploit canary must not gain ground; a dead-cap rule that made hoarding cheap would be a regression.
  • Frozen sota-v2 evidence under results/, releases/, docs/blog/, web/src/data/ stays untouched. SOTA_V2_CONTRACT (558e8f35ea1d66b9) must not move.
  • docs/scoring_calibration.md and golden scores get updated with each change, not after all of them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions