# Il Labirinto del Dojo — board format, rules engine, simulator ## Goal and scope A small tabletop game (1–2 h, target 4 players, playable 3–8), gifted to a friend. Logic and balance first; graphics and cosmetics are out of scope for now. Deliverables: 1. `RULES.md` — the rulebook (English while iterating; Italian final edition produced separately). 2. A text-based board format, with parser, full-board generator and validator. 3. A first playable board. 4. A rules engine + bots + Monte Carlo simulator to validate balance and pacing, and in particular to choose between rule variants A and B (B dropped, D28; now the Final Exam options, D29). ## Decisions log | # | Decision | Why | |---|---|---| | D1 | Kumite loser loses 1 belt (−3), never below 0 | Developer requirement. | | D2 | "Punching up": belts change only on equal-belt fights or upsets | Prevents leader farming weak players; built-in catch-up. | | D3 | Kumite: 1d6 each, higher belt +1 per belt of gap, capped at +2 | Uncapped d6 bonus made fights predetermined (gap 5 = 100%). | | D4 | **Senshu rule**: ties go to the attacker | WKF-inspired, one rule without exceptions, makes attacking slightly favourable so fights happen. | | D5 | Kumite triggers on **crossing** (entering an occupied square), always forced; winning attacker continues moving; chains allowed | Developer requirement; route choice gives the agency. | | D6 | Kumite loser is sent to own Start (corner Safe Room) | Developer requirement. | | D7 | Corner Safe Rooms: 3×3, 2 exits, no Kumite/effects/targeting, stacking allowed | Prevents spawn camping / spawn lock. | | D8 | Movement 2d6, exact count, orthogonal, no re-entering a square in the same move | Developer requirement + clarity. | | D9 | Most squares carry an effect from a small vocabulary (7 codes); ~30–40% Tatami | Developer requirement; limits reading load and analysis paralysis. | | D10 | Effects trigger only when stopping on a square | Avoids multiple triggers per move. | | D11 | Nebbia works like Monopoly Chance: the square stays active | Developer requirement. | | D12 | Progress track 0–17, 3 stripes per belt; squares ±1, Kumite ±3 | Fine-grained board income without breaking pacing. Physical tracking method decided later. | | D13 | Nebbia deck is mostly movement/interaction/Tecniche, very few flat stripes | Prevents "stripe farming" that would make players avoid fights. | | D14 | Tecnica cards: hold max 1, played after dice, attacker first | Converts Nebbia luck into a reason to fight. | | D15 | Passo Falso returns to the start of the current move; skip-turn allowed only if rare (1 card) | Developer requirement. | | D16 | Tunnel: when stopping on it you may teleport; movement is over; occupied exit starts a Kumite | Developer confirmed. | | D17 | Meditazione: pass-through; ending on it forces one extra step | Developer rule. | | D18 | Central Dojo is one logical space, several entrances, Brown only, no exact count to enter | Avoids end-game "need exactly a 3" frustration. | | D19 | Final Exam: highest opponent defends; challenger wins → game; challenger loses → −3 and Start; defender gains nothing | Avoids the top-two flip-flop. Brown's extra stripes protect against the board, not the exam. | | D20 | Two rule variants to be decided by simulation: **A** (no Brown cap, higher-beats-lower 0) vs **B** (squares/cards cannot move 14→15, higher-beats-lower +1) | Neither is clearly better a priori. *Superseded by D28.* | | D21 | Board 16×16, 4×4 Dojo in the centre, authored as one 8×8 quadrant rotated 3× (C4 symmetry) | Fairness by construction. Small asymmetries may be wanted later (see Q2). | | D22 | Tooling in Python, environment managed with **uv** | Developer choice. | | D23 | Rules drafted in English; Italian final edition generated separately | Developer choice. | | D24 | Board format: see `board/FORMAT.md`. Tunnel pairing across quadrants via `quadrant_offset` in `board/legend.toml` (cell `a` in quadrant k ↔ cell `b` in quadrant k+o) | Every tunnel cell keeps exactly one partner under rotation. | | D25 | Only the Tunnel square is optional. Sfida (square and card), Irimi and Tunnel Segreto are mandatory; every Nebbia card must be resolved | Sfida prevents stalling; Nebbia cards are chances that can also turn out badly. | | D26 | Tecnica cards are secret: drawn face down, others only know *whether* you hold one, a replaced Tecnica is discarded face down, revealed only when played | Hidden information and bluffing. Bots must not see opponents' Tecnica identity (Phase 4). | | D27 | Edge rules (RULES.md v0.5, inline + FAQ): boxed-in → move as far as possible (safeguard; boards have no dead ends); Uke-saved attacker stops on its previous square, turn ends, no square effect; extra-step rule for ending on any unfightable pawn; tunnel exits with a meditating pawn unusable; meditating players cannot defend the Exam; Tecnica window after both dice, attacker first; relocations never fight or trigger squares; Tsugi-ashi has its own origin; becoming Brown mid-move opens the Dojo | Developer confirmed all ten. | | D28 | Variant B dropped (developer, after the Phase 5 baseline): no Brown cap; a higher belt beating a lower one gains nothing. Removed from engine, bots, simulator, RULES.md (v0.6) | B made games shorter (13 vs 18 rounds) instead of adding an endgame challenge; the cap barely bit and the +1 applied to every such win. Too complex for what it gave. | | D29 | Final Exam options under test: **a** ties go to the defender; **c** the defender fights as a Black belt (defender +1 vs a Brown challenger); **d** best of 3. Combinable (`--exam ac`). Option b (no belt bonus in the Exam) not pursued. Developer favours c | Baseline pass rate ~80%: the defender is usually 1–2 belts below the Brown challenger, who also gets Senshu. | | D30 | Simulator reporting: income per source as gains and losses separately (Kumite is zero-sum, so its net hid the flow); standings sampled at the end of every turn; ten one-sidedness measures (see 5.4) | Developer request after the Phase 5 baseline. | | D31 | Final Exam rule: **the defender fights as a Black belt** (option c; RULES.md v0.7). Options a (ties to the defender) and d (best of 3) stay as simulator switches only; `--exam` lists the options in force and defaults to `c` | c gave a 44% pass rate, more distinct challengers (1.58/game) and closer endings; d alone raised the pass rate to 91%. | | D32 | Phase 6 targets confirmed by the developer as a starting point (table in Phase 6) | Q5. | | D33 | Fatica fix: test **option 2 first** — a chance square (stop on it, roll 1d6: 1–2 lose a stripe, 3–6 gain one) replacing Fatica and some Allenamento; then **option 1 must also be tested** — Fatica costs a stripe when walked through as well as when stopped on (breaks D10 for that square only). Options 3 (fewer paths: board rework) and 4 (fewer / once-per-room Allenamento) kept in reserve. The casual bot (6.0.1) is built regardless | Q6. Any on-stop penalty is avoidable when a move reaches ~24 end squares; the chance outcome cannot be dodged. Developer likes 1 and 2. | ## Phases ### Phase 0 — Rules consolidation - [x] 0.1 Write `RULES.md` v0.4 with variants A/B marked. - [x] 0.2 `RULES.md` v0.5: mandatory effects, Tecnica secrecy, edge rules and FAQ (D25–D27). ### Phase 1 — Project scaffold and board format - [x] 1.1 `uv init` project (package `dojo`), Python version pinned via uv; dev deps: pytest, ruff. No runtime deps (legend in TOML, parsed with stdlib `tomllib`). - [x] 1.2 `board/FORMAT.md` — the format specification: - [x] ASCII grid: `+` corners, `|` vertical walls, `---` horizontal walls, 3-char cells with a centred code. - [x] Cell codes: `S` safe room, `D` dojo, `.` `A` `F` `?` `P` `K` (Allenamento/Fatica were `+`/`-`, changed so no code looks like a wall), digits = tunnel pair ids. - [x] Outer border must be fully walled. - [x] Dojo entrance = open edge between a `D` cell and a non-`D` cell. - [x] Cross-quadrant tunnel links declared in `board/legend.toml`. - [x] 1.3 Parser: `quadrant.txt` → cell grid + wall set. - [x] 1.4 Generator: rotate the quadrant 90°/180°/270° around the board centre → full 16×16 model; write `board/board.txt` (generated, never hand-edited). - [x] 1.5 Graph model: cells as nodes, open edges as links. The Dojo is handled as one cell set (impassable, entrances = open D/non-D edges); treating it as a single space for movement lands in the rules engine (3.3). - [x] 1.6 Validator: - [x] every non-Dojo square reachable from every Safe Room; - [x] each Safe Room has exactly 2 exits, on different sides; - [x] no Safe Room lies on a shortest path between two other points (not a shortcut); - [x] every tunnel id has exactly one partner; - [x] Dojo has ≥1 entrance per quadrant; - [x] report: square-type mix and Tatami %, shortest distance from each start to the Dojo. - [x] 1.7 CLI: `uv run dojo board validate`, `uv run dojo board render`. - [x] 1.8 Unit tests for parser, rotation, validator. ### Phase 2 — First board - [x] 2.1 Draft the first quadrant (`board/quadrant.txt`), following D7, D9, D18. - [x] 2.2 Validate and iterate until clean. - [x] 2.3 Developer review of the layout (approved as draft 1). ### Phase 3 — Rules engine - [x] 3.1 Game state: pawns, track positions, hands, deck/discard, meditation status, turn order. - [x] 3.2 Rule configuration object: variant A/B, Kiai value, stripes per belt, starting position, player count. - [x] 3.3 Movement enumeration: all legal exact-length self-avoiding walks, with Kumite on crossing, chain continuation, Dojo entry with ≥1 step left, Meditazione extra step. - [x] 3.4 Kumite resolution: gap bonus cap, Senshu, Tecnica timing, consequences table per variant, Uke placement. - [x] 3.5 Square effects and Nebbia cards (all 13 card types). - [x] 3.6 Final Exam. - [x] 3.7 Unit tests covering every edge rule in `RULES.md` (`tests/test_engine.py`, `tests/test_movement.py`; mutation-checked: breaking Senshu, bonus cap, chain continuation, Meditazione step, variant B cap, Safe Room or meditator immunity each fails tests). - Code: `src/dojo/rules.py` (config, variants, deck), `movement.py` (walks), `engine.py` (Game, Agent protocol, Randomness), `events.py` (event log for metrics). ### Phase 4 — Bots - [x] 4.0 Hidden information: agents receive a `GameView` snapshot (`Game.view(player)`), never the `Game`: opponents' Tecnica shows only as `holds_tecnica`; the event log (which records `CardDrawn`) is not visible to agents (D26). - [x] 4.1 Random bot (baseline): `RandomAgent(rng)` in `src/dojo/bots.py`. - [x] 4.2 Heuristic bot: `HeuristicAgent(profile)`. Value = track position; once Brown, plus `DOJO_PULL` (0.3) per step closer to the Dojo. Scores each offered path as the expected value change over the chain of Kumite (exact 1d6-vs-1d6 odds with belt bonus and Senshu, ±0.1 per Tecnica held), then the stop square's effect (Nebbia worth 0.2, Sfida = best target, Tunnel = better of staying/exit). Exam = 100 × pass chance. Tecnica played only when losing (Kiai only if it turns the fight). Known simplifications: Meditazione extra step, Passo Falso origin after a chain, opponents' replies. - [x] 4.3 Profiles (`PROFILES`): losses weighted by `risk`, opponents' value changes by `rivalry`, Tecnica keep order. balanced (1.0, 0.5, Kiai>Kaeshi>Uke), farmer (2.5, 0.2, Uke first), aggressive (0.5, 1.0). - Smoke run (60 seeds, real board, 1 heuristic vs 3 random): heuristic wins 58–60/60 for every profile and both variants; ~20–22 rounds; ~1,300–1,450 turns/s. Four heuristic bots: ~18.7 rounds. Finding to check in Phase 5/6: attacking a higher belt risks nothing for a non-Brown player (losing costs 0 stripes; Start is no worse than elsewhere), so every profile attacks upward. ### Phase 5 — Simulator and metrics - [x] 5.1 Monte Carlo runner over a config matrix; seeded and reproducible. `src/dojo/sim.py`: `Scenario` (variant × players × Kiai bonus × start position × lineup), `matrix`, `run(files, scenarios, games, seed, jobs)`. Game `i` uses seed `base + i` in every scenario (same dice across scenarios); the lineup rotates one seat per game; process pool, results independent of `jobs`. - [x] 5.2 Metrics (`src/dojo/metrics.py`, computed from the event log; Kumite counts exclude the Exam): - [x] game length in rounds and estimated minutes (45 s per turn + 30 s per Kumite); - [x] Kumite per round, upset rate, higher-beats-lower rate, longest chain per game (Kumite attacked by the active player in one turn); - [x] win rate by seat and by bot profile (per seat occupied); - [x] exam attempts per game, exam success rate; - [x] turns started on 14, longest streak (variant B deadlock check); - [x] net stripe income per turn: squares vs cards vs Kumite vs Exam; square income in the player's own quadrant vs elsewhere (home farming); - [x] lead changes per game (strict leader), final spread. - [x] 5.3 Report output: `uv run dojo sim` prints a text summary per scenario; `--csv` writes one row per game (`src/dojo/report.py`). Commands in `README.md`. - Baseline (1,000 games per variant, 4 players, lineup balanced/farmer/aggressive, seed 0), for Phase 6: | Metric | A | B | |---|---|---| | Rounds mean (p10–p90) | 18.4 (14–23) | 13.1 (9–17) | | Minutes mean (p10–p90) | 66 (50–85) | 46 (33–62) | | Kumite per round | 1.25 | 1.16 | | Upsets / higher beats lower | 29% / 55% | 27% / 52% | | Exam attempts per game, pass rate | 1.26, 79% | 1.22, 82% | | Turns on 14 per game, longest streak | 1.26, 4 | 1.73, 7 | | Income per turn: square (home / away), card, Kumite | +0.52 (0.38 / 0.14), 0.00, +0.04 | +0.54 (0.39 / 0.14), 0.00, +0.22 | | Lead changes, final spread | 5.6, 9.3 | 3.5, 11.5 | | Wins by seat | 23–27% | 23–27% | - [x] 5.4 Follow-up to the baseline review (D28–D30): - [x] remove variant B; `--variant` replaced by `--exam` (Exam options, `RulesConfig.exam_ties_to_defender / exam_defender_black / exam_rounds`); "turns on 14" metric removed with it; - [x] income per source as gains / losses; - [x] one-sidedness measures, sampled at turn ends: (1) gap 1st–2nd (mean per game) and winner margin over the best rival at the end; (2) share of turns the winner led; (3) halfway leader wins; (4) point of the game of the last lead change; (5) rivals within one belt of the winner at the end; (6) share of player-turns more than two belts behind the leader, and the worst player's share; (7) rank swaps between any two players; (8) Kumite lost by the leader; (9) distinct Exam challengers; (10) winner's own turns to reach Brown. - After 5.4 (300 games per option, 4 players, lineup balanced/farmer/aggressive, seed 0): | Metric | none | a | c | d | |---|---|---|---|---| | Rounds mean (p10–p90) | 18.5 (14–23) | 19.3 (14–25) | 21.9 (16–29) | 18.0 (14–22) | | Minutes mean | 67 | 70 | 80 | 66 | | Exam pass rate, attempts per game | 81%, 1.24 | 67%, 1.50 | 44%, 2.28 | 91%, 1.10 | | Distinct challengers per game | 1.13 | 1.24 | 1.58 | 1.06 | | Winner margin at the end | 4.7 | 4.6 | 3.9 | 4.8 | | Halfway leader wins | 40% | 41% | 40% | 41% | | Rivals within one belt at the end | 0.32 | 0.37 | 0.62 | 0.30 | | Player-turns out of contention | 22% | 22% | 21% | 22% | Also measured: Kumite moves +0.44 / −0.40 stripes per turn; heuristic bots almost never stop on Fatica (square losses 0.0002 per turn vs 0.08 for random bots): with 2d6 and many paths an Allenamento is nearly always reachable. Seat 3 (last in turn order) won 28–31% in every option. ### Phase 6 — Balance experiments **Method** (applies to every step): - Runs use `uv run dojo sim`. Default 1,000 games per scenario (a 25% win rate is then known to about ±1.4 pp); 4,000 games where seat effects are measured (about ±0.7 pp). - Compared scenarios share seeds (`--seed 0`), so differences come from the rules, not the dice. - Every conclusion is checked on at least two lineups (mixed `balanced,farmer,aggressive`, and one same-style lineup), because results depend on bot quality. - One change at a time. When a change is adopted, re-run the 6.1 baseline so later steps start from the current rules, and log the decision (D-number) here and in `RULES.md`. - Results go in this file as a table per step (command line, scenario, key measures). **Next action:** 6.0 (tooling), then 6.1. Everything up to D33 and RULES.md v0.7 is committed; commit only when the developer asks. **Targets** (confirmed by the developer as a starting point, D32): | Measure | Target | |---|---| | Minutes, 4 players | mean 60–90, p90 ≤ 110 | | Exam pass rate | 35–55% | | Halfway leader wins (4 players) | 35–55% | | Player-turns out of contention | ≤ 20%; worst player mean ≤ 35% | | Rivals within one belt of the winner at the end | ≥ 0.5 | | Win rate by seat, 4 players | each 25% ± 3 pp | | Win rate by bot style (mixed lineup) | spread ≤ 6 pp | | Games finished within 200 rounds | 100%, every lineup | **Steps:** - [ ] 6.0 Tooling needed by the experiments - [ ] 6.0.1 "Casual" bot: the heuristic bot scoring only a small random sample of the offered moves (sample size is a parameter, default 6), as a stand-in for a human who cannot compare ~24 end squares every move. Added to `BOTS` as `casual`. - [ ] 6.0.2 `--summary-csv PATH`: one row per scenario with the summary measures, to compare scenarios side by side. - [ ] 6.0.3 Seat breakdown in the summary: per seat, mean final track, Exam attempts, and share of games where that seat defended the Exam (for 6.2). - [ ] 6.0.4 Square reachability measure: share of moves where an Allenamento / Fatica end square is reachable, and mean distinct end squares per move (the Phase 6 probe, made a metric; for 6.4). - [ ] 6.1 Baseline on rules v0.7 (Exam c): 4 players, 1,000 games, lineups mixed / `balanced` / `casual` / `random`. Record the table; check it against the targets. - [ ] 6.2 Seat order (monitor; developer concerned): 4,000 games, 4 players, lineups mixed and `random`; 2,000 games at 3 and 6 players. If a seat is outside 25% ± 3 pp, diagnose with the 6.0.3 breakdown (hypotheses: moving last means seeing everyone else move first; Exam defender selection; Sensei / Peso cards) and bring candidate fixes to the developer. No fix without agreement. - [ ] 6.3 Style dynamics: same-style lineups (`farmer`, `aggressive`, `balanced`, `casual`) and one-against-three (`aggressive,farmer,farmer,farmer`, `farmer,aggressive,aggressive,aggressive`, `balanced,farmer,farmer,farmer`). Watch: all-farmer stalling (length, games finished, Kumite per round), a dominant style, time out of contention. - [ ] 6.4 Fatica and the square economy (D33). Each candidate is a board draft in `board/drafts/` (new square codes need `Square`, `board/FORMAT.md`, validator stats and `RULES.md` entries) and/or a `RulesConfig` switch, compared with the 6.1 baseline on square gains / losses per turn, reachability (6.0.4), game length, style win rates, casual vs heuristic gap: - [ ] 6.4.1 Option 2, chance square: new code (proposal `C`, name to choose with the developer, e.g. "Sorte" / "Randori") — on stop roll 1d6, 1–2 → −1 stripe, 3–6 → +1. Draft: replace every Fatica and some Allenamento with it (start: all F plus about a third of A, keep the square mix inside the validator's Tatami range). Bot: value = expected change (+1/3 stripe, risk-weighted: gain 2/3, loss 1/3). Try variations of the split (e.g. 1–3 / 4–6) if income is off. - [ ] 6.4.2 Option 1, Fatica on crossing: `RulesConfig.fatica_on_crossing` — each Fatica square entered during a move costs 1 stripe, **1 in total per square whether or not the move stops there** (developer, D33); stopping on it adds nothing more. Engine: apply in `_walk` per square entered (not on teleports / relocations, which never walk). Bot: count Fatica crossed in `_path_score`. Likely needs fewer Fatica squares; test on the current board and on a draft with fewer F. - [ ] 6.4.3 Compare 6.4.1, 6.4.2 and both together; bring the table to the developer for a decision (then `RULES.md` and D-number). - [ ] 6.5 Home farming: after 6.4, if the own-quadrant share of square income is still above 60% (baseline: 71%), move Allenamento squares away from the Safe Rooms in a board draft (`dojo sim --quadrant board/drafts/...`) and compare. The board must still pass `dojo board validate`. - [ ] 6.6 Kiai +2 vs +1: 1,000 games each on two lineups. Watch the Exam pass rate, upset rate and how often a Tecnica turns a fight. - [ ] 6.7 Player counts: 3 / 4 / 6 / 8 players, start 0; plus start 3 at 6 and 8 players. Minutes per count; decide whether 6+ players start on Yellow. - [ ] 6.8 Consolidation: the final configuration at 3 / 4 / 6 / 8 players with all lineups, 2,000 games each; every target checked; `RULES.md` v0.8 with the decisions; open items moved to Phase 7. ### Phase 7 — Finalisation (later) - [ ] 7.1 Freeze rules. - [ ] 7.2 Italian edition. - [ ] 7.3 Physical tracking method and components; printable board. - [ ] Tunnel pairs need unique labels on the printed board (the text format repeats each digit in all 4 quadrants). ## Questions ### Resolved - Tie rule → Senshu (attacker wins). - Several opponents on one square → impossible (loser is teleported); only exception handled by the Meditazione extra-step rule. - Exam loss → −3 and Start; defender gains nothing. - Tunnel → teleport optional on stop, movement over. - Optional vs mandatory effects → D25. - Engine edge rules → D27. - Sfida squares → keep, balance via low count. - Il Peso della Cintura → keep (1 card). - Player count → focus 4, support 3–8. - Tests → the agent runs the suite (`uv run pytest`), plus `ruff check`, `ruff format --check`, `pyright`. - Minutes estimate → 45 s per turn, +30 s per Kumite. - Q4 Target net income per turn → current pace is fine: A already runs ~66 min (p90 ~85); the winner's pace is measured instead of a table average. - Variant A vs B → B dropped (D28). - Final Exam → the defender fights as a Black belt (D31). - Q5 Phase 6 targets → confirmed as a starting point (D32). - Q6 Fatica avoidable (bots stop on it once in ~4,000 turns; ~24 distinct end squares per move; Allenamento reachable on 79% of moves, Fatica never forced) → test the chance square first, then Fatica on crossing (D33). - Safe Room placement → the owner places the pawn on any square of their room (setup and when sent to Start). ### Open - Q7 Casual bot sample size: 6 offered moves is a guess at human play; revisit if 6.1 shows it is far weaker or stronger than the heuristic bot. - Q2 Board asymmetry: keep the quadrant as source of truth, or later switch to the full board as source of truth with a symmetry *report* (warnings) instead of generation? *(Deferred by the developer.)* ### Risks - Movement enumeration: self-avoiding walks of length up to 12 can explode in open rooms. Keep rooms small; prune by outcome (destination + ordered opponents crossed + Dojo entry). - Engine speed ~1,400 turns/s (random and heuristic bots, real board). If Phase 5 needs more: precompute neighbour lists in `Board`. - Simulation conclusions depend on bot quality; compare several profiles before trusting a result. - Board draft 1 puts 4 Allenamento in each 3x3 room next to the Safe Room: possible "home farming" (see the Phase 2 review). Measured (Phase 5 baseline): square income is +0.38/turn in the own quadrant vs +0.14/turn elsewhere; 6.5 decides whether that is a problem. ## External documentation - uv — https://docs.astral.sh/uv/ — project init, dependency and Python version management. - Python `tomllib` — https://docs.python.org/3/library/tomllib.html — stdlib TOML parser for `legend.toml`. - pytest — https://docs.pytest.org/ — unit tests. - Ruff — https://docs.astral.sh/ruff/ — lint and format. - WKF Kumite competition rules (wkf.net, "Rules") — source of the Senshu concept.