Files
troupe-standup/checkins/2026-10-30-timmy-tm-formidable.md
T

6.3 KiB
Raw Blame History

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)

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.