timmy: TM formidable-opponent build — table directive, plans/agendas, geometric scoring, starve-guard

This commit is contained in:
Timmy committed 2026-10-30 18:04:19 +00:00
1 parent 285945621a
commit bd160bb42e
1 file changed
+99
@@ -0,0 +1,99 @@
# Timmy — TM "formidable opponent" build, 2026-10-30
*Authored as Timmy (operating partner). Review request for the troupe.*
## The problem this build answers
Timmy's Terraforming Mars seat is **0-10** against Alexander, losing by 26–70 points every
game. The last loss (G10: 30 vs 165) exposed the root failure precisely: **TR froze at 17 for
six straight generations** while the seat banked 37 steel + 18 heat + 5 titanium + 4 plants +
25 MC and played 17 cards to Alexander's 77. The deterministic scorer graded every option *in
isolation* — it had no idea it was losing the TR race, what was rotting in the bank, or what
lane it had committed to at the opening. The fixes below wire the *whole table* into the
decision layer, and make the seat decide from a **multi-step agenda** instead of cold-scoring
every turn.
## What changed (5 items, all in `/root/tm-ai/tm-ai-server/src/tm_llm/`)
1. **Table directive — the fundamental fix.** `strategy.py:69 _table_directive(state)` reads my
TR vs the opponents' leader, banked resources, and committed strategy. When **behind on TR
(>3 below the leader)**, terraforming cards get `+22`, resource conversion and terraforming
standard projects get `+12`, forcing the TR hunt above normal card play. This is the fix for
"TR froze at 17" — the scorer now *knows* it's losing the clock race and acts on it.
2. **Plans/agendas, not cold decisions.** `strategy.py:93 derive_plan(state)` returns an ordered
agenda from the committed strategy (space → titanium_production → tag_engine → discounts →
points; ground → plant_production → convert → points; etc.), overridden by phase (late →
points) and table position (behind → terraform/convert). `strategy.py:130 _option_directive`
maps each option to one directive; the **top 2 agenda directives get `+8`** in
`choose_action` (`strategy.py:167`) and `_score` (`strategy.py:238`). Alexander's exact
words, implemented: *"you don't make decisions every single turn — you create paths and
plans … your top 2/3 actions come from your agendas."*
3. **Geometric/exponential scoring.** `card_ev.py:310 compound` adds a bonus for cards that
COMPOUND — discount, card draw, tag-scaling production, broad production — weighted by timing
(`×1.0` early, `×0.25` late). A discount engine is worth more early because it makes every
future card cheaper. Probe: `Earth Catapult` grades **S 9.5 early → A 6.7 late**.
4. **Committed strategy reaches the scorer.** `card_ev.py:246 detect_strategy` (from
corporations, stable all game) + `card_ev.py:270 strategy_fit` (weights cards to the
archetype). Under a Saturn Systems start, jovian cards grade S while an off-strategy building
card stays C. The strategy is also printed as `COMMITTED STRATEGY:` in the action prompt
(`prompts.py`).
5. **Starve-the-generation guard.** `engine.py:29 _LAST_PICK` + `engine.py:32 _waiting_key`
track the last returned deterministic pick; when the TM server rejects it and re-offers the
identical decision, `choose_action` gets it as `blocked_idx` and cycles to the next-best
option instead of looping on the same card (G10 live: "Spin-off Department" re-picked 7×).
Plus an affordability guard on card-play (budget = MC + steel×2 building + titanium×3 space).
## Verification (run from this host, all green)
```bash
cd /root/tm-ai/tm-ai-server && .venv/bin/python -c "
import sys; sys.path.insert(0,'src')
from tm_llm import strategy, card_ev, engine
# behind on TR forces a terraforming card
s={'game':{'temperature':2,'oxygen':5,'oceanCount':4,'generation':8},
'player':{'terraformRating':17,'megacredits':50,'steel':0,'titanium':0,'plants':0,'heat':0,'corporations':['Helion']},
'opponents':[{'name':'Alexander','terraformRating':59}]}
opts=[{'title':'Play project card: Acquired Company','index':0},
{'title':'Play project card: Deimos Down','index':1}]
assert strategy.choose_action(s,{'type':'or'},opts)[0]==1 # Deimos Down (terraform) forced
# space plan: titanium production on agenda
sp={'game':{'temperature':-10,'oxygen':2,'oceanCount':1,'generation':2},
'player':{'terraformRating':20,'megacredits':50,'steel':0,'titanium':0,'plants':0,'heat':0,'corporations':['Phobolog']},
'opponents':[{'name':'A','terraformRating':21}]}
o2=[{'title':'Play project card: Titanium Mine','index':0},{'title':'Play project card: Acquired Company','index':1}]
assert strategy.choose_action(sp,{'type':'or'},o2)[0]==0 # on-plan titanium
assert strategy.derive_plan(sp)[0]=='titanium_production'
# geometric: discount engine decays
assert card_ev.card_ev('Earth Catapult',{'game':{'temperature':-30,'oxygen':0,'oceanCount':0,'generation':1},'player':{'corporations':['CrediCor'],'production':{},'tags':{}}})[0] > \
card_ev.card_ev('Earth Catapult',{'game':{'temperature':6,'oxygen':11,'oceanCount':7,'generation':12},'player':{'corporations':['CrediCor'],'production':{},'tags':{}}})[0]
# starve guard present
assert hasattr(engine,'_waiting_key') and hasattr(engine,'_LAST_PICK')
print('ALL GREEN')
"
```
Service restarted and active: `systemctl is-active tm-ai-server` → `active`.
## Ask (review against these, per wizard)
- **Argus (evidence law):** each of the 5 items is claimed at a `file:line` above — confirm the
code actually lives there and does what the check-in says, and that the verification command
reproduces `ALL GREEN` from this host.
- **Beam / Merlin (deep reasoning):** hunt for a regression. Specifically: does the `+22`/`+12`
TR-hunt boost, combined with the `+8` plan boost, ever out-score a milestone (70) or an S-tier
engine card in a way that's *wrong*? Is `compound` double-counting `discount_value`/`draw_value`
(both still in the score line)? Does `_option_directive` misclassify any card type?
- **Volt (workhorse):** the residual open bug — the G10 "Spin-off Department" card was affordable
and shape-legal yet never resolved (server-side `getCardCost` may exceed the model's
`calculatedCost` in `checkPaymentAndPlayCard`). Confirm or falsify that hypothesis; the guard
(item 5) now contains the loop but doesn't fix why that card never played.
## Next
Playtest against Alexander; if the TR race is no longer lost by 40+ points, the directive/plan
layer is doing its job. Record the next loss as G11 in `strategy-lessons.md` and keep the lesson
loop wired to the scorer.