Log · 2026-09-19

A structured prior over initial placements

Hypothesis, stated before any code or game (study setups/structured-prior, theory T9 of the program, brief agents/briefs/setup-prior.md): with the same move policy in both seats, a structured setup source, Flag on the back two rows with Bombs likely beside it, Scouts forward, Miners back, Marshal and General apart and the Spy near the General, beats uniform setups on a per-seed paired contrast with the 95% interval above zero for greedy, rollout and expectimax:{"samples":16,"depth":2} under the training rules, and the gain is larger for the players that attack early. The structured source keeps more than 3 bits of entropy on the Flag's square, and a null cell (uniform against uniform on the same seeds) has an interval covering zero. Exploratory motivation, not evidence: on 1,200 unregistered games a front-row Flag lost 61.5% of sides against 32.7% on the back row.

Result: supported on all three policies

The sampler is Setup::structured in the engine (constructive placement, sixteen bounded retries, uniform fallback; 10,000-sample property tests cover every rule, determinism and stream separation), the setup parameter is one WithSetup wrapper applied in armies_search::policy and listed in every catalog schema, and the rules table lives in the server's docs/setups.md. Smoke runs of four games per cell (throwaway seed 900) verified the plumbing before registration: the structured contestant's Flag sat in the back two rows and paired seeds shared its setup across seats. Smokes are not evidence.

Four registrations ran under the training rules, 400 paired games each on seeds 0 to 199, --threads 8, each cell alone on the machine (report):

  • greedy: structured 71.5% effective (0.669 to 0.757), contrast 0.430 (0.339 to 0.521). Record
  • rollout: structured 81.5% (0.774 to 0.850), contrast 0.630 (0.551 to 0.709). Record
  • expectimax sixteen worlds depth 2: structured 83.5% (0.795 to 0.868), contrast 0.670 (0.589 to 0.751). Record
  • null, uniform against uniform: 48.4% (0.435 to 0.533), contrast minus 0.033 (minus 0.143 to 0.078), covering zero as registered. Record

The decision rule is met for all three policies. Diversity over 10,000 samples per source (setupstats binary, scripts/setup_stats.py): Flag-square entropy 4.14 bits structured against 5.32 uniform (the rule asks for 3); a Flag with no adjacent Bomb in 0.3% of structured setups against 57.1% of uniform ones. Early endings: the null cell ended within 20 plies in 7.2% of games, split evenly; the treatment cells in 4.0%, 7.2% and 6.2%, and all seventy of those games were lost by the uniform side. The brief expected the largest gain for the early attackers; the measured gradient runs the other way (greedy 0.43, rollout 0.63, expectimax 0.67), which the report reads as search converting a setup edge more reliably.

Deviations and failures: none against the brief. The diversity stats use the small arena binary the brief allows, because the Python bindings do not expose setup sources. The cells were re-run twice for provenance: once after the server commit, and again after rebasing over the merged kluss branch (the registry conflict resolved by wrapping its arm with the same setup parameter). Every re-run reproduced the game outcomes exactly; only wall-clock timings moved, and the records say which runs shared the machine. The pre-existing tests/training/test_runlog.py::test_resume_equality_cpu flake (residual CPU nondeterminism documented yesterday by the run-layout study) failed once on this worktree before any change of mine; unrelated to this study. Whether structured becomes the registry default is left to the user, as the brief instructs.

Curated games: seed 0 from both seats of each treatment cell under records/games/structured-v-uniform/ (6 records, indexed and replayed). The compiled armies Python module was not rebuilt; no server/crates/python change.

Attribution: baseten/moonshotai/Kimi-K3 through opencode.

The scaled run is registered

Hypothesis, stated before any measurement: of the three candidates (the small preset, a medium one at embedding 256 and 8 layers over 1,536 environments, and the paper's per-GPU numbers), the largest whose iteration is under three minutes is the one the multi-day run takes, and after 48 hours its EMA move network beats greedy on 200 paired games, after 96 hours expectimax at sixteen worlds and depth two, with the Elo-against-time curve the result either way. This is study ataraxos/scaled-run, theory T5 of the program; the training run itself is launched by the user, never by this agent.

Sizing, measured under the GPU lock on 2026-09-19 with scripts/size_presets.py, three iterations per candidate from scratch, under the day's load (load average 2.4 to 9.9 of 32 threads): small 31.5 s per iteration (25.5, 32.6, 36.4) at 5.96 GiB and 10,350 collection steps per second; medium 76.4 s (63.7, 72.1, 93.4) at 9.51 GiB and 5,970 steps per second; large, the paper's numbers, 135.4 s (95.2, 168.0, 143.1) at 12.14 GiB and 3,900 steps per second. All three sit under three minutes, so the largest qualifies and PRESETS["scaled"] takes the paper's per-GPU numbers with the measurement in its docstring. At that pace the run reaches about 1,280 iterations by hour 48.

The draw rule is confirmed as the paper's, not changed: the training rules preset carries exactly Appendix D Table 17 (a draw after 100 consecutive battleless moves and after 4,000 moves, Rules::TRAINING in crates/engine/src/state.rs), and the advantage filter is on (top quantile 0.75 with a 0.01 magnitude floor). The smoke run's battleless stalling reads as an early-training artefact, so no change to the objective is proposed; the draw share is logged per iteration and is a first-class diagnostic beside entropy and clip rate (a share above half sustained is the registered disproof's second clause).

Both milestones are registered and nothing runs yet: the 48-hour match against greedy and the 96-hour match against expectimax, 200 paired games each (both seats per seed, matched setups, seeds 2000 to 2099, training rules), decided by the per-seed contrast with its 95% interval above zero. scripts/milestone.sh RUN HOUR OPPONENT finds the checkpoint whose run wall clock is nearest the hour, plays the registered games, writes runs/<uuid>/ output and files the record; it reads the registration, so the match cannot drift from it, and its --smoke mode plays throwaway games and files nothing.

The dry run (runs/scaled-dry, kept) proved the schedule end to end: launched through just launch as the systemd unit armies-scaled-dry, four iterations of the scaled preset with --evaluate-every 2 --eval-games 4 wrote checkpoints at every even iteration and evaluations against greedy and expectimax to evaluations.jsonl; stopped by signal mid-run it marked itself stopped; resumed, it finished, and a second resume with the schedule flags repeated evaluated again at iteration 6. Six iterations took 868 s of run wall clock at 133 to 180 s per iteration, draw share 0.61 to 0.67 early in training, and the expectimax evaluations ran through the new Python search bridge. The milestone smokes against both registrations played four throwaway paired games each and filed nothing.

Failures and deviations, all recorded: the run-layout study's CPU resume-equality test flaked before any of my changes (a policy_loss delta of 1.1e-5 against its 1e-5 tolerance, while training/README.md documents up to 5e-4 of per-process CPU noise); its float tolerance is widened to the documented bound in a separate commit. The Python bindings could not play expectimax, which the brief's launch and milestone commands need, so armies.RustPolicy now builds from the full policy registry (armies_search::policy, a two-line server change) and the shared armies module was rebuilt; the learned-belief agent works in the same crate and rebuilt the shared module twice during the session, so my GPU jobs re-verified the bridge before each launch and the merge will carry both changes. A resumed run keeps its hyperparameters but not its schedule flags, so the resume command must repeat --save-every and --evaluate-every; the first dry-run resume omitted them and silently evaluated nothing, which is how it was found. just watch -- -c watch.toml never worked (the recipe is positional, so -c became a recipe name); the recipe now passes any .toml argument as a config, and docs/monitoring.md says so. The dry-run's second resume had to be re-polled after a stale status.json made the first wait exit early; no training time was lost.

The commands the user runs are in the report: launch, watch, both milestones, and the resume.

Attribution: baseten/zai-org/GLM-5.3 through opencode.

The learned belief against the counting prior

Hypothesis, stated before any measurement (study subgame-solving/learned-belief, theory T3 of the program): a belief network trained on the small preset's self-play assigns a higher log-likelihood to the true types of hidden opponent pieces on held-out games between other players (the replay library's greedy, expectimax, rollout and random games) than the counting prior the observations already carry, and expectimax at 16 worlds and depth 2 that samples its worlds from the network beats the same expectimax sampling from the prior on 200 paired games under the training rules. If only the match holds, the belief fits the searcher's own style; if only the likelihood holds, the search does not use what the belief knows. Both outcomes are filed. The disproof is no gain in either measure.

Method, fixed in advance: the move network comes from thirty small preset iterations from seed 0; the belief network trains on its self-play (400 collection steps over 256 environments, 2 epochs). The likelihood measurement replays every game in the replay library through the engine and scores, at every ply of the side to move, the log probability of every hidden opponent piece's true type under the counting prior's belief planes and under the network's marginal estimated from 64 sampled assignments, floored at 1/128 so a zero count is not minus infinity. The match plays 200 paired games, both seats, the same two setups drawn per seed, the candidate sampling 16 assignments from the network per move and the control sampling from the prior, both searching depth 2. The decision rule is the per-seed contrast with a 95% interval above zero.

A sampling bridge is needed first: the Python bindings gain a Searcher wrapping the expectimax search, seeded for determinism, that either samples worlds from the counting prior or searches worlds handed to it as hidden-type assignments. Positions from games the belief policy itself played are not held out anywhere in this measurement; the replay library holds none of its games.

Result

Both measurements are negative (report, record, registration d72d5305). The held-out likelihood, on the library's 64 games, 10,588 positions and 270,223 hidden pieces: the counting prior 2.096 nats per piece and 24.2% top-1, the network 2.293 and 21.4%, worse in every phase and for every acting player. The match on seeds 0 to 99 from both seats: expectimax with belief-sampled worlds 90 wins, 1 draw, 109 losses (45.25%, interval 38.5% to 52.2%) against prior-sampled expectimax, per-seed contrast minus 0.095 (minus 0.217 to 0.027), 196 of 200 games ending by Flag capture. The decision rule is not met; the interval overlaps zero with a negative point estimate, which is inconclusive, not equivalence. The belief side cost 52.9 ms per move against the control's 4.3, both measured while other agents used the machine. On its own self-play the network trained to 1.62 nats per piece, so it fitted a policy thirty iterations old rather than a transferable belief. The study closes at this rung; the retest belongs to the scaled run's checkpoint, and the bridge (the Searcher class, a search-crate test that the sampled and the given-worlds entry points agree bit for bit) is on main for it.

Failures and deviations. The small-preset training crashed at iteration 21 with an illegal action: the setup network sampled a legal setup whose frontier row is Bombs and the Flag with the movables facing lakes, the side to move had no legal move at ply 0, and the engine ends a no-move game only after a move is applied, so the collector stepped an empty legal mask and went uniform over all 10,000 actions. The crash was reproducible from the iteration-20 checkpoint; the fix filters such setups out of the self-play pool (first_mover_can_move in armies_train.selfplay, with tests pinning the engine behaviour), the run resumed from the checkpoint, and the trajectory from iteration 21 differs from an unguarded uninterrupted run. BeliefNet.sample had a second latent bug this study tripped on: decoding in row-major order could spend its movable types before reaching squares that have moved, emptying the whole feasibility mask and degenerating the softmax to uniform, which the engine's assignment validation rejected; the sampler now keeps a lookahead so a completion always exists (armies_train.networks, test tests/training/test_belief_sample.py). The shared venv armies module was overwritten twice by other agents' just build-env runs while this study used it, so every Python here ran against a private install of the module built from this worktree (.armies-module, on PYTHONPATH); the module was also rebuilt once with just build-env as the brief asks. The optional per-game GameRecordDto records were not written. The first launch of the training died to a tool timeout after two minutes; it and all later GPU jobs ran detached with setsid under the lock.

The training log: 30 small-preset iterations at about 10,300 steps per second and 42 seconds each (the throughput agent's attention fix), the belief network 6,394,124 parameters on 102,400 positions in 2 epochs.

Attribution: baseten/zai-org/GLM-5.3 through opencode.