semif-api-rocm

SemIf HTTP API and rocm flake
Log | Files | Refs | README | LICENSE

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):

  1. insufficient argmax with p ≥ 0.99 (Planner.INSUFFICIENT_THRESHOLD). A weaker insufficient argmax does not plan — the game takes the best real action and says so.
  2. The player falls into a pit.
  3. 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".