Repository navigation
M6 PR 1: difficulties, disclosed bot bonus, personalities, retreat and influence maps (lines 1-4) - #56
Merged
Conversation
…hStats Bump SIM_VERSION to 18 and rules_version to 26: difficulty levels in data/ai/difficulty/ and step 6d' pays each bot seat its level's disclosed bonus bundle; every fixture re-recorded, only m5_first_match.hash moves. - rules: Difficulty, FirstRaid and WaveAgeGate loaded from ai/difficulty/<id>.ron with validators and negative tests, resolve() for a level's wave knobs, and a build-order rule that an armed trainer stands before the first Advance; data-check prints difficulties=3. - sim: Sim.bonus derived from the header and the rules (rebuilt in rebuild_derived), economy::bonus_step as step 6d' with SimEvent::BonusIncome, the tick-free SimView::ai_noise, RateInfo::at_cap, sim::stats::MatchStats and verify_observed. - ai: every difficulty id is a Standard bot playing the resolved knobs (bot_ids, is_standard_bot), bot_stats, resolve_personality for random, and the 26 M6 CSV columns fed by MatchStats. - sim-cli: play-bots takes difficulty ids, walks distinct ordered personality pairs (--mirrors adds mirrors) and resolves random; match-log; bench --bots --bot/--personality. - game: --difficulty and --personality for a plain launch and --skip-menu; SimHandle feeds MatchStats; the amber rule is RateInfo::at_cap. m5_first_match moves from 0x3d2552033d51b4a2 at tick 26012 to 0x7207c1e8dc3ce351 at tick 22832: its per-tick hash-dump equals the pre-bump one through tick 4099 and first differs at tick 4100 in economy, the first Standard bonus payment.
- scripts/fun_gate.sh (line 1 by the owner's readings),
check_ordering.sh (line 2), check_attack_before.sh and
check_difficulty_flip.sh (line 4) and bot_matrix.sh (the tuning
matrix); just fun-gate, just bot-matrix and just match-log.
- CI: ROADMAP M6 line 3 in the macOS check and ubuntu headless jobs
(play-bots --seeds 7..7 twice, diff, final hash to
hash-<os>-seed7.txt, with a Hard-against-Easy pair beside it) and
the hash-parity comparison of the two files.
- Docs: ai.md's difficulty table as built, the play-bots pairing, the
M6 CSV columns, match-log and the scripts; DETERMINISM step 6d',
ai_noise and rule 5's wording, MatchStats, the re-recorded
m5_first_match; DATA_FORMAT, data/ai/README, CONTRIBUTING ("A
difficulty"), ARCHITECTURE and STREAMING.
- play_bots test: assert! instead of assert_eq! with a literal bool.
Two pure modules in crates/ai/src/standard that no rule calls yet, so no bot decision and no fixture changes: - influence.rs: per-plan grids of hit points and damage per second (own units, enemy units, enemy shooting buildings) built from the view, Lanchester strength over a block of cells, a per-plan (owner, kind) stats cache and an integer square root. - compose.rs: enemy shares, the counter advantage and factor from the units.ron counter table, and the counter-weighted mix with counter kinds that join only while they counter what the enemy shows. Unit tests cover Arms upgrades, civilians, Wains and the dead, the shooter layer, Bowmen turning the mix to Skirmishers, the Outrider joining against Mangonels, and weight 0 keeping the mix.
…ings m5b_defend_outranks_saving_for_an_age (ignored, CI --ignored m5b_) set up its premise at tick 6700 on the M5b timeline: the Masonry Advance saving up with the Muster Hall idle. The Standard level's bonus from tick 4100 fills the stock earlier and keeps the Muster Hall busy, so the premise no longer held. The test now runs on a copy of the rules whose standard level pays no bonus (income_interval_ds 0), asserts that no bonus is paid, and keeps every assertion. BUILD_TIMES: the sim and ai test times, the seed-7 runs, M5b line 1, the m5_first_match verify and bench --bots in M6 slice 1.
The Standard bot gains the M6 behaviours, all integer, tick-free and restore-exact (rules_version 26 -> 27, SIM_VERSION stays 18 in PR 1): - influence.rs is wired in: per-plan grids built lazily from the view (Ctx::influence), with enemy units read through facts::visible_enemies, the one seam M7's fog filters. - retreat.rs: a wave whose strength falls below retreat_ratio_permille of the enemy's around it, after losing retreat_loss_permille of its units, goes home in one grouped Move, except within the commit window, on a weak target, in the all-in or on Hold; a cooldown delays the next launch. - army.rs: the mix is weighted by compose's counter factors and per-kind noise; emergency production toward the level's raid size runs before the military latch; Wains are trained only while the Wain rule wants one. - waves.rs: raids (RaidUntilSiege) at the most worked enemy building outside tower reach, the level's deadline raid (Hard by 10:00), the Wain rule, seat_last, a siege target scored by distance and influence, a tech gate, and bounded noise on the first wave, sizes (upward only) and intervals. - defend.rs: defend_economy, tower guards attack Wains first, and the defended building that build_order's reactive towers answer. - sites.rs: mirror-aware ties from doubled centres and the partner tile, and noise among the top Expansion and BorderEdge candidates. - The plan phase alternates with the seed; BotMemory appends the raid, retreat and wipe counters and per-wave kind, size and contact. rules: the personality knobs and their validators (negative test per rule), WaveMode and WainRule, Difficulty::worker_target_permille and resolve_order. data/ai: the rush, boom and tower rows and build orders in the random pool, the new knobs in standard. Tests: unit tests per behaviour, tests/m6.rs (ignored, CI --ignored m6_: Hard attacks before 14400 for every personality from both seats, rush before 8400, every level and personality takes Passive's Seat, memory under 1 KiB, restore during a raid and after a retreat). M5b tests that pinned the old timeline use quiet knobs or the new seed-1 cadence. sim-cli bench --bots reports influence_plans. Every fixture is re-recorded for its header; only m5_first_match.hash moves (0x7207c1e8dc3ce351 at 22832 to 0xa97c1bd612bca5f5 at 34098, now P1's Seat capture).
…icap rules_version 27 -> 28. Reason: with one Muster Hall the bonus floated as stockpile (a 4x bundle won 6/12 and 10/12 against 1x), so Standard's build order takes a second Muster Hall after arms_1 and a third after Masonry (the same 4x bundle then won 12/12 from both seats); and, because line 2 does not pass under the bonus ceiling, Easy gets owner decision 2's disclosed handicap, worker_target_permille 850. Every fixture is re-recorded for its header; only m5_first_match.hash moves (0xa97c1bd612bca5f5 at 34098 to 0xf78f9cf501cfe707 at 41889, P0's Seat capture). The rules tests follow the build order's new step indices (38 steps; the armed-trainer case makes both early Muster Halls optional), and the restore test plays rush against tower at seed 2, where the rush seat raids and both seats retreat (against Standard the rush seat now wins before any retreat).
CI: a fun-gate job on ubuntu, in parallel with headless, runs ROADMAP M6 lines 1, 2 and 4 with the literal commands (fun_gate.sh, check_ordering.sh, check_attack_before.sh from both seats and every Random style, check_difficulty_flip.sh) and the ignored m6_ tests; each step runs even when an earlier one failed. The seed-7 pair beside line 3 now plays Hard rush against Easy tower. At rules_version 28 the job fails on line 2 and on the flip's 9/20 bar (docs/design/ai.md). bench --bots: the first-wave noise held both first waves past the bench's minute, so the bench zeroes noise_first_wave_ds and its waves go out on M5b's timeline; the influence gate is 20 plans once a siege wave went out (32 of 240 in the default bench; the maps are built only on plans that read them), not 200. Docs: ai.md gets the personality table, the influence maps' location, retreat, targets and counters as built, the difficulty table as tuned with the line-2 and flip readings and what the tuning measured, the fun gate in CI with line 1 over seeds 1..60 and the B1-B6 and F1-F6 proxies, and the answered open questions; DETERMINISM.md the slice's fixture moves and m5_first_match at 41889; STREAMING.md seed 1; BUILD_TIMES.md the slice's timings.
enemy_military_near (the Build steps' border backoff) and the ring hold's defender list read every unit directly, so M7's fog would not filter them. Both now go through facts::visible_enemies, which passes the same units in the same order today: no decision changes and every fixture verifies with its committed hash. A test pins the remaining units() reads per module, and fog.md names Ctx::occupied as a placement-legality read.
The weak-target case built an Assault wave a few ticks into the arena, so the commit window returned first and the retreat_commit_town_hp_permille check was never reached: with that check disabled every retreat test still passed. The case now uses a staging wave, asserts that assess() turns the rule off, and keeps the same staging wave against the sound Seat as its paired control.
rules::ai::resolve_order applied worker_target_permille with no test: making it return the order unchanged left every rules and ai test green. A rules test now pins the shipped Standard order's Workers counts at Easy's 850 (7, 9, 11, 14, 17, 20, 23 to 6, 8, 10, 12, 15, 17, 20, splits and other steps unchanged, identity at 1000, never below 1), and the ai knobs test checks that each seat's StandardBot and describe play the resolved order.
waves::end counts a wave as wiped when it ends with fewer than recall_below_units armed units, but no test asserted the counter: with the increment disabled every non-ignored ai test passed. wave_ends_without_annexers now checks it for all four of its cases (two armed counts; three Mangonels ending by the annexer rule, three Skirmishers ending by age and a wave that goes on do not). ai.md said 'no armed unit left'; it now states the code's rule.
choose_target skips the enemy Seat only while another enemy Town has no annexation timer, but no test set one up: dropping the condition left every ai test and fixture green. A new test puts P0's own annexation on P1's only other Town (0 HP, Skirmishers on its ring) and checks that the Seat, scoring better, becomes the target again.
match_log_stats_equal_the_harness wrote a replay with no Tick records and stopped before first contact, so verify_observed's Tick branch (the one a human-against-bot replay uses) was never observed by a test: reverting it to an unobserved step passed everything. A new test records 4000 ticks of a human against Hard with Tick records (some on ticks whose next step is a 20-tick sample), past first contact and the human's first losses, and checks that match-log re-derives exactly the MatchStats fed after every step.
The influence_plans gate applied only when a siege wave went out, so the plan's Hard-rush bench (raids only) exited 0 with influence_plans=0 and its ai_ms covered none of the influence, retreat or target code. The gate now applies once any wave went out, in a bots_exercised function with a unit test. The 1200-tick Hard-rush bench fails it (its raids are still inside their 20 s commit window when it ends), so its timing is taken at --ticks 1800: influence_plans=49, ai_ms mean 0.0287/0.0259/0.0230, exit 0. ai.md and BUILD_TIMES record both runs.
…n the flip CI's line-2 step stopped at the first play-bots exit 1 (any draw) under bash -eo pipefail, so it printed nothing and never ran Standard against Hard; line 1 and line 4a had the same shape. Each step now records every failure and exits at its end, and line 2's runs pass --allow-undecided (same CSV; the line counts wins, a panic still fails). check_difficulty_flip.sh passed the flipped run on Hard <= 9/20 alone, so draws counted for Easy and a run with Hard still ahead of Easy could pass. It now also requires Easy >= 11/20 in both seat orders (plan 2a #3) and prints Easy's wins, the draws and the untouched cap games; its header no longer calls the 300/300 run an equal-income control (Easy keeps its 850 Yeomen handicap). check_ordering.sh prints a second line: cap games, draws, untouched cap games and how many of those ended within 3 tiles.
- ai.md: influence maps live in crates/ai, built per plan (target table and the first design paragraph); the plan phase alternates with the seed's parity; difficulty ids in the AI seat; Hard keeps Standard's wave sizes; first_attack_tick can be a raid's launch since M6 (also waves.rs's module doc); the flip table gets seat orders, Easy's wins, draws, the bundle and the commit, and the bar names Easy's majority; the bonus ceiling's formula, its Standard-mirror readings (pooled 177, line 1 184; single losing seats up to 228 and 267) and the open pooled- or-per-match question; untouched cap games as the mechanism behind line 2's and the flip's draws and 3-tile tiebreaks (site noise between mirrored bots), replacing the seat-bias reading; the bundle sweep table; owner decision 1's fun-gate readings; no scratch path. - ARCHITECTURE: ai_noise, bench --bot/--personality, play-bots ids, pairing and random, a match-log row, six CI jobs with fun-gate and the seed-7 steps; IMPLEMENTER_NOTES lists fun-gate among the jobs. - DATA_FORMAT, CONTRIBUTING and data/rules/README: random is required, the walkthroughs are at rules_version 28 with personalities=4 build_orders=4, and CONTRIBUTING states the share formula and the Standard-mirror run it is defined on. - BUILD_TIMES: the seed-7 CI step is about 25 s of games at rules 28. - ROADMAP M6 gains a Notes entry recording the eleven owner decisions of 2026-10-28 (no acceptance line changed), and bare citations now say 'M6 owner decision N' (easy.ron's comment only; rules_hash unchanged).
Owner
Author
|
Step 5 (line 1), step 7 (Hard attacks before minute 12) and step 9 (the ignored M6 bot tests) passed. Cause: when two intact bots meet, defence beats attack. In Easy vs Standard, 15 of 17 games reach the 54000 cap with no Town taken. Mirrored sites then reduce them to exact ties or 3-tile tiebreaks, so the bonus difference never shows. Fix: a retune is running on this branch now (M6 owner decision 12):
It will be pushed here once every fun-gate step passes locally. This PR stays a draft until then, and merges only on a fully green fun-gate (decision 13). Generated by Claude Code |
…oise, all off Owner decision 12 gives the difficulty levels real behaviour. This adds the knobs, neutral in the shipped data, so behaviour is unchanged: - rules: Difficulty.pressure (reinforce_min_units, commit_ds, retreat_ratio_permille, retreat_cooldown_permille, home_guard_units, lure_ds, cohesion_tiles, launch_ratio_permille) with validators; resolve() scales the personality's retreat ratio and cooldown; Personality.noise_border_top_k picks BorderEdge sites among the first k candidates by each seat's own noise. - ai: reinforcement groups from the rally join an assaulting siege wave (and the all-in goes in such groups), a committed siege neither retreats nor times out while its target loses hit points, the lure holds an assault at the Wain point while defenders stand near the target, the vanguard stops for the rear quarter, a launch can wait until the wave outweighs the enemy army at home, and a home guard stays; the siege wave's memory gains its target's last hit points. Unit tests for each. - data: rules_version 29; every new field neutral; fixtures re-recorded for their header; only m5_first_match.hash moves (memory bytes), 0xf78f9cf501cfe707 -> 0x71ed5e727f1430f2, still P0 at tick 41889.
Owner decision 12: Standard and Hard keep up siege pressure and Easy stays defensive. Standard and Hard reinforce assaults with rally groups, commit to a siege that is taking a Town down, never retreat, lure defenders out and keep their waves together; Standard launches only when it outweighs the army at home (0.8), Hard assaults a staged wave only at 1.3 to 1 and waves 0.8 times as far apart. The all-ins go Hard 30:00, Easy 33:00, Standard 36:00. Easy and the tower personality research Statecraft II, so a cap game in which no Town fell is no longer a mirror image; Easy drops the 850 Yeomen fallback, which measured as a stronger Easy. Code: the cohesion rule counts only units past the stage point and never stops a unit under fire, a rally group always leaves one armed unit home, Pressure gains assault_ratio_permille and Difficulty gains extra_research. The measured-and-unused standoff knob and the personality noise_border_top_k are removed. rules_version 30; every fixture re-recorded for its header, only m5_first_match's .hash moves.
The bench's mirrored world never lets a wave outweigh the enemy army at home, so it sets each level's nonzero launch and assault gates to 1 per mille (still checked and timed) and gives a level that never retreats the personality's retreat back, so the influence and retreat paths it times still run. docs/design/ai.md gains the retuned difficulty table, the behaviour, sweep and stalemate analysis, the rules-30 flip and fun-gate readings, and answers the open questions owner decisions 12 and 14 settled; the data READMEs, DETERMINISM, STREAMING, CONTRIBUTING, DATA_FORMAT and BUILD_TIMES follow rules_version 30.
…nforcement Pin the research that keeps mirrored cap games apart: Easy's resolved order differs from Standard's and Hard's, tower researches Statecraft II, and Standard and Hard still resolve to the same order.
The review of the M6 retune found that pressure.retreat_ratio_permille 0 had switched retreat-when-losing off for Standard (and its raids). A committed siege still never retreats. Hard keeps 0: restoring it at 500 or 1000 cost line 2's second clause and the flip's 200 clause. rules_version 31; only m5_first_match.hash moves (38818, P0).
… owner Rewrite the flip's explanation from the measured capture ticks and drop the general all-in-first rule; correct the sweep figures; record the seeds 41..60 flip miss, the Balanced-only ordering, the balance band, the F1 and F2 proxies at rules_version 31 and the same-research mirror limit; list Easy's earlier all-in and Hard's missing retreat as deviations pending the owner. Fix the docs that still said the standard level resolves to the personality itself, the SIM_VERSION 18 line, the ARCHITECTURE rows and the BUILD_TIMES line-3 row.
tonianev
pushed a commit
that referenced
this pull request
Oct 30, 2026
Each line keeps its text and gains the evidence observed locally at rules_version 31 and in Actions run 41008686740 at de5b0cd (fun-gate, macOS check, headless and hash parity). Lines 5 and 6 are slice 3's and the owner's.
Each line keeps its text and gains the evidence observed locally at rules_version 31 and in Actions run 41008686740 at de5b0cd (fun-gate, macOS check, headless and hash parity). Lines 5 and 6 are slice 3's and the owner's.
tonianev
force-pushed
the
claude/confident-hopper-ho6dz1
branch
from
October 30, 2026 20:51
237c3ee to
9808600
Compare
tonianev
marked this pull request as ready for review
October 30, 2026 20:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
M6's first PR covers the plan's slices 1 and 2, the AI half of the fun gate. This is a draft. The
fun-gateCI job is expected to fail on the line-2 and flip steps until the retune now running on this branch lands. Per M6 owner decision 13, this PR merges only when every fun-gate step is green.Slice 1 (SIM 17 → 18, rules 25 → 26)
data/ai/difficulty/{easy,standard,hard}.ronwith validators.BonusIncomeevent. It is rebuilt on restore and is never rubber-banded (M6 owner decisions 2 and 14).SimView::ai_noiseis a pure, tick-free hash that never touches thePcg32. Each seed is now a different game; before, all 20 seeds of the Standard mirror played identically.MatchStats, engine-free and unhashed. It feeds the play-bots CSV and the newsim-cli match-log.--a/--b easy|standard|hardand personality lists, played as distinct ordered pairs (--mirrorsto opt in).fun_gate.sh,check_ordering.sh,check_difficulty_flip.sh,check_attack_before.sh,bot_matrix.sh.Slice 2 (rules 27 → 28)
crates/ai, never stored;seat_last: waves take the second Town before the Seat.fun-gatejob.Review: four lenses raised 32 findings; 30 were confirmed and fixed and 2 refuted.
Status against the ROADMAP (nothing ticked yet)
Why lines 2 and 4 fail: when two intact bots meet, defence beats attack. In Easy vs Standard, 15 of 17 games reach the 45-minute cap with no Town taken. Mirrored sites then make each one an exact tie or a 3-tile tiebreak, so the bonus difference never shows.
Fix in progress (M6 owner decision 12): give difficulty the behaviour the ROADMAP names:
The bonus stays under the ceiling, read as averaged over a run (decision 14).
Determinism and fixtures
SIM 18 and rules 28, with all 11 fixtures re-recorded. Only
m5_first_match.hashmoved, and the hash-dump explains each move: the first bonus payment at tick 4100 at rules 26, then slice 2's phase change.Tier
Code PR
just cipasses locally. machete and deny are not installed here; CI runs them.m5b_15,m6_5;f32/f64/HashMap/HashSet/Instantin sim, rules or ai, and no engine dependency outsidecrates/game.Data PR
data-check:OK rules_version=28 rules_hash=0xed93afd3bba71fb9 ... personalities=4 build_orders=4 difficulties=3rules_versionbump has its one-line reason and a re-record.Licensing and disclosure
How to verify
Generated by Claude Code