Skip to content

feat(playability): 300-game human-vs-AI study + per-level reports - #15

Merged
emp3thy merged 3 commits into
mainfrom
chore/playability-study
Jul 26, 2026
Merged

feat(playability): 300-game human-vs-AI study + per-level reports#15
emp3thy merged 3 commits into
mainfrom
chore/playability-study

Conversation

@emp3thy

@emp3thy emp3thy commented Jul 26, 2026

Copy link
Copy Markdown
Owner

What

A headless playability study: 300 games (100 per difficulty) with a human player (player1, a fixed "measured-reactive" policy) against a fixed 4-AI cast, so difficulty is the only independent variable. Per-game records + narratives, a deterministic aggregator, and per-level trend + playability reports — produced by an agent team (3 play agents, 3 collators).

Deliverables

  • scripts/playability/runner.ts (game loop + per-game record/narrative), humanPolicy.ts (the human proxy), aggregate.ts (exact stats), run.ts (CLI). Run: npx vite-node scripts/playability/run.ts <level> 100 docs/playability.
  • docs/playability/<level>/ for easy/normal/hard — games.jsonl (100 records), games.md (narratives), play-summary.md, trend-analysis.md, and playability-outcome.md (the 3 playability outcome reports).

Headline findings (all from the aggregator)

Level Human win% Human survival% Outcomes Avg rounds Verdict
easy 18 8 58 survivor / 42 pyrrhic 15.2 punishing
normal 28 18 83 survivor / 17 pyrrhic 17.5 most playable
hard 37 0 100 pyrrhic 17.4 broken
  • Human win% rises with difficulty (18→28→37) while survival falls (8→18→0) — because on hard every "win" is pyrrhic (you're the last to die).
  • Hard is degenerate: 100/100 games are mutual annihilation, 17–18 rounds every time, with a per-round launch/impact sequence identical across all 100 games through round 16 (hard drops AI randomization → a near-deterministic script). Player agency ≈ zero.
  • Reporting artifact surfaced: the pyrrhic "winner" is the last leader to die, so winRate/placement read like dominance while survivalRate is 0 — they mislead unless read together.
  • Board damage swamps defence (~71 impacts/game; human takes ~19 hits, lands ~3), worst on hard.

Top recommendations (detailed per level in the reports): make a win-alive state reachable on hard (throttle AI launch volume / buff defensive economy), add variance so hard isn't two scripted games, and fix pyrrhic scoring so the scoreboard stops crediting a corpse as 1st.

Notes

  • Analysis tooling + generated data/reports only — no product/engine code changed; suite untouched.
  • Harness typechecks (tsc --noEmit clean); every cited number comes from aggregate.ts, not hand-counting.

🤖 Generated with Claude Code

Headless harness (scripts/playability/): a fixed measured-reactive human
policy (player1) vs a fixed 4-AI cast, so difficulty is the only variable.
Plays 100 games/level, emits per-game JSON records + narratives, and a
deterministic aggregator so the reports cite exact numbers.

docs/playability/<level>/: games.jsonl, games.md, play-summary.md,
trend-analysis.md, playability-outcome.md (3 playability outcome reports).

Headline: normal is the most playable (83% survivor); easy 58%; hard is
degenerate — 100/100 pyrrhic, human survival 0%, near-identical script every
game. Human win% rises with difficulty (18/28/37) while survival falls
(8/18/0) because hard 'wins' are all pyrrhic. Also surfaced a reporting
artifact: pyrrhic winner = last-to-die, so win%/placement mislead unless read
against survival%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Claude BugBot Analysis

Reviewed the four new TypeScript files (aggregate.ts, humanPolicy.ts, run.ts, runner.ts); verified Order/ResolutionEvent/Leader/Stockpile shapes and the validateOrderSequence/totalApCost/planAi/isHuman signatures against src/engine, and all usages match. No genuine bugs found — the rest of the diff is auto-generated playability report data (docs/playability/**/*.jsonl and *.md).

No bugs were detected in this PR.

…ence

Adds extra.ts (deterministic replay instrumenting AI warhead yields, fired-vs-
built ratio, and launch cadence by game-third) and a findings report answering:
- warhead mix: small-dominant everywhere, rising with difficulty (easy 71% →
  normal 89% → hard 98.5% small; hard never fired a large warhead in 100 games);
- hoarding: none — AI fires 92-96% of what it builds, front-loaded (~75-80% of
  launches in the first two game-thirds);
- game length: easy 15.2 / normal 17.5 / hard 17.4 avg rounds;
- explains the easy<normal survival paradox: easy's 30% randomness manifests as
  3x more large-warhead strikes and shorter, more-pyrrhic games, which punish a
  cautious human more than legible normal AI does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Claude BugBot Analysis

Found 1 potential bug in this PR.

low: 1

The diff is mostly generated docs/data; the actual code changes (scripts/playability/*.ts) are clean and type-consistent with the engine APIs they call. Only one minor correctness issue was found: aggregate.ts's avgEliminatedRound silently defaults to 0 (instead of null/N-A) when no games in a batch have a human elimination, which can produce misleading statistics.

Comment thread scripts/playability/aggregate.ts Outdated
Adds balanced + turtle policies and a policy-selectable runner; runs each of
cautious / aggressive / balanced as a lone human vs the 4-AI cast, 100 games
per difficulty (900 games), with a strategy-comparison report.

Findings: balanced >= cautious >> aggressive, but the human's ceiling is low
(<=7% win vs 4 AI). All-in dies ~2x faster (elim round ~7 vs ~13-16) and never
survives — opening fire spreads grudges and draws concentrated retaliation.
Balanced is the only strategy to score on hard (5% win, best placement).
Escalating 'eliminate the attacker' retaliation lowered survival vs simple
measured retaliation — proportionate response is self-defeating here.

Also fixes the BugBot LOW finding: aggregate.ts avgEliminatedRound now returns
null (not 0) when no human elimination occurs, so it can't misread as 'round 0'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Claude BugBot Analysis

Reviewed the added scripts/playability/*.ts files (the only non-generated code in this diff; the rest is generated markdown/jsonl playability data). Cross-checked all Order/ResolutionEvent field usages, imports, and AP/stockpile validation logic against src/engine/types.ts and src/engine/orders.ts — all signatures, field names, and sequencing logic are correct. No genuine bugs found.

No bugs were detected in this PR.

@emp3thy
emp3thy merged commit 8fdd5a7 into main Jul 26, 2026
1 check passed
@emp3thy
emp3thy deleted the chore/playability-study branch July 26, 2026 10:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant