Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 67 additions & 25 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -919,37 +919,79 @@ doesn't train at all, so the campaigns stay `internal` to the Lab (the contract
**Net −284 lines.** If a contributor needs to (re)produce a checkpoint, that's a dev-side Lab/Console run + an LFS
commit, documented in `ADDING_A_GAME.md`.

## M27 — Snake: spatial observation + anti-self-trap shield *(branch `snake-spatial-obs`, stacked on M25/M26)* ⏳

**Problem.** "Improve the Snake AI" (it expects >100 food). The shipped net scored **~21 food on 12×12** and was
stuck there structurally, not for lack of training: (1) the observation was **12 local features** (danger one cell
out + a food compass) — the agent was *blind to its own body*, so a long snake inevitably trapped itself; (2)
`MaxEpisodeSteps = 1000` was a flat truncation that *hard-capped* how much food was even reachable per episode.

**Fixes (this milestone).** A grid-size-invariant **egocentric observation** — a 9×9 obstacle+food patch centred on
the head + food/tail direction & distance + heading + length + a **flood-fill of reachable open space per move**
(the anti-trap signal a fixed window can't give) — and a **starvation episode limit** (`StarveLimit ≈ 2·cells` with no
food) replacing the flat cap. Plus an opt-in **`safeMask`**: the action mask also forbids moves that flood-fill into
a region too small for the body (a reactive 1-ply shield), used in training and the live web demo.

**Experiment ledger (food@12, 50-ep eval unless noted; all >> the old 21):**
## M27 — Snake: stronger AI via better inputs + net-guided search ⏳ SEARCH SHIPPING (100 stretch open)

**Goal.** "Improve the Snake AI" — the user wants it toward **>100 food on the 12×12 demo grid** (max 141). The
original shipped net plateaued at **~21 food**, and that was *structural*, not under-training.

**Root causes found + fixed (all SHIPPED to master via #9, hotfixed by #10):**
1. **Blind observation** — was 12 local features (danger one cell out + food compass); the agent couldn't see its
own body, so a long snake trapped itself. → reworked the observation (below).
2. **Flat `MaxEpisodeSteps=1000` truncation** hard-capped reachable food. → replaced with a board-scaled
**starvation limit** (`StarveLimit ≈ 2·cells` without food) + a large absolute ceiling.
3. **Self-trapping deaths** capped the score. → opt-in **`safeMask`**: the action mask also forbids moves that
flood-fill into a region smaller than the body (reactive 1-ply shield), used in training + the live demo.
4. Reusable training mechanics added to the harness: `DqnTrainer.warmStart` (continue a deployable net-only
checkpoint), **save-best** (ship the best-eval net, not the noisy last), trainer internal-eval switched off in
the campaign (≈2× throughput), Lab knobs `--hidden/--gamma/--step-penalty/--safe-mask/--explore`.

**Experiment ledger (food@12 on 12×12, 50-ep eval unless noted; all ≫ the old 21):**
| config | food |
|---|---|
| egocentric obs, `[128,128]` | 39.9 |
| egocentric 9×9 patch obs, `[128,128]` | 39.9 |
| `[256,256]` (capacity) | 39.4 — capacity is *not* the bottleneck |
| + tail feature + γ0.995 | 43.1 |
| + step-penalty 0 + γ0.997 | 42.6 |
| same net + post-hoc shield | 46.6 |
| **shield-*trained*** (γ0.997, pen 0) | **52.3 peak / 50.7 @200-ep** |

Shipping the shield-trained net (**~50 food, ~2.4× the old 21**): `models/snake.dqn.ckpt` overwritten,
`SnakeController` live demo uses `safeMask`. **In progress:** a small→large **grid curriculum** (7×7 → 9×9 → 12×12,
warm-started) — train space-management where trapping is unavoidable-to-confront, then transfer up; may beat ~50.

**Honest ceiling.** ~50 is the **pure-learned (DQN + 1-ply shield)** plateau across capacity/feature/horizon/reward
experiments. Reliable **100** (filling ~70% of the board) needs **multi-ply planning/search** — the EfficientCube
pattern (learned net guiding a beam/lookahead, already in this repo) or a Hamiltonian planner — out of scope for
this learning-only milestone, recommended as the follow-up.
| **shield-trained** (γ0.997, pen 0) — **SHIPPED in #9** | **52.3 peak / 50.7 @200-ep** |
| 7×7 curriculum (old patch obs, stopped early) | climbed to 26.5/46 on 7×7, abandoned for the ray pivot |

**Shipped (master):** `models/snake.dqn.ckpt` = the shield-trained net (**~50 food, ~2.4× the old 21**); web live
demo uses `safeMask`. **#10 hotfix:** the live stream broke ("stream closed") because the persistent `/data` volume
kept the OLD 12-dim net vs the new obs — fixed by (a) seed now OVERWRITES shipped checkpoints (load-only web ⇒
`models/` is source of truth; this also means model updates finally propagate to the volume) and (b)
`SnakeModelService` rejects a net whose `InputSize != SnakeEnv.ObservationSize` (clean 503, not a crash). Redeploy
needed for prod; or delete `/data/snake.dqn.ckpt` + restart.

**Lean ray-cast observation (branch `snake-ray-obs`, off master + #10) — DONE.** The 9×9 patch was **myopic**
(±4 cells, 162 of 177 inputs mostly zeros). Replaced with **8-direction ray-cast vision** (CodeBullet-style, from
the user's cloned `C:\Repos\SnakeFusion`): per ray, food-on-line + 1/dist-to-body + 1/dist-to-wall = 24 inputs of
*long-range* awareness, + flood-fill (4) + food/tail bearing&dist (6) + heading (4) + length (1). **ObservationSize
177 → 39**, size-invariant. Trained fresh 12×12 (`--hidden 256,256 --gamma 0.997 --step-penalty 0 --safe-mask`):
peaked **food@12 ≈ 53 greedy** (vs the shipped 50.7) at ~240k steps, converging far faster than the patch obs. But
~53 confirmed the **reactive-DQN plateau is structural** — better inputs alone don't reach 100.

**Multi-ply search (the user's chosen path; the EfficientCube pattern applied to Snake) — DONE, shipping.**
Snake's dynamics are deterministic between food (the food RNG is in the env's saved state), so look-ahead is *exact*.
New `SnakeSearchAgent` (Environments/Snake): receding-horizon beam search that simulates every legal line on a
private env clone (`SaveState`/`RestoreState`), scores each leaf by **flood-fill survivability** (the new
`SnakeEnv.FreeSpaceAhead`, a hard self-trap penalty) + food eaten + the trained net's value + tiebreaks, and plays
the first move of the best line. Net-guided (the net is the leaf evaluator — the AlphaZero/EfficientCube idea), so
it honors the user's "really trained AI, not brute force" preference. Lab eval gained `--search --depth --beam
--w-* --starve`; `SnakeModelService` exposes the net; `SnakeController.Live` drives the demo via the planner.

**Search sweep (food@12 on 12×12; greedy baseline ≈ 50):**
| lever | finding |
|---|---|
| depth | **the lever** — 65 (d6) → 71 (d10) → **84/79 (d20)**; d28/d36 no reliable gain (beam misranks deep lines). d20 = sweet spot |
| beam width | wider **hurts** (d6: 65→59 at b64) — keeps greedy food-grabs that trap later |
| space weight | helps, saturates (d6 65→69; d16 72→79; flat by ~50–100) |
| net weight | **marginal** — net=0 ties net-on (search carries it); net forward skipped when weight 0 |
| starvation window | **not the cap** — 2→4→8 saturates at ~80; the cap is self-traps, not truncation |
| **SHIPPED: net-guided d20, beam 32, space 50** | **food@12 ≈ 78.6** (deployable net, 20-ep) — **greedy 50 → 78.6, old shipped 21 → 78.6 (3.7×)** |

**Shipped artifacts (branch `snake-ray-obs`, local commit — PR pending user OK):** lean `SnakeEnv` (39-dim obs +
`FreeSpaceAhead` + configurable `starveLimitCells`), `SnakeSearchAgent`/`SnakeSearchOptions`, Lab search flags,
`SnakeModelService.Net`, `SnakeController` net-guided look-ahead, `SnakeSearchAgentTests` (3) + updated env test;
`models/snake.dqn.ckpt` replaced with the lean-obs net (39-dim; the old 177-dim net is incompatible with this
branch, so net + `SnakeEnv` must ship together for the #10 `InputSize` guard).

**Honest ceiling.** ~50 was the reactive-DQN plateau; net-guided search lifts it to **~78** by guaranteeing the
snake doesn't walk into a box within ~20 plies. The residual gap to a clean **100** is occasional self-traps that
form *beyond* the horizon — the principled fix is a **tail-reachability** survival invariant (guarantee the head can
always reach its own tail → it can follow the tail indefinitely) and/or explicit Hamiltonian-style endgame play.
Offered as the next step; not yet built. Branch map: `master` = #9+#10. `snake-ray-obs` (current) = master +
eval-speedup + lean obs + search.

## Testing strategy (cross-cutting, from research)

Expand Down
11 changes: 11 additions & 0 deletions docs/PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,3 +557,14 @@ via `CampaignRunner`. Investigation showed the web's `EnsureModel` training was
committed to `models/` (Git LFS) and seeded at startup, so it never ran in production. So M26 instead made the web
**load-only**: training lives solely on the dev side (Lab campaigns / Console) and is committed; the web loads and
serves. The campaigns stay `internal` to the Lab (no shared library needed — the web doesn't train). See PLAN M26.

**Snake AI — stronger via better inputs + net-guided search (M27).** Goal: push the Snake demo toward >100 food on
12×12 (was ~21, structurally capped by a blind observation + a flat episode cap). Path: (1) reworked the observation
to a grid-size-invariant **8-direction ray-cast** (CodeBullet-style, long-range), added a starvation episode limit +
an opt-in flood-fill self-trap shield — a learned reactive DQN tops out at **~50 food**; (2) on the user's chosen
route, added **net-guided multi-ply look-ahead** (`SnakeSearchAgent`, the EfficientCube "search on a learned value
function" idea): exact deterministic simulation to ~20 plies, leaf-scored by flood-fill survivability + the trained
net's value, plays the best line. **Result: food@12 ≈ 78.6** (3.7× the old 21, ~1.6× the reactive net). The live
demo (`SnakeController`) now plays via the planner. The residual gap to a clean 100 is self-traps forming beyond the
horizon; the next step is a **tail-reachability** survival invariant / Hamiltonian endgame. Branch `snake-ray-obs`
(lean obs + search; `models/snake.dqn.ckpt` swapped to the 39-dim net). Authoritative detail in **PLAN M27**.
4 changes: 2 additions & 2 deletions models/snake.dqn.ckpt
Git LFS file not shown
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
</PropertyGroup>

<PropertyGroup Label="Package">
<Version>0.2.0</Version>
<Version>0.3.0</Version>
<Authors>Pieterjan De Clippel</Authors>
<Company>MintPlayer</Company>
<Description>Ready-made environments for MintPlayer.AI.ReinforcementLearning: a bit-for-bit faithful CartPole-v1 port, GridWorld, FrozenLake, 2048 (with an n-tuple TD learner, ~84% win rate), and Rush Hour with a BFS oracle, puzzle generator, and an imitation-learning + policy-guided A* toolkit that solves official expert boards optimally.</Description>
Expand Down
Loading
Loading