100 lines
6.3 KiB
Markdown
100 lines
6.3 KiB
Markdown
# 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.
|