Game Contract

The Game Contract defines the interface any web game must implement to plug into LoopLoop. The platform treats the game as a black box that exposes well-defined integration points. As long as a game satisfies these contracts, LoopLoop can collect feedback, run the AI pipeline, playtest with bots, deploy changes, and notify players — regardless of genre, engine, or complexity.

Contract Summary

ContractPurposeRequired
Config ContractData-driven parameters the AI can safely editYes
State Exposure ContractObservable game state for automated playtestingYes
Telemetry ContractLifecycle events the platform collects and analyzesYes
Feedback Hook ContractIntegration points for the feedback UIYes
Scene ContractRequired scenes/screens the game must implementYes
Guardrail File ContractDesign constraints the AI reads as contextYes
Build ContractHow the game compiles and runsYes
Asset ContractHow generated art integrates into the gameRecommended

Config Contract

All tunable game parameters must live in JSON files under a config/ directory. This is the AI’s primary editing surface — the safest place for the pipeline to make changes, because it’s pure data with no logic.

Requirements

  1. A config/ directory must exist at the game’s source root.
  2. All numeric balance values (speeds, health, damage, cooldowns, spawn rates, XP thresholds, etc.) must live in config files, not hardcoded in source.
  3. Entity definitions (enemies, weapons, items, levels, etc.) must be data-driven — defined in config and loaded by the engine at boot.
  4. Config files must be valid JSON with a consistent, documented structure.
  5. The game engine must read config at startup. Changing a config file and rebuilding must change game behavior with no code modifications.
config/
├── balance.json       # Global tunable numbers (player stats, timings, rates)
├── enemies.json       # Enemy type definitions (name, stats, behavior, sprite)
├── weapons.json       # Weapon definitions (name, stats, projectile, sprite)
├── items.json         # Item/upgrade definitions
├── waves.json         # Spawn patterns, difficulty curve over time
└── progression.json   # XP curve, level-up pools, unlock conditions

balance.json Example Shape

{
  "player": {
    "maxHealth": 100,
    "speed": 150,
    "invincibilityFrames": 500,
    "pickupRadius": 50
  },
  "xp": {
    "baseToLevel": 10,
    "scalingFactor": 1.3,
    "gemValue": 1
  },
  "session": {
    "maxDurationSeconds": 300,
    "difficultyRampInterval": 30
  }
}

Entity Definition Example Shape (enemies.json)

{
  "enemies": [
    {
      "id": "bat",
      "name": "Bat",
      "health": 5,
      "damage": 8,
      "speed": 80,
      "xpValue": 1,
      "sprite": "enemies/bat",
      "behavior": "chase_player",
      "spawnWeight": 10
    }
  ]
}

The AI pipeline edits these files directly. The guardrail file contract constrains what values the AI can set.


State Exposure Contract

The game must expose its internal state to window.__gameState so that Playwright bots can observe the game without computer vision. This object is updated every frame (or at a minimum every 100ms).

Requirements

  1. The game must set window.__gameState as a plain JavaScript object.
  2. The object must be updated continuously during gameplay.
  3. The object must include at minimum the fields defined below.
  4. Games may add additional game-specific fields beyond the required set.

Required Fields

interface GameState {
  // Session metadata
  status: "loading" | "playing" | "paused" | "game_over" | "transition" | "menu" | "feedback";
  sessionId: string;
  elapsedSeconds: number;
  gameVersion: string;

  // Player state
  player: {
    x: number;
    y: number;
    health: number;
    maxHealth: number;
    level: number;
    xp: number;
    xpToNextLevel: number;
    alive: boolean;
  };

  // Entity counts and positions
  entities: {
    enemies: Array<{ id: string; type: string; x: number; y: number; health: number }>;
    pickups: Array<{ id: string; type: string; x: number; y: number }>;
  };

  // Session metrics (running totals)
  metrics: {
    enemiesKilled: number;
    damageDealt: number;
    damageTaken: number;
    itemsCollected: number;
    levelsGained: number;
  };
}

Usage

Playwright bots query this object to make decisions (which direction to move) and to collect session metrics (how long the player survived, what killed them). The platform’s playtest harness reads window.__gameState via page.evaluate().


Telemetry Contract

The game must emit lifecycle events that the platform collects, stores, and feeds into the AI pipeline. Telemetry is the implicit feedback channel — it tells us what players actually do, not just what they say.

Requirements

  1. The game must call a platform-provided LoopLoop.telemetry.emit(event) function for each event. See the API Reference (section 8. Client SDK) for the client SDK surface.
  2. Events must conform to the schemas below.
  3. The platform batches events and submits them to the backend on session end (or periodically for long sessions).

Required Events

EventWhenRequired Fields
session_startGame scene loads and gameplay beginssessionId, gameVersion, playerId (nullable)
session_endPlayer dies, quits, or timer expiressessionId, durationSeconds, enemiesKilled, levelReached, causeOfDeath
player_damagedPlayer takes damagesessionId, source (enemy type or hazard), damage, remainingHealth
entity_killedPlayer kills an enemysessionId, entityType, weaponUsed, elapsedSeconds
level_upPlayer gains a levelsessionId, newLevel, upgradeChosen, upgradesOffered
item_acquiredPlayer picks up an item/weaponsessionId, itemId, itemType, elapsedSeconds

Optional Events (Game-Specific)

Games may emit additional events beyond the required set. The pipeline stores all events and the AI triage step can reference any of them. Examples:

Event Shape Example

LoopLoop.telemetry.emit({
  type: "level_up",
  sessionId: "abc-123",
  timestamp: Date.now(),
  data: {
    newLevel: 5,
    upgradeChosen: "fire_wand_2",
    upgradesOffered: ["fire_wand_2", "health_boost", "speed_up"]
  }
});

Feedback Hook Contract

The game must integrate LoopLoop’s feedback UI at natural pause points in the game loop — moments where the player is already waiting or reflecting, so asking for feedback feels seamless rather than disruptive.

Requirements

  1. Natural feedback moments — The game must identify and flag at least one natural pause point where asking for feedback fits the flow. Examples: between rounds, after a boss fight, on a results tally, at a level transition, after an upgrade selection, when returning to a hub. There is no requirement for a dedicated “Game Over” screen — the game decides what its natural moments are.
  2. Quick prompt at pause points — At each flagged moment, the game triggers the quick feedback prompt. This is a lightweight one-tap rating that takes under 2 seconds and does not break flow.
  3. Detailed feedback via the feedback button — The SDK auto-mounts a draggable in-game feedback button. Players can tap it at any time to open the detailed form. No additional integration is needed beyond calling startSession().
  4. Context passing — When invoking the feedback UI, the game must pass the current session context (sessionId, game version, player state at time of feedback).

Quick Feedback Prompt

A single-tap rating shown at natural pause points:

Plus an optional “Tell us more…” link that opens the detailed form.

Detailed Feedback Form

A full form with:

Accessible at any time via the in-game feedback button (auto-mounted by the SDK).

Integration

// At a natural pause point (between rounds, after a boss, level transition, etc.)
LoopLoop.feedback.showQuickPrompt({
  sessionId: currentSession.id,
  gameVersion: CONFIG.version,
  sessionMetrics: {
    durationSeconds: elapsed,
    enemiesKilled: kills,
    levelReached: level,
    causeOfDeath: deathCause
  }
});

// The detailed feedback form is always available via the in-game
// feedback button (auto-mounted by LoopLoop.telemetry.startSession).
// No additional code needed. To open it programmatically:
LoopLoop.feedback.showDetailedForm({
  gameVersion: CONFIG.version,
  context: "pause_menu"
});

Examples of Natural Feedback Moments

Game TypeNatural Moment
Roguelite / Run-basedBetween runs, on the results tally
Endless runnerAfter the run ends, before “play again”
PuzzleBetween levels, after completing a chapter
RacingPost-race standings screen
RPGAfter a boss fight, at a save point
Idle / IncrementalOn prestige, after an offline earnings screen

Scene Contract

The platform does not prescribe how games structure their screens, scenes, menus, or UI. Games can have whatever internal architecture they want. The only two hard rules are:

  1. Direct to gameplay. The first interactive screen the player sees must be the game running. No title screens, no main menus, no “press start”. A loading screen is fine. A login popup overlay is fine (and can be dismissed). But the player must land in gameplay with zero interaction required.

  2. Trigger feedback at natural pause points. The game must call showQuickPrompt() at least once per session at a moment that fits its loop (see the Feedback Hook Contract). Everything else — menus, settings, pause screens, inventories, shops — is entirely up to the game.


Guardrail File Contract

The game must provide a set of guardrail files in a guardrails/ directory. The AI pipeline reads these files as context before making any changes. They define the game’s identity, what’s safe to change, and the hard boundaries on every parameter.

Required Files

FilePurpose
guardrails/DESIGN_DOC.mdThe game’s design document / constitution. Defines genre, theme, player fantasy, core loop, tone, hard no’s. The AI reads this to understand what kind of game it’s working on.
guardrails/BOUNDARIES.mdDefines GREEN / YELLOW / RED zones — which files and systems the AI may freely change, change with caution, or must not touch.
guardrails/PARAMETER_BOUNDS.jsonMachine-readable min/max bounds for every tunable parameter in config/. The pipeline’s validate step rejects any change that exceeds these bounds.
guardrails/STYLE_GUIDE.mdVisual and tonal guidelines for art generation. Defines art style, color palette, sprite dimensions, visual language for entities. The art agent reads this when generating assets via Scenario.gg.

These files are the authoritative guardrail surface: the pipeline parses them, applies them during validation, and blocks changes that violate bounds or cross RED zones.


Build Contract

The game must be buildable and deployable as a static web application.

Requirements

  1. Single build command — The game must build with a single shell command (e.g., npm run build). The command must be specified in the project’s package.json scripts.
  2. Static output — The build must produce a directory of static files (HTML, JS, CSS, assets) that can be served by any static hosting provider.
  3. Output directory — The build output must go to a known directory (e.g., dist/), configurable in the project config.
  4. Zero runtime dependencies — The built game must run entirely in the browser. No server-side rendering or runtime server required for the game itself (API endpoints are separate).
  5. Error-free build — A clean build on the current main branch must always succeed. The pipeline treats build failure as a hard gate.

Platform API Endpoints

The game’s runtime code communicates with the platform via HTTP endpoints. Schemas, authentication, and the full route list are documented in the API Reference. At a high level, the browser SDK talks to routes such as:


For games that want to use LoopLoop’s art pipeline (Scenario.gg integration), assets should follow these conventions.

Requirements

  1. Generated assets directory — AI-generated art goes in public/assets/generated/. The art agent writes to this directory; the game reads from it.
  2. Asset references in config — Entity definitions in config/*.json must reference sprites by path (e.g., "sprite": "enemies/bat"). The art agent updates these references when it generates new assets.
  3. Fallback sprites — The game should handle missing sprites gracefully (show a placeholder) in case an art generation fails.

Directory Structure

public/assets/
├── sprites/           # Hand-crafted or initial placeholder sprites
├── audio/             # Sound effects and music
└── generated/         # AI-generated art (Scenario.gg output)
    ├── enemies/
    ├── weapons/
    ├── items/
    └── environment/

Minimal Compliant Game Example

The simplest possible game that satisfies the contract:

my-game/
├── package.json              # "build": "vite build"
├── vite.config.ts
├── config/
│   └── balance.json          # At least one config file with tunable values
├── guardrails/
│   ├── DESIGN_DOC.md         # "This is a clicker game about..."
│   ├── BOUNDARIES.md         # "GREEN: config/, YELLOW: src/entities/, RED: src/engine/"
│   ├── PARAMETER_BOUNDS.json # { "clickValue": { "min": 1, "max": 100 } }
│   └── STYLE_GUIDE.md        # "Flat, colorful, 64x64 icons"
├── src/
│   └── main.ts               # Sets window.__gameState, emits telemetry, renders game
├── public/
│   └── index.html
└── dist/                     # Build output

Even a one-button clicker game can plug into LoopLoop and receive autonomous daily improvements from player feedback. The contract is designed to be lightweight enough that compliance is trivial, while providing enough structure for the pipeline to operate effectively.