games.md (4195B)
Game demos
Four tabs in /ui/: platformer and puzzle room, each ruled (the model plays
from a briefing) and self-learning (SL — the model plays from neutral
observations and learns rules via /plan after failures). The SL loop is
game-agnostic: planner.js holds the trigger policy and wire format, and a
per-game PLAN_GOAL one-liner is the entire game-specific surface.
Platformer
Integer horizontal positions: run moves 1 unit, jump moves 5 units before
landing, each pit is 3 units wide; pits at [14,17), [31,34), [48,51), flag at
64, start at 3, four jumps for three pits. Vertical animation uses fractional
gravity internally; the model never sees it (web/game-rules.js owns
physics and prompt text). The prompt teaches "jump at the edge" without
exposing jump distance. Each pit has two valid takeoff positions:
| Position near first pit | Jump landing | Safe action |
|---|---|---|
| 10 | 15, inside pit | Run |
| 11 | 16, inside pit | Run |
| 12 | 17, right edge | Run or jump |
| 13 | 18, ground | Jump |
State text, most to least legible: Guided (adds a per-turn hint),
Unguided (prose counts: "4 ground spaces, 3 hole spaces, …"), Run-length
(-7 | #3 | -14 | … | [!]), ASCII (one symbolic row: - # * !). Terrain
behind the player and any action outcomes are omitted; counts start at the
next space.
Platformer SL (/ui/#self)
Same physics, inverted information policy: the observation carries only
neutral facts (player state, jumps remaining, terrain ahead — the payload
lines are byte-identical to the ruled modes) and the options are run,
jump, plus insufficient: "Insufficient evidence to decide". No goal line,
no rules, no hints.
Three events trigger /plan, a reasoning chat completion over the transcript
of completed actions (bare state + chosen action + outcome, capped at 24
turns):
insufficientargmax with p ≥ 0.99 (Planner.INSUFFICIENT_THRESHOLD). A weakerinsufficientargmax does not plan — the game takes the best real action and says so.- The player falls into a pit.
- Three jump requests in a row with no jumps remaining.
After planning, the level restarts with the learned rules injected at the top
of every observation; rules, transcript, and stats carry across restarts of
the same run (Reset clears everything). The planner writes the decision
model's entire prompt — facts and imperatives in the model's own voice, no
if-then branches — under one universal system prompt (PLAN_SYSTEM in
semif_api/llama.py); the goal reaches it only as the composed PLAN_GOAL,
and repeat failures attach the previous rules pre-indicted.
Puzzle room and room SL (/ui/#room, /ui/#roomself)
First-person grid room: the human gets a raycast view; the model sees text
only — facing, all four adjacent cells ("Ahead: open floor"; egocentric
directions only, no compass frame), an "In view:" line for objects in a 120°
cone, a "Known:" line of bearings to discovered landmarks out of sight (exact
alignments read "directly ahead/right/behind/left"; a walled straight line
reads "(blocked)"), carry status, and a step count — or a top-down map with a
neutral legend. It must find the key, unlock the dividing door, and reach the
exit within the 80-step budget (RoomRules.MAX_STEPS). Layouts are seeded and
always solvable (verified by BFS over the decision state space). Options are
forward / left / right (+ insufficient in SL), with forward
withheld when the faced cell is a wall or a locked door without the key — a
silent no-op, not a decision.
The SL loop mirrors the platformer with one policy difference: any
insufficient plurality plans (threshold 0), because the room has no failure
signal until the step budget runs out — a wandering model must be able to ask
early. Running out of steps also plans; either way the room then resets to
the same layout with the learned rules in place.
Try it / test
Spot-check decisions offline:
node examples/game-fixtures.cjs > /tmp/game-decisions.jsonl
.venv/bin/python -m semif_api.llama --input /tmp/game-decisions.jsonl --timeout 600
Prompt/physics coverage: node --test "tests/**/*.cjs".