Games / Web

AI-player-ready web games

Who it's for. You write a browser sim or Hub adapter (Tsunami, Surge, or your own).

What you'll do. Encode rays into 32 (or 64) floats, POST /play/act without blocking the 0.1 s tick, and keep wave/contest extras as overlays.

For authors of a browser sim or adapter that talks to the Hub. The NPC sees classified rays, not pixels. Reference sim: apps/web-obby (Escape the Tsunami, Surge Gauntlet). Roblox companion · Licensing · Deploy & compete

Space / physics envelope (stud-equivalent)

SpecTraining targetNotes
Walk speed16Panic sprint is animation only on tsunami
Learn gaps2–4Lip remain ≤ 3 (MAP_LIP_REMAIN)
Advanced gaps≤ 8Not a 12-stud default jump
Body width~2Surge corridor radius 0.7, clearance ≥ 0.5 to obstacles
Vision30 unitsClassified hits, not pixels

What to send every tick (live play)

POST https://<hub>/api/v1/play/act with Authorization: Bearer aig_…. Do not block the 0.1 s sim on fetch. One in-flight POST per agent. Hold last action ~0.45 s, then a lip-safe local pack, then halt after ~2 s.

Build obs from pose (x,y,z, grounded, checkpoint) plus perception_rays[]: angle, pitch, direction, distance, hit_kind (platform, hazard, gap, wall, target, reward, finish). Room extras: wave height, surge distance. Do not put rivals into the 32-dim map vector; contest is overlay.

What is not in the 32 floats

Map obs is pose + checkpoint + edge probes + yaw platform/hazard distances + down ray + nearest reward + falling + atEdge. Wave height, panic, lip remain, contest rivals, and moving-pad JumpNow are overlays or extra fields you pack — Hub greedy act does not invent them from a still ray. Surge uses a separate 64-dim vector (dest / The Surge). Do not slice 64→32.

Response: { m, j, f, seq } only — never persist this as a brain file.

const TICK_SEC = 0.1;
const HOLD_SEC = 0.45;
const HALT_SEC = 2.0;

let seq = 0;
let inFlight = false;
let lastAct = { m: 1, j: false, f: false };
let lastHubAt = 0;
let startedAt = performance.now() / 1000;
let connectionLost = false;

async function requestAct(obs) {
  if (inFlight || connectionLost) return;
  inFlight = true;
  seq += 1;
  const mySeq = seq;
  try {
    const res = await fetch("https://aia-hub.metamindinglab.com/api/v1/play/act", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer aig_YOUR_LICENSE",
      },
      body: JSON.stringify({
        game_id: "your-game",
        brain_id: "brain-uuid",
        room_slug: "obby_wave",
        obs,
        seq: mySeq,
      }),
    });
    const reply = await res.json();
    if ((reply.seq ?? 0) < mySeq) return;
    lastAct = {
      m: Math.max(0, Math.min(8, Math.floor(reply.m ?? 1))),
      j: reply.j === true,
      f: reply.f === true,
    };
    lastHubAt = performance.now() / 1000;
  } finally {
    inFlight = false;
  }
}

function currentAct(obs) {
  void requestAct(obs);
  const now = performance.now() / 1000;
  if (lastHubAt > 0) {
    const age = now - lastHubAt;
    if (age <= HOLD_SEC) return { ...lastAct, mode: "hub" };
    if (age <= HALT_SEC) return safetyFallback(obs);
    connectionLost = true;
    return { m: 0, j: false, f: false, mode: "halt" };
  }
  if (now - startedAt <= HALT_SEC) return safetyFallback(obs);
  connectionLost = true;
  return { m: 0, j: false, f: false, mode: "halt" };
}

function safetyFallback(obs) {
  const falling = (obs[30] ?? 0) > 0.5;
  const atEdge = (obs[31] ?? 0) > 0.5;
  if (falling) return { m: 0, j: false, f: false, mode: "safety" };
  if (atEdge) return { m: 1, j: true, f: false, mode: "safety" };
  return { m: 1, j: false, f: false, mode: "safety" };
}

setInterval(() => {
  const obs = encodeObs(sim.state); // 32 or 64 floats
  const act = currentAct(obs);
  if (act.mode !== "halt") applyMoveJumpFire(sim.agent, act);
}, TICK_SEC * 1000);

What to POST at end of a training run

First-party / train license only. Public production builds must not call /training/brain/export.

Use campus secret VITE_ARENA_CAMPUS_SECRET / ARENA_CAMPUS_SECRET or a train grant.

Skill gating (tsunami)

Map owns the tick when airborne, jump pending, pad remain ≤ 3, side hop lip, or wave panic. Strategy and contest yield. Canonical: apps/web-obby/src/policy/skillCatalog.ts.

Contest director (optional): POST /api/v1/training/contest-director with train/campus auth. Apply block_slow / bump_deny only if map does not own the tick; typical gates confidence ≥ 0.7 and safe_to_contest ≥ 0.55.

Contest heat

4 agents, shared collision, pads Platform_5–Platform_9, CP ≥ 3. Block if ahead and rival behind; bump if behind and rival beside/ahead. Sessions only — no map BC. Full owner-selects-game + N-runtime example: Deploy & compete.

Internal references