semif-api-rocm

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

room-self-rules.js (4205B)


      1 "use strict";
      2 
      3 /* No-rules puzzle room: the same turn-based grid as room-rules.js with every
      4  * line of how-to-play prose removed. The model is never told the goal, what
      5  * stepping or turning does, or what the key/door/exit mean — it only sees
      6  * neutral observations (adjacent presences, what the view cone
      7  * sees, bearings to discovered landmarks, carry status, a step count,
      8  * or the top-down map with a neutral legend) plus the action list
      9  * with accurate descriptions, and an "insufficient" escape hatch. When it
     10  * cannot decide, or the step budget runs out, the client asks /plan for rules
     11  * and injects the answer into later states as "Rules".
     12  *
     13  * Mechanics are RoomRules' (this module only assembles observation text);
     14  * SelfRoomRules.PLAN_GOAL is the simulation's one-line statement of the goal —
     15  * the only game-specific input to /plan.
     16  */
     17 const SelfRoomRules = (() => {
     18   const { W, H, MAX_STEPS, DIRS, adjacentLines, inViewLine, knownLine } = RoomRules;
     19 
     20   // First-person state — the ruled fppText minus its three how-to-play lines
     21   // (the room/goal intro, the action explanation, the key/door briefing) and
     22   // the guided hint. Facing, presence, in-view, carry and steps stay:
     23   // they are observations, not rules. There is no event log: a list of the
     24   // actor's own recent actions is imitation bait for a single-pass reader.
     25   function fppText(state, layout, seen) {
     26     return [
     27       // Ordered for a single-pass reader: standing context first, immediate
     28       // sensory evidence last (the freshest tokens weigh most).
     29       "Current State:",
     30       `Steps taken: ${state.steps}/${MAX_STEPS}`,
     31       "",
     32       knownLine(state, layout, seen),
     33       `You carry: ${state.hasKey ? "the key" : "no key"}`,
     34       "",
     35       inViewLine(state, layout),
     36       "",
     37       ...adjacentLines(state, layout),
     38     ].join("\n");
     39   }
     40 
     41   // Top-down map — the ruled mapText minus the goal line and the pickup/door
     42   // instructions. The legend stays: it names symbols, it does not teach rules.
     43   function mapText(state, layout) {
     44     const rows = [];
     45     for (let y = 0; y < H; y++) {
     46       const row = [];
     47       for (let x = 0; x < W; x++) {
     48         if (x === state.x && y === state.y) { row.push(DIRS[state.dir].arrow); continue; }
     49         let c = layout.grid[y][x];
     50         if (c === "K" && state.hasKey) c = ".";
     51         if (c === "D" && state.doorOpen) c = "o";
     52         row.push(c);
     53       }
     54       rows.push(row.join(" "));
     55     }
     56     return [
     57       "Legend: [`#`: wall, `.`: floor, `D`: locked door, `o`: open door, `K`: key, `E`: exit, `^ > v <`: you, facing that way]",
     58       "",
     59       ...rows,
     60       "",
     61       `Steps taken: ${state.steps}/${MAX_STEPS}`,
     62     ].join("\n");
     63   }
     64 
     65   // mode: "fpp" | "map" — both neutral; there is deliberately no guided
     66   // variant: guidance was instructional prose, which this demo never sends.
     67   function stateText(state, layout, mode = "fpp", seen) {
     68     if (mode === "map") return mapText(state, layout);
     69     return fppText(state, layout, seen);
     70   }
     71 
     72   const QUESTION = "What should the player do now?";
     73   // Actions only, with accurate descriptions, plus the escape hatch. Unlike
     74   // the platformer there is no denied action here (a bump is still a
     75   // completed step), so insufficient is the only mid-attempt trigger.
     76   const OPTIONS = [
     77     ...RoomRules.OPTIONS,
     78     { id: Planner.INSUFFICIENT_ID, description: "Insufficient evidence to decide" },
     79   ];
     80 
     81   // Options for the CURRENT state: forward is withheld when the faced cell
     82   // is a no-op (wall, or a locked door without the key) — a decision that
     83   // cannot change anything is not a decision. Insufficient stays.
     84   const optionsFor = (state, layout) => [
     85     ...RoomRules.optionsFor(state, layout),
     86     OPTIONS[OPTIONS.length - 1],
     87   ];
     88 
     89   /* The simulation's one-line statement of the goal — the only game-specific
     90    * input to /plan. Planner.context() wraps it with the trigger note and the
     91    * rules currently in effect; the system prompt is universal. */
     92   const PLAN_GOAL = "Reach the exit of the room.";
     93 
     94   return { fppText, mapText, stateText, QUESTION, OPTIONS, optionsFor, PLAN_GOAL };
     95 })();
     96 
     97 if (typeof module !== "undefined") module.exports = SelfRoomRules;