{"_id":"@caputchin/preset-excalibur","name":"@caputchin/preset-excalibur","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@caputchin/preset-excalibur","version":"0.2.0","description":"Self-contained Caputchin replay preset for the Excalibur.js engine: headless boot, fixed-step pump, deterministic RNG rail, and the conforming run/live adapters. The on-ramp for any Excalibur game on the deterministic-replay platform.","keywords":["caputchin","excalibur","deterministic","replay","captcha","game","preset"],"homepage":"https://github.com/Caputchin/caputchin-sdk#readme","repository":{"type":"git","url":"git+https://github.com/Caputchin/caputchin-sdk.git","directory":"presets/excalibur"},"bugs":{"url":"https://github.com/Caputchin/caputchin-sdk/issues"},"license":"Apache-2.0","type":"module","publishConfig":{"access":"public"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./install":{"types":"./dist/install.d.ts","import":"./dist/install.js","require":"./dist/install.cjs"}},"sideEffects":["./dist/install.js","./dist/install.cjs"],"dependencies":{"@caputchin/determinism":"0.2.0","@caputchin/replay-contract":"0.3.0"},"peerDependencies":{"@caputchin/game-sdk":"^3.0.0","excalibur":"0.32.0"},"devDependencies":{"@types/node":"^22.0.0","excalibur":"0.32.0","tsup":"^8.0.0","typedoc":"^0.28.19","typedoc-plugin-markdown":"^4.11.0","typescript":"^5.4.0","vitest":"^2.0.0","@caputchin/game-sdk":"3.2.0","@caputchin/replay-selfcheck":"0.2.0"},"scripts":{"build":"tsup","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","docs":"typedoc && node ../../scripts/normalize-docs.mjs docs"},"_id":"@caputchin/preset-excalibur@0.2.0","_integrity":"sha512-kGiJMt0vshdjaZhrsXXw078by30DuUn9bbGEbNpryR4bw5pKd3tyOGAEiGqelJK/0OKarOWX5opvoZUWAf1+dQ==","_resolved":"/tmp/db0465faa9e61d3ba46d37b1001510c3/caputchin-preset-excalibur-0.2.0.tgz","_from":"file:caputchin-preset-excalibur-0.2.0.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-kGiJMt0vshdjaZhrsXXw078by30DuUn9bbGEbNpryR4bw5pKd3tyOGAEiGqelJK/0OKarOWX5opvoZUWAf1+dQ==","shasum":"ab44a251420d7575d0efe326e59ac0c3ef12cb54","tarball":"https://registry.npmjs.org/@caputchin/preset-excalibur/-/preset-excalibur-0.2.0.tgz","fileCount":12,"unpackedSize":102564,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDj58OB7fN4Kf32bMCnFbaL55jBrcu2k5+SNMMKm+4z2gIhAOKaPEZZhEiNCGTaNGpuSLbIfRUVnlYtpvJWr4HBA1Rt"}]},"_npmUser":{"name":"caputchiner","email":"info@caputchin.com"},"directories":{},"maintainers":[{"name":"caputchiner","email":"info@caputchin.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/preset-excalibur_0.2.0_1781221176363_0.5612304603029792"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-11T23:39:36.232Z","0.2.0":"2026-06-11T23:39:36.508Z","modified":"2026-06-11T23:39:36.693Z"},"maintainers":[{"name":"caputchiner","email":"info@caputchin.com"}],"description":"Self-contained Caputchin replay preset for the Excalibur.js engine: headless boot, fixed-step pump, deterministic RNG rail, and the conforming run/live adapters. The on-ramp for any Excalibur game on the deterministic-replay platform.","homepage":"https://github.com/Caputchin/caputchin-sdk#readme","keywords":["caputchin","excalibur","deterministic","replay","captcha","game","preset"],"repository":{"type":"git","url":"git+https://github.com/Caputchin/caputchin-sdk.git","directory":"presets/excalibur"},"bugs":{"url":"https://github.com/Caputchin/caputchin-sdk/issues"},"license":"Apache-2.0","readme":"# @caputchin/preset-excalibur\n\nThe on-ramp for an [Excalibur.js](https://excaliburjs.com) game on the\n[Caputchin](https://caputchin.com) deterministic-replay platform.\n\nCaputchin runs a replay captcha: your game records an input trace in the browser,\nand the server re-runs the same simulation over that trace and trusts only the\nreplayed verdict. Excalibur has no headless mode, so this preset makes it run\nheadless (a DOM shim on Excalibur's 2D-canvas path) AND deterministic: it advances\nthe engine at a fixed timestep (the same fixed-dt ticks in the browser and on the\nserver), seeds the gameplay RNG from the round seed, and makes `Math`\ntranscendentals deterministic. You write the game once against a small per-tick\napi; it runs live in the browser and re-runs on the server. You give it the\nconforming `run`.\n\nThe input model is **pointer-first** (a rich-gesture channel: down / move / up\nwith world coordinates), with an optional named-action channel. This makes it a\ngood fit for gesture games (slash / drag / draw); discrete-button games can use\nthe action channel.\n\nDependencies: the mandatory `@caputchin/replay-contract` and the shared\n`@caputchin/determinism` kit (the deterministic Math + seeded RNG).\n\n## Install\n\n```sh\npnpm add @caputchin/preset-excalibur @caputchin/game-sdk excalibur\n```\n\n`excalibur` and `@caputchin/game-sdk` are peer dependencies. Pin the exact\n`excalibur` version the preset is built against (its determinism is\nversion-specific).\n\n## Write your game once\n\n```ts\n// src/game.ts\nimport { defineExcaliburGame } from '@caputchin/preset-excalibur';\n\nexport const game = defineExcaliburGame(\n  (engine, api) => {\n    // Render-only setup is skipped on the server with `if (!api.headless)`.\n    // (v0.1 is procedural: draw with Excalibur graphics primitives, no asset files.)\n\n    // The sim lives in api.onTick - called once per fixed tick, AFTER this tick's\n    // input is applied, on BOTH ends. Read input via the api, randomness via the\n    // api's seeded rand*. Never read engine.input, Date, Math.random, or fetch.\n    let score = 0;\n    api.onTick(() => {\n      for (const ev of api.pointer.events) {\n        // ev = { kind: 0|1|2 (down/move/up), x, y } in fixed world space.\n        if (ev.kind === 0 && hitTarget(ev.x, ev.y)) score += 1;\n      }\n      api.setScore(score);\n      if (score >= readPassScore(api.ctx)) {\n        api.pass();\n        api.gameOver(); // pass implies the round is over; the pump stops here\n      }\n      if (api.tick > timeBudgetTicks(api.ctx)) api.gameOver(); // time's up = fail\n    });\n  },\n  {\n    width: 800, // the fixed world the sim reasons in (pointer coords are in this space)\n    height: 600,\n    maxTicks: 60 * 50, // reject runs longer than ~60s (50 ticks/s)\n    // actions: ['left', 'right'], // optional discrete channel (index is the wire code: APPEND, never reorder)\n  },\n);\n```\n\n## The two entries\n\n```ts\n// src/run.ts  -> caputchin.json `run.entry` (the headless replay artifact)\nimport '@caputchin/preset-excalibur/install'; // MUST be first - shims the DOM before excalibur evaluates\nimport { excaliburRun } from '@caputchin/preset-excalibur';\nimport { game } from './game.js';\nexport const run = excaliburRun(game);\n```\n\n```ts\n// src/index.ts -> caputchin.json `entry` (the playable browser game)\nimport { register } from '@caputchin/game-sdk';\nimport { mountExcaliburGame } from '@caputchin/preset-excalibur';\nimport { game } from './game.js';\nregister((container, bridge, ctx) => mountExcaliburGame(game, { container, bridge, ctx }));\n```\n\nThe live mount runs Excalibur on its normal (WebGL) renderer, sized to the\ncontainer with `DisplayMode.FitContainer`; the headless replay runs the 2D-canvas\npath with rendering disabled. Bundle both into the iframe artifact (Excalibur\nincluded) with your bundler; there is no runtime CDN fetch under the game CSP.\n\n## `caputchin.json`\n\nExcalibur is pure JavaScript, so the replay artifact is a single entry, no\n`run.modules`:\n\n```json\n{\n  \"entry\": \"dist/<game>.js\",\n  \"run\": { \"entry\": \"dist/run.js\" },\n  \"preferred\": { \"width\": 320, \"height\": 480 }\n}\n```\n\n## The api\n\nRead these each tick inside `api.onTick`:\n\n- **`api.pointer`** - `{ isDown, x, y, events }`. `events` is the pointer edges\n  that landed THIS tick (a run of them is a slash / drag) in fixed world space -\n  the rich-gesture channel the input-signature judge scores.\n- **`api.isDown / justPressed / justReleased(action)`** - the optional named-action\n  channel (declare `options.actions`; bind keyboard yourself and call\n  `api.press` / `api.release` from live handlers).\n- **`api.rand / randi / randiRange / chance / choose`** - the seeded RNG (sfc32,\n  seeded from the round seed). The ONLY randomness source; reproduces both ends.\n- **`api.setScore / pass / gameOver`** - the verdict. `pass()` latches the captcha;\n  `gameOver()` ends the round (call it when you pass, fail, or run out of time).\n- **`api.announce(msg)`** - polite ARIA announcement (live only).\n- **`api.ctx`** - `{ seed, config, locale, skin }`. Read gate-affecting params\n  (pass threshold, difficulty) from `config`, never from input.\n- **`api.headless`** - true on the server; guard render-only setup with it.\n\n## The few rules\n\n- **Input only through `api`** (pointer + named actions), never `engine.input`.\n  This is the one thing the preset records and replays.\n- **Randomness only through `api.rand*`.** Never `Math.random()` in the sim.\n- **No wall clock, no network.** Never read `Date`, `performance.now`, `crypto`,\n  or `fetch` in the sim - those stay non-deterministic.\n- **The sim must self-terminate.** Call `api.gameOver()` when the round ends\n  (pass / fail / time budget). A run that never ends hits `maxTicks` and is\n  rejected (truncated = fail).\n- **Author the sim in world space.** Keep a static camera (the live mount defaults\n  it so world `(0,0)` is the top-left and `(width,height)` the bottom-right) so the\n  pointer coordinates the preset records match the space the sim reasons in.\n\nGet this wrong and your game false-rejects its own players. There is no\nindex-time determinism gate, only a conformance smoke test, so verify locally\n(e.g. `@caputchin/replay-selfcheck`).\n\n## What the preset handles for you\n\n- Headless Excalibur boot under an isolated DOM shim (2D-canvas path, no WebGL),\n  and the fixed-step `TestClock` pump that drives the loop on the server.\n- Seeding the gameplay RNG from the round seed + deterministic `Math`.\n- Recording pointer + action input tick-stamped in the browser and replaying it at\n  the same ticks on the server (the trace codec).\n- The `run(seed, config, trace) -> verdict` contract, including a failing verdict\n  for a malformed trace and rejection of a non-terminating run.\n\n## Builds on\n\n- [`@caputchin/replay-contract`](https://www.npmjs.com/package/@caputchin/replay-contract) - the `run` / `Seed` / `Verdict` shapes.\n- [`@caputchin/determinism`](https://www.npmjs.com/package/@caputchin/determinism) - the deterministic Math + seeded RNG.\n- [`@caputchin/game-sdk`](https://www.npmjs.com/package/@caputchin/game-sdk) - the widget `register` surface and `ctx`.\n- [`excalibur`](https://excaliburjs.com) - the game engine itself.\n```\n","readmeFilename":"README.md","_rev":"1-38ccf8e41c534d8fd941efa0e498138d"}