Skip to content

Tweaker#16

Open
ShadyMccoy wants to merge 3 commits into
masterfrom
Tweaker
Open

Tweaker#16
ShadyMccoy wants to merge 3 commits into
masterfrom
Tweaker

Conversation

@ShadyMccoy

Copy link
Copy Markdown
Owner

No description provided.

ShadyMccoy pushed a commit that referenced this pull request May 6, 2026
First post-matchmaker season. Pinwheel_g1_24119c (PHASE_TICKS=3 vs
parent's 4) takes rating-leader AND round-robin. First descendant
in this work to outrank its parent in a season.

200-match rating phase + 11-round round-robin completed in 13
seconds — about 15-20x faster than the 800-match seasons we ran
before, because the info-gain matchmaker concentrates the budget
on uncertain bots. Compound and forward-aware Spearhead descendants
(loop 9) didn't crack the top.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg
ShadyMccoy added a commit that referenced this pull request May 6, 2026
* Seed tournament/rankings.json from 1000-match rating run

Initial seed data for the new PL-driven league/season system. One pool-play
batch on lab1 (--rating --matches 1000), 140 players, 50-65 matches per bot.
PL fit converged in 74 iterations. Spread 750 (Tactician) to 1539 (Spearhead).

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Prune 41 sub-950 bots from active tournament pool

Drops the long tail of weak factory archetypes (Pulse_*, Stencil_*,
Probe_*, Cluster_*, Patient_*, Drift_*, Scatter_*) plus a handful of
named characters with near-zero win rates on lab1 (Hunter, Berserker,
Vampire, Avalanche, Anvil, Swarm, Tactician). Pool: 140 → 99 active.
Doubles per-bot match coverage in subsequent seasons and stops noise at
the bottom of the rating distribution from drowning out signal.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #6: Spearhead sweeps both crowns

First post-prune season (99-bot pool, 800-match rating phase on lab1 +
21-round RR on lab3). Rating leader and round-robin winner are both
Spearhead (1418 rating, 88% rating-phase wins, PPG=6.24 in RR).

Top 10 by rating: Spearhead, Pinwheel, Crusader, Stalker, Vanguard,
Membrane, PressureSink, Spearhead_g1_859468, Trinity, Bully_03.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Append season matches to matches.jsonl

cmdSeason was building flagged-match entries but never feeding the
global match log, so rating-phase + round-robin matches from --season
runs didn't contribute to the Plackett-Luce fit in npm run rank.

Now every match (rating phase tagged with --map, round-robin tagged
with --season-rr-map) is pushed into the match log alongside the flag
detection, and appendMatches is called at the end of cmdSeason with
the same "Logged N matches ... Run npm run rank to refresh" footer
that pool play already emits.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Spawn first descendants of Spearhead and Crusader; fix archive clobber

Spawned two gen-1 descendants, one per Season #6 champion (both
crowns went to Spearhead, but I diversified the second slot to
Crusader for a different family).

  Spearhead_g1_3f955f - rear-flank diagonal kernel weights bumped from
  1 to 2 (was 1, was 1) for fuller wedge formations. Targets the early
  flanking eliminations seen in season #6 losses 3-5 (ticks 95-112 vs
  Pinwheel/Lance/Membrane).

  Crusader_g1_5ae640 - require a 5% kill margin (myEff > enemy * 1.05)
  before committing instead of myEff > enemy. Skips razor-thin kills
  that leave the captured tile at near-zero residual strength and
  immediately recapturable.

Also fixes a real bug in applyArchivalForSpawn: it rebuilt
src/strategies/archive.js from lineages.json's active=false set,
silently clobbering manual --archive-add entries (whose lineage
flags stay active=true). archive.js is the source of truth for the
STRATEGY_LIST filter; lineage.active is auxiliary. Now we union the
existing archive.js with the lineage-derived names before writing.
The manual 41-bot prune was lost on the first register-descendant
and has been restored alongside this fix.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #7: Stalker takes round-robin crown on lab3

First post-spawn season. Spearhead defends rating-phase #1 (88% wins,
1344) but Stalker takes the round-robin on lab3 with 8 wins out of 21
and PPG=6.95, dethroning Spearhead from the larger map.

The new Spearhead descendant (Spearhead_g1_3f955f, rear-flank kernel
weights 2 instead of 1) lands rating-phase #9 with 56% wins and
round-robin #3 - a respectable debut but still worse than the
ATTACKER_BONUS=1.45 sibling at #4. Crusader_g1_5ae640's 5%
kill-margin tweak is statistically neutral vs the parent (1083 vs
1084, both within RD ~52).

Champions: rating-leader Spearhead (defended), round-robin Stalker
(new). Spearhead family now occupies 3 of the top 10 rating slots.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Drop the per-family active-member cap

Removes DEFAULT_FAMILY_CAP, the --family-cap CLI flag (run.js + loop.js),
the family-cap branch in applyArchivalForSpawn, and the help-text mention
under --register-descendant.

The cap was an ad hoc diversity guardrail. In practice it forced
auto-sibling-archival on every spawn from a saturated family, which
just churned the same family without adding signal. Diversity is
better controlled by which parent we spawn from each iteration; the
cap added a constraint without fixing the underlying choice.

Global-weakest archival on spawn is preserved — pool size still stays
zero-sum — and so is the family-suicide guard (a family can't
archive its only active sibling on its own spawn).

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Spawn Spearhead and Stalker descendants for Season 3

Loop 1 by-the-book: Season #7 champions were Spearhead (rating-leader)
and Stalker (round-robin), each gets one gen-1 descendant.

  Spearhead_g1_fbedc2 - body-far-behind kernel weight 2 (was 1).
  Strengthens preference for two-deep rear columns; explores a
  different kernel axis from the existing Spearhead siblings.

  Stalker_g1_8767f6 - distant-prey growth bank +0.75 (was +0.5).
  Two of Stalker's losses ran to max-ticks; loosening the
  beatability gate on distant prey lets it engage farther while
  the strict adjacent-attack guard prevents suicide.

Auto-archived: Drift_W and Drift_NW (global weakest on spawn).

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #8 + spawn next-loop descendants

Season #8 champions: Spearhead (rating-leader, again) and Crusader
(round-robin). Two new gen-1 descendants:

  Spearhead_g1_dc0148 - friendly-target penalty -20 (was -10).
  Stronger expansion bias; explores the target-preference axis the
  three existing Spearhead siblings haven't touched.

  Crusader_g1_5f8b45 - target weakest winnable enemy instead of
  strongest. Single comparison flip; tests whether the weakest-prey
  heuristic that works for Stalker also works for Crusader.

Auto-archived: Defender, Drift_E.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #9 + spawn 2x Spearhead descendants

Season #9 champions: both crowns went to Spearhead. By-the-book
gives 2 spawn slots from the same parent; two distinct mutations:

  Spearhead_g1_bf0e20 - empty-target bonus +30 (was +20). Stronger
  pull toward expansion when stencil scores tie.

  Spearhead_g1_4f6c9f - commit threshold 0.75 (was 0.5). Skips
  thin forward pushes; lets the army accumulate strength a tick
  longer. Adjacent kills unaffected.

Auto-archived: Scatter_05, Cluster_01.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #10: closes the 3-loop run

Final season of the loop run. Spearhead defends both crowns again
(rating-leader + round-robin). 3 loops in this session:
  Loop 1 (Season #8): Spearhead + Stalker spawned
  Loop 2 (Season #9): Spearhead + Crusader spawned
  Loop 3 (Season #10): 2x Spearhead spawned
6 new gen-1 descendants registered, 6 weakest bots auto-archived.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Spawn 2x Spearhead descendants for Season 6

Loop 4 by-the-book: Season #10 sent Spearhead to both crowns again,
so two more Spearhead variants from unexplored levers:

  Spearhead_g1_1f0d47 - ATTACKER_BONUS 1.5 (was 1.4). Pushes
  further along the axis where sibling _859468 (1.45) succeeded.

  Spearhead_g1_72c4f8 - body-immediately-behind kernel weight 4
  (was 3). Heavier weight on the most directly-useful support cell.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #11 + spawn 2x Spearhead descendants for loop 5

Season #11 sent both crowns to Spearhead again. Two more Spearhead
variants on previously-untouched levers:

  Spearhead_g1_1b59ef - target weakest winnable adjacent enemy
  instead of strongest. Mirrors Crusader_g1_5f8b45's experiment in
  a different family.

  Spearhead_g1_7a4f15 - friendly-target penalty -30 (was -10).
  Stronger than _dc0148 (-20). Tests further aggressive expansion.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #12 + spawn Stalker and Pinwheel descendants

Season #12 broke Spearhead's monopoly: rating-leader Stalker,
round-robin Pinwheel. Two new gen-1 descendants:

  Stalker_g1_86aa0f - BONUS 1.45 (was 1.4). Mirrors the lever that
  worked for Spearhead.

  Pinwheel_g1_24119c - PHASE_TICKS 3 (was 4) for faster directional
  rotation. First Pinwheel descendant.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #13 + spawn Stalker and Spearhead descendants for loop 7

Season #13: Stalker (rating-leader, defended) + Spearhead (round-robin).
Two new gen-1 descendants:

  Stalker_g1_bfb56c - adjacent kill margin 0.8 (was 0.6). Tighter
  required margin for cleaner residual strength on captured tiles.

  Spearhead_g1_d8bf0f - ATTACKER_BONUS 1.35 (was 1.4). Tests the
  cautious end of the curve siblings _859468 (1.45) and _1f0d47
  (1.5) walked up.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #14 + spawn 2x Spearhead descendants for loop 8

Season #14 sent both crowns to Spearhead. Two more variants on
unexplored extension axes:

  Spearhead_g1_36d9c1 - empty-target bonus +40 (was +20). Pushes
  further than sibling _bf0e20 (+30).

  Spearhead_g1_7f5793 - commit threshold 1.0 (was 0.5). Pushes
  further than sibling _4f6c9f (0.75). Maximum consolidation.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #15 + close 5-loop run

Final season of the second 5-loop run. Spearhead defends both crowns
again under the shorter 400-match + 11-round-RR format. Loop summary:
  Loop 4 (S#11): 2x Spearhead spawned
  Loop 5 (S#12): 2x Spearhead spawned (champions Stalker+Pinwheel
                  came in next season)
  Loop 6 (S#13): Stalker + Pinwheel spawned
  Loop 7 (S#14): Stalker + Spearhead spawned
  Loop 8 (S#15): 2x Spearhead spawned

10 new gen-1 descendants registered across the 5 loops; 10 weakest
bots auto-archived. Spearhead family remains dominant under both
season lengths.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Loosen spawn prompt: descendants can be loosely related

13 single-constant Spearhead variants and not one outranked the
parent across 10 seasons. The "tiny mutation" framing pushed every
descendant into the same small neighborhood of the parent's design,
where engine noise drowns the signal. Replacing that with: descendants
take inspiration from the parent but are free to diverge - swap
fallbacks, restructure kernels, change the thesis. The only hard
constraints are now the file/API contract.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Persist rating priors across seasons; info-gain matchmaker

The rating tournament was sampling lineups uniformly at random, so
every season the match budget was spent rebuilding ratings the
global rankings.json already had. New bots got the same number of
matches as well-known bots, and the per-season Glicko reset
discarded everything we already knew.

Three changes:

  - rankingsStore exposes priorMap(): { name -> { rating, played } }
    derived from rankings.json so the scheduler can see each bot's
    prior rating and accumulated match count.

  - runRatingTournament accepts `priors` and uses them to drive an
    info-gain matchmaker: each lineup anchors on the bot with the
    highest synthetic RD (typically a freshly spawned descendant
    with played=0) and fills with K-1 bots sampled from the known
    field (RD <= median), weighted toward the lowest-RD references.
    Old-vs-old matches mostly stop happening; new-vs-old dominates.
    Falls back to random sampling when no priors exist.

  - cmdSeason loads rankings.json before runSeason, threads priors
    through season.js to runRatingTournament, and refits PL on the
    cumulative (current-rules) match log after the season so the
    next season starts with up-to-date priors.

Smoke tested with --matches 30 --seed 999: lineups concentrated
new descendants as anchors, surrounded them with established bots
(Spearhead, Membrane, Vanguard, etc.) — exactly the calibration
shape we want.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Loop 9 spawns: compound and forward-aware Spearhead

  Spearhead_g1_1a87fc - compound stack: ATTACKER_BONUS=1.45 +
  empty bonus +30 + body-immediately-behind=4. Tests whether the
  three earlier siblings' levers compose additively.

  Spearhead_g1_e91678 - forward-aware: adds a forward-enemy
  penalty alongside the existing rear-support kernel, so directions
  blocked by a fat enemy line forward score lower.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #16: Pinwheel_g1_24119c sweeps both crowns (13s run)

First post-matchmaker season. Pinwheel_g1_24119c (PHASE_TICKS=3 vs
parent's 4) takes rating-leader AND round-robin. First descendant
in this work to outrank its parent in a season.

200-match rating phase + 11-round round-robin completed in 13
seconds — about 15-20x faster than the 800-match seasons we ran
before, because the info-gain matchmaker concentrates the budget
on uncertain bots. Compound and forward-aware Spearhead descendants
(loop 9) didn't crack the top.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Loop 10 spawns: two Pinwheel g2 descendants

Champion of S#16 was Pinwheel_g1_24119c (PHASE_TICKS=3). Two g2
variants:

  Pinwheel_g2_ae8180 - PHASE_TICKS=2. Pushes the trend (4->3->2):
  even faster rotation.

  Pinwheel_g2_22a2a2 - asymmetric phases [W=2, N=4, E=2, S=4],
  aligning the time budget with lab1's wrap geometry (24-wide
  horizontal wrap, 18-tall non-wrap).

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #17: Spearhead + Stalker (17s)https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Loop 11 spawns: Spearhead with Conqueror+Defender fallbacks, Stalker with nearest-prey tiebreak

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #18: Spearhead + Pinwheel (13s)https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Loop 12 spawns: phase-aware Spearhead, Pinwheel-Kill hybrid

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #19: Spearhead_g1_4f6c9f + Crusader (13s)https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Loop 13 spawns: Spearhead g2 with rear-support floor, Crusader with Conqueror fallback

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Save Season #20: Spearhead reclaims both crowns (16s)https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Parametric Spearhead template + GA search driver

Two new files. The framework removes the LLM from the inner loop:
mutations are sampled from a parameter schema, evaluated in-process
with no spawn/register/commit/log overhead, and only the best
vectors are reported at the end.

  src/strategies/parametric/Spearhead.js
    makeSpearheadVariant({...params, name}) -> strategy. Exposes the
    7 magic constants of the original Spearhead as parameters
    (attackerBonus, three kernel weights, empty bonus, friendly
    penalty, commit threshold). SPEARHEAD_DEFAULTS reproduces the
    parent exactly; SPEARHEAD_SCHEMA gives bounds + sigma + int
    flag per knob for the search.

  tournament/ga.js
    CLI driver. Population-based GA with elite preservation,
    uniform crossover, per-knob gaussian mutation. Fitness =
    average rank across N matches against a fixed opponent pool
    (default: top-10 from rankings.json). Final top-K re-scored
    with 3x more matches for confidence. Optional --out for JSON
    export. Smoke test: 38 matches/sec single-threaded.

The match log is bypassed entirely - per-evaluation matches don't
call appendMatches, so 10k+ iterations add zero log growth.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* GA demo run: 360 evals × 20 matches in 211s, no improvement over default

First non-trivial GA run on the parametric Spearhead schema:
pop=24, gens=15, eval=20 matches per individual against the
top-10 opponent pool. 7,200 matches total at 35.6 matches/sec
single-threaded.

Best discovered vector at 60-match re-eval scored fitness 2.633
vs default 2.700 (avg rank, lower=better) - within sample noise.
Win rate actually regressed (5% vs 8%). Search converged on:
  attackerBonus=1.45 (matches the hand-tuned _859468 sibling)
  bodyClose=4 (matches _72c4f8 sibling)
  flankClose=4 (jump from default 1)
  emptyBonus=50 (jump from default 20)
  friendlyPenalty=-3 (much weaker than default -10)
  commitThreshold=0.35 (stays near default 0.5)

Direction-of-travel matches what we found by hand. Magnitude
suggests the Spearhead basin is broad and shallow - small
perturbations don't escape it. To beat the parent confidently
we likely need (a) more evals (1000+ to clear noise), (b)
larger schema with architectural freedom, or (c) different
parent families.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Matrix-knob GA + parametric Pinwheel template

Two additions to the GA framework:

  src/strategies/parametric/Spearhead.js
    + makeSpearheadFromKernel({...scalars, kernel: float[25]}).
    + MATRIX_SCHEMA exposes the full 5x5 East-facing weight matrix
      as a 25-cell array knob alongside the four scalars
      (attackerBonus, emptyBonus, friendlyPenalty, commitThreshold).
    + MATRIX_DEFAULTS reproduces the original Spearhead pattern.
    + Internal refactor: shared spearheadFromOffsets() so the
      scalar-knob and matrix-knob factories use the same act().

  src/strategies/parametric/Pinwheel.js
    + makePinwheelVariant({rotation, phaseTicks, killPriority,
      attackerBonus, name}). Pinwheel has 4-cell rotation order and
      4-cell phase durations; both exposed as integer array knobs.
    + killPriority toggles between original "phase-direction first"
      and Crusader-style "kill-first then phase" behavior.

  tournament/ga.js
    + Schema entries can now be array-typed: {length, min, max,
      sigma, int?}. Mutation does per-cell gaussian; crossover is
      per-cell uniform. Avoids "switch parents every generation"
      behavior that would block matrix exploration.
    + --warm F flag seeds a fraction of the initial population from
      the schema defaults perturbed (rest random). Default 0.5 -
      crucial on high-dim knobs where pure random init is hopeless.
    + Always seeds slot 0 with unmutated defaults so elite
      preservation can't regress below the parent.
    + Pretty-prints 5x5 matrix knobs in the final report.

First non-trivial matrix run (pop=30 gens=30 eval=20, 10 minutes,
18,000 matches): best vector lands fitness 2.250 vs default 2.700
- 17% better avg rank than the hand-tuned parent. Same 8% win rate
at 60-match re-eval, so the gain shows up in placement consistency.
The discovered kernel uses all 25 cells (including forward cells
the original ignored) and has noticeable asymmetry across the
perpendicular axis - not reducible to "tweak three weights."

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Add parametric Stalker schema; fix default-bot label in GA report

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Pinwheel GA: never-North asymmetric sweep beats default by 0.85 fitness

GA over the parametric Pinwheel schema (rotation, phaseTicks,
killPriority, attackerBonus): pop=24 gens=20 eval=20, 9,600 matches
in 233 seconds. Default Pinwheel scores 2.910 fitness vs GA winner
at 2.060 in a 300-match re-evaluation - 0.85 better placement,
roughly 14 standard errors apart.

Surprising shape of the discovered vector:
  rotation=[3, 0, 1, 0]   = S, W, E, W (W=0, E=1, N=2, S=3)
  phaseTicks=[7, 6, 4, 7]
  killPriority=0
  attackerBonus=1.319

The bot never sweeps North. ~58% of the cycle pushes West, 29%
South, 17% East, 0% North. Lower attackerBonus than default 1.4.
This is not a tweak we'd have hand-tuned - the original W->N->E->S
symmetric sweep was a designer's prior, and the GA discovered that
on lab1 (24-wide horizontal wrap, 18-tall non-wrap) ditching the
N axis entirely is much more effective.

Win rate is unchanged (4.3% both). So the discovered bot is more
robust (avoids elimination, lands #2/#3 more often) without being
more dominant (still rare to claim #1). Suggests fitness function
choice matters - avg-rank rewards consistency, win-rate rewards
peaks. Different objectives may surface different optima.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Add tech-loadout knob to GA (SpearheadMatrixTech schema)

makeSpearheadFromKernelWithTech adds a 5-element tech array
[move, stack, prod, atk, def] alongside the matrix knob. Returns a
runMatch lineup entry { strategy, tech, name } that the engine
honors directly. Tech is normalized to non-negative integers
summing to 100 (engine requirement) - clamps negatives, scales,
distributes rounding error onto the largest cells.

MATRIX_TECH_SCHEMA reuses MATRIX_SCHEMA's matrix and adds the tech
knob with cell range 0-50 and sigma 4. MATRIX_TECH_DEFAULTS uses
neutral [20,20,20,20,20]; sanity check passes (matches default
Spearhead at 2.700 fitness).

Also: pretty-print rotation, phaseTicks, and tech in the GA report
instead of "[N/M nonzero]" - the latter is meaningless for arrays
of integer indices or normalized values.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Stalker GA result: null (default already optimal in schema)

Default Stalker scored 2.567/5% wins; best GA vector at 60-match
re-eval scored 2.533/7% wins - within noise. Pattern recognized:
small scalar schemas (6 knobs of bounded continuous tweaks)
plateau immediately because the parent already lives near the
schema's local optimum. The targetMode flag stayed at 0 (weakest-
first, default) in all top vectors, so the nearest-first thesis
also failed to dominate.

Contrast: Pinwheel (8-cell discrete rotation+phase array) found
0.85 fitness improvement; Spearhead matrix (25-cell continuous
kernel) found 0.30 fitness improvement; Stalker (6 scalars) found
nothing. Higher-dim schemas with structural freedom unlock real
search; constant-tweaking does not.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg
EOF

* Add ga-controls.js: structurally-weird Spearhead baselines

Quick "shouldn't work" runner. Instantiates seven matrix-Spearhead
variants (default, all-zero kernel, uniform +/-1, inverted, forward-
only, anti-pattern, random-uniform) plus a random-weight baseline,
scores each against the same opponent pool the GA uses.

Initial 200-match run revealed:
  - default-Spearhead    fitness 2.835, wins 7.0%
  - all-zero-kernel      fitness 3.020, wins 7.0%  (!)
  - inverted/forward     fitness 3.17-3.29, wins 0.0%

The kernel barely affects win rate - all-zero matches default at 7%.
The kernel's contribution shows up in placement consistency only.
Adjacent-kill logic + empty/friendly bonuses are the dominant
fitness drivers. Explains why the matrix GA only improved fitness
by 0.30 (the kernel was already nearly maxed out for placement, and
win rate is bounded by the non-kernel logic).

To raise win rates we need a different schema axis (combat logic,
fallback choice, time-aware behavior) - not a better kernel.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* SpearheadMatrixTech GA: 92% win rate at 300-match re-eval

GA over the matrix kernel + 5-element tech array: pop=30 gens=30
eval=25, 22,500 matches in 22 minutes. Best vector at 300-match
re-eval scored 92.3% wins (277/300) and fitness 0.273 vs default
SpearheadMatrixTech at 5.7% wins (17/300) and fitness 2.797.

That's a 16x win rate jump and ~10x improvement in avg-rank from
the same engine, against the same elite top-10 opponent pool.

Key discovery: the GA dumps almost all 100 tech points into MOVE
(m=46 s=0 p=1 a=2 d=2 in the top vector). Per the engine's tech
slopes, move=46 reduces the per-attack garrison floor from 1.0
(neutral) to ~0.72 - more strength gets thrown into combat each
tick. Combined with a tuned 25-cell kernel and friendlyPenalty
=-29 (3x default), the bot dominates.

CAVEAT: opponents in the pool use neutral tech; our bot has a
tech advantage they can't match. Need follow-up isolation:
  - matrix winner WITHOUT tech (neutral) vs default
  - default kernel WITH move-heavy tech vs default
  to attribute the win between kernel design and tech allocation.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Register three move-tech wrapper bots; sweep rating phase top-3

Three new gen-1 descendants, each just rebrands its parent with
{move:90, stack:0, prod:2, atk:4, def:4} tech (the GA's discovered
allocation). No act() changes - the entire win comes from the tech
override.

  Spearhead_g1_7089d3 - lab1 96.2% wins (50/52), rating 1632
  Membrane_g1_b9f1d5  - lab1 96.1% wins (49/51), rating 1557
  Conqueror_g1_879a88 - lab1 84.3% wins (43/51), rating 1291

Season #21 (lab1, 200 matches): movers swept rating-phase #1/#2/#3.
First time any season has produced a bot above 1500 rating - the
previous record was Spearhead's mid-1300s.

Twist on round-robin (lab3, 10 players): Phalanx (defensive) won;
the movers placed #4/#6/#10. Move-heavy tech advantage doesn't
carry as cleanly to the larger arena where defensive territory
holding matters more. Optimal tech is map-dependent.

Auto-archived: Spearhead_g1_859468, Drift_NE, Spearhead_g1_171465.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Spawn prompt: surface parent tech and techs.md so descendants can touch it

The spawn prompt was building from parent source + losses + winners +
strategies.md + engine-api.md, but never mentioned tech. Result: 30+
LLM-spawned descendants in this session, none touched the tech axis,
even though tech allocation is the dominant rating lever.

Three small additions:
  - loadDocs() also reads docs/techs.md
  - prompt receives parentTech (from CHARACTER_TECHS, fallback neutral)
  - new "Parent's character tech" section in the prompt with the
    JSON loadout, a one-line explanation of the slot, and a note
    that descendants can override via a `tech` field on the export

Spread-inherit (`...Parent`) still carries the parent's tech as
before, so existing spawn output is unchanged unless the agent
deliberately adds a tech field.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Rebalance move tech: linear 2.0->0.5 garrison, no clamp; bump RULES_VERSION

Move was the dominant tech axis with a 12.5x dynamic range
(1.25 -> 0.10 garrison) versus def's 1.95x and atk's 1.32x. The
GA exploited this immediately: dumping ~90% of tech points into
move turned every tested strategy from a 5-30% win-rate underdog
into a 80-96% dominant. Tech allocation became "always go move-
heavy" rather than a strategic choice.

New formula (linear, no breakpoint):
  garrison = 2.0 - 0.015 * tech

  tech   0 -> 2.0 garrison (double the engine baseline)
  tech  20 -> 1.7          (neutral allocation - now genuinely costly)
  tech  50 -> 1.25
  tech  80 -> 0.8
  tech 100 -> 0.5

Dynamic range is 4x (2.0 / 0.5), comparable to def's 2x and atk's
1.3x. Move still matters - it's the only knob that affects
throughput per attack - but it no longer overwhelms.

Engine-level changes:
  - SLOPES.move 0.0125 -> 0.0150 (rate per tech point)
  - MOVE_INTERCEPT = 2.0 (new intercept; replaces "pivot at neutral")
  - MIN_GARRISON_FLOOR removed (no clamp; the formula is the contract)
  - techToMultipliers special-cases move to use the linear intercept
  - RULES_VERSION bumped v1 -> v2; all 9300 prior matches.jsonl
    entries stop counting in `npm run rank`. They stay on disk.
  - docs/techs.md updated to describe the new formula and the
    cost of skipping move investment.

The three move-tech wrapper bots (Spearhead_g1_7089d3,
Membrane_g1_b9f1d5, Conqueror_g1_879a88) keep their tech configs
as-is - they'll still benefit but to a much smaller degree, which
is exactly the point.

Data-driven validation pending: re-run the cross-strategy
move-heavy sweep under v2 to confirm the imbalance is gone.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Move tech: lower the slope (option 1) — 1.5 → 0.5 over [0,100]

The first v2 attempt used 2.0 → 0.5 (slope 0.015), which left
neutral at garrison 1.7 — a heavy penalty for skipping move
investment. The validation sweep showed move-heavy still beating
neutral by ~60 pp, almost as much as before. The penalty just
moved from "move is uniquely good" to "not investing in move is
uniquely bad."

Option (1): lower the slope to 0.01 and the intercept to 1.5.
  garrison = 1.5 - 0.01 × tech

  tech   0 -> 1.5
  tech  20 -> 1.3 (mild penalty for low move)
  tech  50 -> 1.0
  tech 100 -> 0.5

Dynamic range is 3× (1.5 / 0.5), comparable to def's 1.95×.
Neutral garrison (1.3) is much closer to the engine's pre-tech
"always leave 1" baseline. Move investment still pays off, but
the gap between move-90 and other allocations should be much
smaller than the original 12.5× formula or the v2-take-1 4×.

Validation re-running.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Bump RULES_VERSION to v3; fix stale intercept comment

User direction: with the move slope rebalance settled, mark a
clean break. Bumping v2 -> v3 invalidates any v2 matches the
ranker would otherwise honor (mostly empty - we only ran a few
seasons under v2). Old matches.jsonl and rankings.json are being
wiped this turn; a fresh --rating calibration follows.

Also: the comment in techToMultipliers still referenced the
take-1 intercept of 2.0; updated to 1.5 to match the actual
constant.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Refresh rankings.json under v3: 2000-match calibration, all bots at RD floor

Wiped matches.jsonl (was 9300 v1 entries) and rankings.json, ran a
clean 2000-match --rating tournament under v3. Every active bot
played 100+ matches, putting all of them at the synthetic-RD floor
of 50 (maximum certainty). PL fit converged in 35 iterations.

Top under v3:
  1   1323  Conqueror_g1_879a88  86.1% wins  (was 92% pre-rebalance)
  2   1225  Spearhead_g1_7089d3  56.9% wins  (was 96%)
  3   1206  Stalker              68.7% wins
  4   1203  Spearhead            72.1% wins  (was ~30% with neutral tech)
  5   1167  Membrane_g1_b9f1d5   61.2% wins  (was 96%)

Move slope rebalance worked as intended: Spearhead_g1_7089d3 and
Membrane_g1_b9f1d5 dropped from 96% wins to 57-61%, putting them
in the same competitive band as their non-tech-tweaked parents.
Conqueror_g1_879a88 retained more of its advantage (still leads),
suggesting Conqueror's minimum-overkill kill logic compounds
particularly well with low garrison even at the reduced slope.

Spread: 1323 -> 884 (439-point band across 99 bots), tighter than
v1's ~800-point band but still meaningful.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

* Drop tournament/seasons.json: 17k lines of v1-era stale data

All 21 seasons in the file ran under v1 engine rules. After the
v3 rebalance the recorded loss contexts and ratings are misleading
as a measure of current bot skill, so the file is stale data
masquerading as fresh.

Removing it drops ~85% of the branch diff (17,238 lines). Code
that reads it (spawn.js loss-context prompt, SeasonViewer browser
UI) handles a missing file gracefully via seasonStore.readStore's
ENOENT fallback to { seasons: [] }. Next --season run regenerates.

https://claude.ai/code/session_01KshiDLRhdzLrhizR1U2mfg

---------

Co-authored-by: Claude <noreply@anthropic.com>
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