Skip to content
Agentmart
Submit a tool

game-session-host

Hosts a game module through one typed seam: game screen, pause, undo, hints, a rescue, saved runs, results.

Skill by zakariaf

IndexedMIT★ 11
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/zakariaf-game-session-host

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Hosts a game module through one typed seam: game screen, pause, undo, hints, a rescue, saved runs, results. Use when assembling a game or wiring the Game screen or registry. Not for rules (game-rules-engine).

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md and code. Checked Oct 26, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Node.js package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/game-session-host

What's inside

Skill name
game-session-host
Description
Hosts a game module through one typed seam: game screen, pause, undo, hints, a rescue, saved runs, results. Use when assembling a game or wiring the Game screen or registry. Not for rules (game-rules-engine).
License
MIT
Instructions
230 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (113)

  • SKILL.md
  • shared.json · assets/
  • game-feedback.ts · examples/app-wiring/
  • game-host-config.ts · examples/app-wiring/
  • game-registry.ts · examples/app-wiring/
  • games-root.tsx · examples/app-wiring/
  • lights-out-hosted.ts · examples/app-wiring/
  • lights-out-tutorial.ts · examples/app-wiring/
  • game-route.tsx · examples/game-screen/
  • game-screen.tsx · examples/game-screen/
  • use-game-model.test.tsx · examples/game-screen/
  • use-game-model.ts · examples/game-screen/
  • sqlite-game-runs-repository.test.ts · examples/sqlite-game-runs/
  • sqlite-game-runs-repository.ts · examples/sqlite-game-runs/
  • v0002-create-game-runs.ts · examples/sqlite-game-runs/
  • assembling-a-game.md · references/
  • host-architecture.md · references/
  • session-flow.md · references/
  • check-game-host.mjs · scripts/
  • check-lib.mjs · scripts/
  • app-modules.mjs · scripts/lib/
  • host-layout.mjs · scripts/lib/
  • host-probes.mjs · scripts/lib/
  • host-static.mjs · scripts/lib/
  • source-scan.mjs · scripts/lib/
  • zustand-stand-in.mjs · scripts/lib/
  • selftest.mjs · scripts/
  • run-ref.test.ts · templates/src/core/game-results/
  • run-ref.ts · templates/src/core/game-results/
  • difficulty.test.ts · templates/src/core/game/
  • difficulty.ts · templates/src/core/game/
  • game-engine.ts · templates/src/core/game/
  • game-input.ts · templates/src/core/game/
  • game-module.ts · templates/src/core/game/
  • game-rules.ts · templates/src/core/game/
  • daily-start.test.ts · templates/src/core/game/level-kit/
  • daily-start.ts · templates/src/core/game/level-kit/
  • star-rating.test.ts · templates/src/core/game/level-kit/
  • star-rating.ts · templates/src/core/game/level-kit/
  • levels.ts · templates/src/core/game/
  • messages.ts · templates/src/core/game/
  • persistence.ts · templates/src/core/game/
  • stats.ts · templates/src/core/game/
  • testing.ts · templates/src/core/game/
  • contract-intents.test.ts · templates/src/core/game/testing/
  • contract-intents.ts · templates/src/core/game/testing/
  • engine-contract.test.ts · templates/src/core/game/testing/
  • engine-contract.ts · templates/src/core/game/testing/
  • json-shape.test.ts · templates/src/core/game/testing/
  • json-shape.ts · templates/src/core/game/testing/
  • play-bot.test.ts · templates/src/core/game/testing/
  • play-bot.ts · templates/src/core/game/testing/
  • play-choices.test.ts · templates/src/core/game/testing/
  • play-choices.ts · templates/src/core/game/testing/
  • fake-game-runs-repository.test.ts · templates/src/data/game-runs/
  • fake-game-runs-repository.ts · templates/src/data/game-runs/
  • game-runs-repository.ts · templates/src/data/game-runs/
  • use-pause-on-background.test.tsx · templates/src/hooks/
  • use-pause-on-background.ts · templates/src/hooks/
  • board-host-model.test.ts · templates/src/services/game-host/
  • …and 53 more

SKILL.md (first lines)

# Game session host

Makes every game playable inside the app the same way: the typed game module enters through one
generic seam (`createGameHost`), each run lives in a per-run store that writes the run before the
board animates it, a finished run is recorded in one repository transaction before its result
shows, and screens see only plain data and commands. The skill ships the host (templates), the
wiring around it (examples) and a checker that proves the invariants by running the app's own host
modules against a tiny test game, on iOS and Android alike (the host is pure TypeScript and React).

## Rules that must hold

1. **`createGameHost(game, deps)` is the only place the app touches a game's own types.** Screens,
   routes, UI, stores and shared hooks use the erased `GameHost`, `SessionHandle` and
   `SessionView`; only the registry (`src/games/game-registry.ts`) names a game, one factory per
   game; no cast anywhere in the seam. Why: TypeScript cannot hold "a module of some type" without
   a cast (strictFunctionTypes makes the intent parameter contravariant), and a cast hides exactly
   the errors the compiler exists to catch. (`unsafe-cast`, `screens-type-erased`)
2. **Every change is reduced, persisted, then published.** The per-run store calls its persist step
   before `set()`; a failed write publishes nothing and the view shows `saveFailure` so the screen
   offers a retry. Why: iOS and Android end background apps without warning; a kill during an
   animation must reopen the exact state on screen. (`save-before-publish`, `save-failure-shown`)
3. **Only the game's `intentToMove` makes moves; `null` means nothing happens.** The reducer ignores
   moves while paused or decided, and undo never steps out of a loss; screens send commands (undo,
   hint, continue, pause, finish, leave), never moves. Why: bots, solvers and players share one
   legality, and no illegal state can be saved. (`intent-legality`, `moves-only-while-playing`,
   `undo-not-after-loss`)
4. **A run ends in ONE repository transaction, before its result shows:** the result row, the best,
   the statistics and counters, and the saved run cleared (`recordRunEnd`). The move that decided
   the run then plays the win or lose feedback once. Why: a result on screen that is not on disk
   breaks trust, streaks and statistics. (`run-end-once`, `result-feedback`)
5. **At most one rescue per run, only after a loss, only when the rules have one and the app's config
   allows it** (`src/config/game-host-config.ts`, never a literal). A rescuable loss stays saved and
   unrecorded until the player continues or declines (`finish`); a new run of the same game first
   records a decided saved run (declined) and, if that write fails, writes nothing. Why: a Continue
   the rules cannot honour does nothing, and an abandoned loss must still reach results and streaks.
   (`continue-once`, `rescue-pending`, `abandoned-run-recorded`, `config-rules`, `continue-from-config`)
6. **Never strand a finished run.** A pending loss whose rescue offer is `'hidden'` (ads off,
   offline, no consent, no Premium) is finished at once: the Game screen's model sends
   `{ type: 'finish' }` once per `eventSeq` when `isLossStranded(view, offer)`; a loading ad is
   `'loading'`, never hidden. Why: an endless run in an ads-off build otherwise never shows its
   result and is never counted. (`loss-not-stranded`, `game-screen-wiring`)
7. **A saved run is validated by the game's own parsers at boot and on resume.** An unreadable run is
   dropped alone and logged (results stay); a replay that differs from the snapshot keeps the
   position and drops only the undo history; a resumed run starts paused. Why: one damaged row must
   never cost progress or crash the app. (`saved-run-validated`)
8. **Hosts are created once, in the composition root, after the data layer opened, never at import
   time or in a render** (a `useState` initializer), and provided once (`GameHostsProvider` inside
   the navigation theme). Opening a run writes nothing (dropping an unreadable saved run is the one
   idempotent exception); its handle `begin()`s in `useGameSession`'s effect.
   Why: a host reads and validates saved runs, and a render React repeats (StrictMode) must write
   nothing. (`host-not-created`, `host-at-import`, `host-in-render`, `host-not-provided`)
9. **The Game screen opens its run with `useGameSession`, renders the host's `BoardHost`, pauses on
   background, blur or a full-screen ad, and guards Back with `backCommandOf`.** iOS reports
   `inactive` (app switcher, calls) and keeps `active` under a Google full-screen ad; Android
   reports only `background`; all pause, and Pause shows afterwards on both. Back (header, iOS
   swipe, Android button, predictive back) is caught with `usePreventRemove` while the run is live
   and unrecorded: pause in play, resume in Pause, finish a pending loss; Pause -> Home sets a
   leaving flag first so the guard lets that one removal through. Why: coming back must show Pause,
   and only Pause -> Home leaves a live run. (`game-screen-wiring`, `back-guard`)
10. **Paid perks are sent only after payment, and offered only when they can be given:** a hint or a
    continue is sent when the rewarded ad completed, the allowance was spent or the player owns
    Premium (the monetization layer's `pay(then)`); the hint is offered only while
    `view.isHintAvailable`. Why: a perk granted before the ad completes is free forever, and a paid
    hint with nothing to show is wasted. (Proven by the Game screen's model test.)
11. **A tap-then-tap selection is UI state of the board host, never a move.** A tap in one of
    `input.selectRegions` toggles it, the next tap reaches `intentToMove` with `selected` filled in,
    and any new move, undo, rescue, run end or drag clears it. Why: bots, solvers, par, undo and the
    counters must never see a selection. (`board-host-model.test.ts`; `intent-legality`)
12. **Stars come from the level's own star rule, counters from the kept moves, play time only while
    playing** (each step clamped to 0..300,000 ms), and the first finished attempt of a daily is the
    day's result: a replay is practice (`isPractice`: no new best, no statistics, no streak). Why:
    undone moves must not count, a phone asleep on the result screen must not add hours, and a
    replayed daily must not rewrite the day. (`stars-from-table`, `counters-from-log`, `play-time`,
    `daily-practice`)
13. **Each game is one binding, registered, with its options and save policy right:**
    `src/games/<id>/<id>-hosted.ts` is `{ ...<ID>_GAME, Surface[, tutorial] }` typed `HostedGame`,
    the registry hosts it, its options promise nothing its rules lack, a game with a real-time loop
    (`sim/`) saves at save points and a turn-based one after every move, and
    `contracts/architecture.json` `gameFacing` lists the seam. Why: the binding is the one place the
    app types a game. (`hosted-binding`, `game-registered`, `save-policy`, `game-facing-recorded`)
14. **A real-time game saves through the host at its save points:** its board calls
    `onSavePoint(realtime.snapshot(copy), inputLog)` at each save point and at the run's end; the host
    writes it through the same persist step (never per move) and records the run once when that
    state decides it. Why: a direct write would bypass the store and the run-end transaction.
    (`save-point-write`)
15. **A tutorial (optional) plays its script beside the saved run, in memory:** each step accepts
    only its expected move, nothing is written or recorded, and every step is legal from its start.
    Why: replaying the tutorial must never cost the player their run or count in statistics.
    (`tutorial-beside-run`, `tutorial-legal`)
16. **Test-build shortcuts end runs through the same persist step as real play** and reach only the
    debug layer (`host.debugControls()`), never store builds. Why: an E2E flow must see the real
    run-end write and the real result. (`debug-run-end`)

## Workflow

1. **Check what exists.** Run `node ${CLAUDE_SKILL_DIR}/scripts/check-game-host.mjs .`. It prints
   NOT APPLICABLE until the app has a game. The game's rules and module come first
   (game-rules-engine: `src/core/game/` and `src/games/<id>/<id>-game.ts`); levels make level,
   daily and endless runs possible (level-generation-and-solvers). Read
   [references/host-architecture.md](references/host-architecture.md) for the seam and the files.
2. **Install the host once per app.** Copy `templates/` into the repo at the paths line 1 of each
   file names: `src/services/game-host/`, `src/stores/game-session/`, `src/data/game-runs/`
   (the port and its fake), `src/core/game-results/`, `src/hooks/use-pause-on-background.ts` and
   `test/games/` (the tally test game). `templates/src/core/game/` holds synced copies of
   game-rules-engine's kit and level-generation-and-solvers' `level-kit/star-rating.ts` and
   `level-kit/daily-start.ts`: keep the copies the app already has. Run `npm run typecheck` and
   `npx jest src/services/game-host src/stores/game-session src/data/game-runs --ci --coverage --coverageThreshold='{}'`.
3. **Store runs in the data layer.** Add a SQLite `GameRunsRepository` from
   `examples/sqlite-game-runs/` and hand it down with the app's data (`useAppData()`). Its migration
   is a new schema version (persistence-and-migrations, "Adding version N+1"): register the step,
   then add that version's frozen fixture database, its checksum and its `FIXTURES` entry, or the
   "every shipped version has a fixture" test fails.
4. **Wire the composition.** Read "The composition" in host-architecture.md. Write
   `src/games/game-registry.ts`, `src/config/game-host-config.ts` and the games root from
   `examples/app-wiring/`, and mount `GamesRoot` inside the navigation theme (app-startup-and-
   bootstrap's provider order). Prove the providers with one render test through the real root.
5. **Assemble each game.** Read [references/assembling-a-game.md](references/assembling-a-game.md):
   the binding (`examples/app-wiring/lights-out-hosted.ts`), its registry factory, its options from
   its rules, its save policy, its optional tutorial (`examples/app-wiring/lights-out-tutorial.ts`),

From zakariaf/ReactNative-Skills, MIT.

More from zakariaf/ReactNative-Skills

Signals

Stars
11
Forks
2
Open issues
0
Last commit (this folder)
Oct 17, 2026 (11 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from zakariaf

Cite this page

game-session-host skill. Agent Mart. https://agentmart.dev/skills/zakariaf-game-session-host (last updated 2026-10-24)