#!/usr/bin/env python3 """Match open → seed → seep → T1 → HQ kill — startable foundation (GAMEPLAY.md v0).""" from __future__ import annotations import sys from pathlib import Path ROOT = Path(__file__).resolve().parents[2] if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from game.sim.catalog import FACTION_HOST, FACTION_STAFF, HZ, START_PITCH from game.sim.match import MatchPhase, open_match, seed_v0_path, train_t1_melee def main() -> int: m = open_match() staff = m.side(FACTION_STAFF) host = m.side(FACTION_HOST) if staff.pitch != START_PITCH or host.pitch != START_PITCH: print(f"FAIL: start pitch {staff.pitch}/{host.pitch}") return 1 if len(staff.units) != 4 or len(host.units) != 4: print("FAIL: want 4 workers per side") return 1 if not staff.hq_alive or not host.hq_alive: print("FAIL: both HQs must be alive at open") return 1 notes = seed_v0_path(m, FACTION_STAFF) if any(not n.endswith(":ok") for n in notes): print(f"FAIL: seed {notes}") return 1 # Serial queue: Font 15 + Cot 12 + Yard 20 = 47s; start 500 - 380 = 120 left m.tick_hz(int(47 * HZ) + 2) if not staff.has_building("pitch_font"): print("FAIL: pitch_font missing after seed") return 1 if 0 not in staff.claimed_wells or not m.wells[0].claimed: print("FAIL: well not claimed by Staff Font") return 1 if not staff.has_producer(): print("FAIL: coven_yard missing") return 1 if staff.pop_cap != 18: print(f"FAIL: pop_cap want 18 (start 10 + cot 8) got {staff.pop_cap}") return 1 pitch_before_seep = staff.pitch m.tick_hz(int(10 * HZ)) # Staff harvest −15% on seep if staff.pitch <= pitch_before_seep: print(f"FAIL: seep did not raise pitch ({pitch_before_seep} -> {staff.pitch})") return 1 # Afford one Acolyte from leftover start pitch + seep one = train_t1_melee(m, FACTION_STAFF, n=1) if one != ["acolyte:ok"]: print(f"FAIL: train one {one} pitch={staff.pitch}") return 1 m.tick_hz(int(8 * HZ) + 2) if not any(u.spec_id == "acolyte" and u.alive for u in staff.units): print("FAIL: acolyte not trained") return 1 # Offline cheat for army mass (GAMEPLAY.md cheats offline-only) m.grant_pitch(FACTION_STAFF, 500.0) train = train_t1_melee(m, FACTION_STAFF, n=4) if any(not t.endswith(":ok") for t in train): print(f"FAIL: train army {train}") return 1 m.tick_hz(int(4 * 8 * HZ) + 2) acolytes = [u for u in staff.units if u.spec_id == "acolyte" and u.alive] if len(acolytes) < 5: print(f"FAIL: want >=5 acolytes got {len(acolytes)}") return 1 m.order_attack_hq(FACTION_STAFF) # 5 * 4 dps = 20 HP/s → 5000 HP ≈ 250s for _ in range(HZ * 300): if m.phase == MatchPhase.ENDED: break m.tick_hz(1) if m.phase != MatchPhase.ENDED or m.winner != FACTION_STAFF: print(f"FAIL: expected staff win got phase={m.phase} winner={m.winner}") print(m.snapshot()) return 1 if host.hq_alive: print("FAIL: host HQ still alive") return 1 print( f"PASS: open+seed+seep+T1 HQ kill t={m.sim_t:.1f}s " f"staff_pitch={staff.pitch:.1f} ticks={m.tick_i}" ) return 0 if __name__ == "__main__": raise SystemExit(main())