{"_id":"@caputchin/preset-melonjs","_rev":"2-02654c00e2b08e0a45970f3b9c717b02","name":"@caputchin/preset-melonjs","dist-tags":{"latest":"0.2.1"},"versions":{"0.2.0":{"name":"@caputchin/preset-melonjs","version":"0.2.0","keywords":["caputchin","melonjs","preset","deterministic","replay","captcha"],"license":"Apache-2.0","_id":"@caputchin/preset-melonjs@0.2.0","maintainers":[{"name":"caputchiner","email":"info@caputchin.com"}],"homepage":"https://github.com/Caputchin/caputchin-sdk#readme","bugs":{"url":"https://github.com/Caputchin/caputchin-sdk/issues"},"dist":{"shasum":"dffe515519a69dc11c126d3b6e7ba626c87070b2","tarball":"https://registry.npmjs.org/@caputchin/preset-melonjs/-/preset-melonjs-0.2.0.tgz","fileCount":15,"integrity":"sha512-jNhrECtdMPGVtUwk1IRmcCFBr4vnM113fr98IDVShUMbDK3NvVHyUWoC8TvUhAslm99HPThfzZj+vH402YR2Uw==","signatures":[{"sig":"MEUCIA5J83Pu8A+yXqRoREvXPaK8pV12UMVkNrqtDtBUMtv2AiEA9gTNZtbMkmCxGmMD8Llm6hdLNgWjZoWkAKmv0U6nUrQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":40717},"type":"module","_from":"file:caputchin-preset-melonjs-0.2.0.tgz","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./live":{"types":"./dist/live.d.ts","import":"./dist/live.js","require":"./dist/live.cjs"},"./install":{"types":"./dist/install.d.ts","import":"./dist/install.js","require":"./dist/install.cjs"}},"scripts":{"dev":"tsup --watch","docs":"typedoc && node ../../scripts/normalize-docs.mjs docs","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"caputchiner","email":"info@caputchin.com"},"_resolved":"/tmp/1b9ce9fdb58b3e9f9cc14b10a2108efa/caputchin-preset-melonjs-0.2.0.tgz","_integrity":"sha512-jNhrECtdMPGVtUwk1IRmcCFBr4vnM113fr98IDVShUMbDK3NvVHyUWoC8TvUhAslm99HPThfzZj+vH402YR2Uw==","repository":{"url":"git+https://github.com/Caputchin/caputchin-sdk.git","type":"git"},"_npmVersion":"11.13.0","description":"Per-engine on-ramp for running a melonJS game headless and deterministically on the Caputchin replay platform.","directories":{},"sideEffects":["./dist/install.js","./dist/install.cjs","./dist/live.js","./dist/live.cjs"],"_nodeVersion":"24.16.0","dependencies":{"@caputchin/engine-kit":"0.4.0","@caputchin/determinism":"0.2.0","@caputchin/replay-contract":"0.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.0.0","vitest":"^2.0.0","melonjs":"^19.6.0","typedoc":"^0.28.19","typescript":"^5.4.0","@types/node":"^22.0.0","typedoc-plugin-markdown":"^4.11.0"},"peerDependencies":{"melonjs":"^19"},"_npmOperationalInternal":{"tmp":"tmp/preset-melonjs_0.2.0_1780536361491_0.14870498570523472","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@caputchin/preset-melonjs","version":"0.2.1","description":"Per-engine on-ramp for running a melonJS game headless and deterministically on the Caputchin replay platform.","keywords":["caputchin","melonjs","preset","deterministic","replay","captcha"],"homepage":"https://github.com/Caputchin/caputchin-sdk#readme","repository":{"type":"git","url":"git+https://github.com/Caputchin/caputchin-sdk.git"},"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"},"./live":{"types":"./dist/live.d.ts","import":"./dist/live.js","require":"./dist/live.cjs"}},"sideEffects":["./dist/install.js","./dist/install.cjs","./dist/live.js","./dist/live.cjs"],"peerDependencies":{"melonjs":"^19"},"dependencies":{"@caputchin/determinism":"0.2.0","@caputchin/engine-kit":"0.4.0","@caputchin/replay-contract":"0.3.0"},"devDependencies":{"@types/node":"^22.0.0","melonjs":"^19.6.0","typescript":"^5.4.0","tsup":"^8.0.0","typedoc":"^0.28.19","typedoc-plugin-markdown":"^4.11.0","vitest":"^2.0.0"},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest","docs":"typedoc && node ../../scripts/normalize-docs.mjs docs"},"_id":"@caputchin/preset-melonjs@0.2.1","_integrity":"sha512-z2tczT0B9yJv2Jz4h0ploCZF2f0o20ZtbQYkwfItJj6U3o3Wt2N4KhUxeeH2qZB7LQhjMMJG/B/GLc+oM3Br3g==","_resolved":"/tmp/99273f3da02e6be03ca055dfa16d6eb6/caputchin-preset-melonjs-0.2.1.tgz","_from":"file:caputchin-preset-melonjs-0.2.1.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-z2tczT0B9yJv2Jz4h0ploCZF2f0o20ZtbQYkwfItJj6U3o3Wt2N4KhUxeeH2qZB7LQhjMMJG/B/GLc+oM3Br3g==","shasum":"b2563a50fbdf3c5915305af7b84897c01db367d0","tarball":"https://registry.npmjs.org/@caputchin/preset-melonjs/-/preset-melonjs-0.2.1.tgz","fileCount":15,"unpackedSize":40987,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCDwuQ1NSp8P77E15ubHeK0GhuOiUqYHzYSl2kLasrBiAIgDKuJquDZwolN2t1/uAOSYU7i86jTuJ8hw1+o87tXf7Y="}]},"_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-melonjs_0.2.1_1780593986138_0.47545849432964915"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-04T01:26:01.282Z","modified":"2026-06-04T17:26:26.449Z","0.2.0":"2026-06-04T01:26:01.665Z","0.2.1":"2026-06-04T17:26:26.295Z"},"bugs":{"url":"https://github.com/Caputchin/caputchin-sdk/issues"},"license":"Apache-2.0","homepage":"https://github.com/Caputchin/caputchin-sdk#readme","keywords":["caputchin","melonjs","preset","deterministic","replay","captcha"],"repository":{"type":"git","url":"git+https://github.com/Caputchin/caputchin-sdk.git"},"description":"Per-engine on-ramp for running a melonJS game headless and deterministically on the Caputchin replay platform.","maintainers":[{"name":"caputchiner","email":"info@caputchin.com"}],"readme":"# @caputchin/preset-melonjs\n\nThe per-engine on-ramp for bringing a [melonJS](https://melonjs.org) game onto the [Caputchin](https://caputchin.com) deterministic-replay platform.\n\nCaputchin runs a deterministic-replay captcha: your game records an input trace in the browser, the server re-runs the same simulation over that trace, and only the replayed verdict is trusted. melonJS is a coupled engine (its loop, physics, audio, and timers assume a browser), so running it headless and bit-for-bit deterministic on the server takes engine-specific care. This preset packages that care as tested, version-pinned code so you do not have to reverse-engineer it.\n\nIt is the framework-as-sim lane on-ramp; the shared mechanism it builds on splits across two kits: the fixed-step replay loop and `run` adapter live in [`@caputchin/engine-kit`](https://www.npmjs.com/package/@caputchin/engine-kit), the deterministic primitives and trap in [`@caputchin/determinism`](https://www.npmjs.com/package/@caputchin/determinism). This preset re-exports both, so a melonJS author has a single import site.\n\n## Install\n\n```sh\nnpm install @caputchin/preset-melonjs melonjs\n```\n\n`melonjs` is a peer dependency: your game pins and bundles its own copy, and the preset is tested against melonJS 19.\n\nThe full generated API reference for every export lives in [`docs/`](docs/README.md).\n\n## The one rule: import the determinism layer first\n\nmelonJS pulls in polyfills (core-js) that read `Math.random()` while their module *evaluates*, not only when your game runs. The server replay isolate bans ambient `Math.random` (a read throws), so the determinism layer must be installed BEFORE melonJS is imported, or the artifact never loads and the game self-rejects every player.\n\nIn your **headless replay entry**, make `@caputchin/preset-melonjs/install` the **first** import, before anything that pulls melonJS. It seeds `Math.random` (so the eval-time read is deterministic, not a throw), swaps the transcendental Math kernels, and installs the headless DOM shim + frozen clock:\n\n```ts\n// run.ts - the conforming replay artifact (caputchin.json `run.entry` pins it).\nimport '@caputchin/preset-melonjs/install'; // MUST be first\nimport * as me from 'melonjs';\nimport { defineMelonGame, toRun, type MelonGameSpec } from '@caputchin/preset-melonjs';\n```\n\nIn your **live (browser) entry**, make `@caputchin/preset-melonjs/live` the **first** import. It applies the SAME transcendental Math swap (so the player's browser and the server isolate compute identical floats) but keeps the real DOM and clock:\n\n```ts\n// game.ts - the live entry\nimport '@caputchin/preset-melonjs/live'; // MUST be first\nimport * as me from 'melonjs';\n```\n\nBoth are side-effect imports (no bindings). Never import `/install` in the browser build (it stubs the DOM) or `/live` in the headless build.\n\n## The headless sim (the part the server replays)\n\nWrite your game as a `MelonGameSpec` and wrap it with `defineMelonGame` + `toRun`. The preset boots a headless `me.Application` and drives `world.update` at a fixed step under the determinism trap; you build the scene and read the state back. The same spec runs the browser round and the server replay, so the live score equals the replayed verdict by construction.\n\n```ts\nimport '@caputchin/preset-melonjs/install'; // MUST be first\nimport * as me from 'melonjs';\nimport { defineMelonGame, toRun, type MelonGameSpec } from '@caputchin/preset-melonjs';\n\ninterface State { /* serializable game state */ }\ntype Action = { /* one player input */ };\n\nconst spec: MelonGameSpec<State, Action> = {\n  me,\n  width: 256,\n  height: 256,\n  setup(api) {\n    // Build your melonJS physics scene (me.Body bodies, collision shapes) into\n    // api.app.world; stash live refs in api.ctx; return the serializable State.\n    // Seed ALL gameplay randomness from api.rng (NOT Math.random).\n    return { /* ... */ } as State;\n  },\n  input(state, action, api) { /* apply one player input */ return state; },\n  afterStep(state, api) {\n    // The preset already advanced the physics (world.update) under the trap;\n    // read engine state (positions, collisions, score) back into State here.\n    return state;\n  },\n  isOver(state) { return /* round ended? */ false; },\n  result(state) { return { score: 0, passed: false }; },\n};\n\nexport const run = toRun(defineMelonGame(spec), { maxTicks: 2000 });\n```\n\n`defineMelonGame` does the engine-specific work for you:\n\n- boots a headless `me.Application` (CANVAS renderer, no GL); with `/install` it runs against the headless DOM shim + frozen clock in the replay isolate, while the browser keeps the real DOM;\n- runs every fixed step's `world.update` inside a determinism trap that feeds melonJS internals a seeded `Math.random` and a fixed tick-clock, restoring the real globals afterward, so live and replay never diverge;\n- draws the trap's randomness from a stream independent of your `api.rng`, so a melonJS version that changes how often it calls `Math.random` internally cannot shift your game's own randomness.\n\n## Determinism rules (non-negotiable)\n\nThe browser and the server must agree bit-for-bit, so inside your `setup`/`input`/`afterStep`:\n\n- keep the verdict-determining state integer or fixed-point (grid cells, integer sub-steps); do not let engine float physics decide the outcome;\n- draw all game randomness from `api.rng`, never `Math.random` or `me.Math.random`;\n- never read the clock, the network, or any host state;\n- collide with melonJS `getBounds().overlaps(...)` or integer cell tests, not floating tween positions.\n\nGet this wrong and your game false-rejects its own players. The preset's determinism trap is your safety net for melonJS internals, not a license to use nondeterminism in your own logic.\n\nVerify before you publish with `caputchin-selfcheck dist/run.js` (the CLI re-exported by [`@caputchin/engine-kit`](https://www.npmjs.com/package/@caputchin/engine-kit)): it runs the same determinism probe the platform runs at vendor time, so a non-conforming run fails on your machine instead of silently in production.\n\n## The live (browser) render\n\nThe headless sim above has no pixels. Your playable game is an idiomatic melonJS app that drives the SAME spec with `createMelonDriver` and records the same trace your sim replays:\n\n```ts\nimport '@caputchin/preset-melonjs/live'; // MUST be first\nimport * as me from 'melonjs';\nimport { createMelonDriver, encodeTrace } from '@caputchin/preset-melonjs';\n```\n\n1. `me.video.init(...)` against your container, with a responsive scale method so the canvas fills its parent on both axes.\n2. `createMelonDriver(spec, app, { seed, config })` returns the same fixed-step driver the server replays; run it from a manual loop (the preset trap-wraps each `world.update`), recording each input tick-stamped with `encodeTrace`.\n3. On pass / round end, forward the recorded trace to `bridge.pass({ trace })` via [`@caputchin/game-sdk`](https://www.npmjs.com/package/@caputchin/game-sdk).\n\n## API\n\n| Export | Purpose |\n|---|---|\n| `@caputchin/preset-melonjs/install` | side-effect import (headless): seeds `Math.random`, swaps Math, installs the DOM shim + frozen clock at module load, before melonJS evaluates. First import in the run entry. |\n| `@caputchin/preset-melonjs/live` | side-effect import (browser): the Math swap only, for browser/server float parity. First import in the live entry. |\n| `defineMelonGame(spec)` | adapt a `MelonGameSpec` into the headless `run` engine (pair with `toRun`). |\n| `createMelonDriver(spec, app, setup)` | the shared fixed-step driver, for the live mount. |\n| `toRun(engine, { maxTicks })` | build the conforming `run(seed, config, trace) => Verdict`. |\n\n## What this builds on\n\n- [`@caputchin/determinism`](https://www.npmjs.com/package/@caputchin/determinism) - the shared determinism layer every framework preset reuses: the seeded `rng` / deterministic `capMath` primitives, the `withDeterministicEnv` trap (seeded `Math.random`, deterministic transcendental math, fixed clock), and the headless DOM shim. The preset re-exports `rng` / `rngFromState` / `capMath` so your game needs no direct dependency on it.\n- [`@caputchin/engine-kit`](https://www.npmjs.com/package/@caputchin/engine-kit) - the fixed-step replay loop, the `toRun` adapter, and the `caputchin-selfcheck` CLI.\n- [`@caputchin/replay-contract`](https://www.npmjs.com/package/@caputchin/replay-contract) - the `run` / `Seed` / `Verdict` shapes.\n","readmeFilename":"README.md"}