Skip to content

feat(core): world providers for game-owned simulations, and optional physics - #39

Open
cmaycumber wants to merge 3 commits into
elemarin:mainfrom
cmaycumber:feat/world-providers
Open

cmaycumber wants to merge 3 commits into
elemarin:mainfrom
cmaycumber:feat/world-providers

Conversation

@cmaycumber

Copy link
Copy Markdown

Some games keep their state in their own simulation (run as a renderoni system) rather than as renderoni entities. For those games the MCP tools saw nothing: describe/observe listed only entities, check resolved only entities.* paths, and the state hash never changed.

World providers

engine.worlds.register({ name, describe?, observe?, resolve?, hash? }) feeds all of them:

  • describe: worlds.<name>. The key is present only when providers are registered.
  • observe: tier 0 gets ## <name> sections. Entities keep up to half the budget, and unused provider bytes flow back to them.
  • check: world.<name>.<path> paths go through resolve(), with explicit failures for a missing provider, a missing resolve, or a throwing resolve.
  • getStateHash: provider digests are appended in name order. With no providers the digest is unchanged, and a test pins it against main.
  • Diagnostics: RND_0410, RND_0411 and RND_0413.

Optional physics

createRenderoni({ physics: false }) never loads Rapier. Rapier is now a cached dynamic import(), so bundlers split the WASM into its own chunk. Presets take it from ctx.native.rapier. The default engine is unchanged. A physics-free consumer bundle drops from about 2.2 MB to about 117 KB.

prepare script

Git installs (npm install github:user/renderoni#branch) now build dist.

Question for you: with physics: false, Rapier could become an optional peer (peerDependenciesMeta). Our fork does this, but it conflicts with the package-contract test that requires it, so this PR leaves the contract alone. I'm happy to add it here if you want it.

Verification: typecheck, build, and the full test suite pass. I'm using this in a real game (Sunder March, a Kenshi-like) whose whole simulation is exposed over MCP through a world provider.

🤖 Generated with Claude Code

cmaycumber and others added 3 commits September 24, 2026 18:37
Games whose state lives in their own simulation (run as a renderoni system)
were invisible to agents: describe/observe listed only entities, check only
resolved entities.* paths, and the state hash never changed.

engine.worlds.register({ name, describe?, observe?, resolve?, hash? }) now
feeds all of them:
- describe: worlds.<name> = describe() (key present only with providers)
- observe tier 0: "## <name>" sections after the entity section; entities
  reserve up to half the 500-byte budget, providers split the rest evenly,
  unused provider bytes flow back to entities
- check: world.<name>.<path...> via resolve(), with explicit failures for a
  missing provider, missing resolve() or a throwing resolve()
- getStateHash: provider digests appended in name order; with no hashing
  providers the digest bytes are unchanged (pinned against upstream main)

Diagnostics: RND_0410 invalid provider, RND_0411 duplicate name,
RND_0413 invalid hash value.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
createRenderoni({ physics: false }) / new RenderoniEngine({ physics: false })
never load Rapier. The default engine is unchanged.

- physics.ts imports Rapier through a cached dynamic import() (loadRapier),
  so bundlers split the ~2 MB inlined WASM into its own chunk. Every other
  module uses `import type` only.
- Presets build descriptors from ctx.native.rapier (the loaded module)
  instead of a static import.
- With physics disabled, step() skips the physics step, contact/sensor
  processing and the canonical re-sync; engine.native.world and
  ctx.native.rapier throw RND_0412 (physics presets fail before creating
  anything); getStateHash works with no contacts.

A physics-free consumer bundle (esbuild, esm + splitting, three external)
goes from one 2,206,271-byte chunk containing the WASM to a 116,789-byte
entry plus a 2,101,004-byte Rapier chunk that is never loaded.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`npm install github:<user>/renderoni#<branch>` now runs `prepare`
(`npm run build`), producing dist/ from source. The build does not need the
network or the optional @github/copilot-sdk: the editor imports it through
a non-literal specifier, so tsc and the tsup DTS pass no longer try to
resolve its types when it is not installed.

prepare sends the build log to stderr because npm also runs it for
`npm pack --json`, whose stdout the release contract gate parses as JSON.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant