Requests from module authors to the core module SDK
(theprototype-app/core, src/lib/moduleSDK.js). Each one was hit while
building a module in this repo, verified against the SDK surface as it actually
is, and worked around rather than patched around — a module never reaches into
core internals, because an installed module has no imports and core-only
privileges are exactly what makes an example useless as an example.
Feeds roadmap 17-A1 (SDK gaps). Status is what the modules need, not a promise from core.
| # | Gap | Blocks | Worked around? |
|---|---|---|---|
| 1 | first-person camera for possess |
fps-player |
partly — third-person walk mode ships, first person does not |
| 2 | per-hand VR pose + button state | sabers |
partly — one hand via pointerRay() |
| 3 | api.haptic(hand, intensity, ms) |
door-keypad, sabers |
yes — silent, no feedback |
| 4 | fire a flow trigger on an object | door-keypad |
yes — module ops only, flows can't react |
| 5 | create/move a replicated object from module code | tutorial-room, fps-player |
yes — own scene-root content + own ops |
| 6 | know whether we are in VR | sabers |
yes — inferred from pointerRay() shape |
| 7 | multi-selection uuids | — | yes — selectedUuid() is enough so far |
| 8 | BUG: api.onInput misses keys for ~2s after register |
fps-player |
yes — edge-detect from api.input() in a frame task |
| 9 | dungeon-realms |
SHIPPED (core 21-A1 + R3a; verified by 36-dataflow) — api.registerValueNode(type, fn, {vtype, inputs}) outputs values (incl. vtype: 'event'), api.fireNodeTrigger(type, match, opts) fires pulses, module effects get {id, graphId, trigger} as a 5th arg; the 1.23 value graph memoizes them once per tick |
|
| 10 | dungeon-realms |
SHIPPED (core 1.23, 36-dataflow) — api.flow.seedGraph({key, graphId?, nodes, edges}): addNodes (replicated, one undo entry) but ABSENT-ONLY — nothing is added once any node carries the seed mark data.seed = '<module>:<key>' or is one of the module's own node types |
|
| 11 | play-mode signal (api.isPlaying() / onPlayMode) |
dungeon-realms |
yes — observes #dungeon-minimap visibility (brittle DOM) |
| 12 | text param kind for module nodes |
dungeon-realms |
yes — button labels derive from their action select |
| 13 | play contract is name-keyed to 'dungeon-module' |
dungeon-realms |
yes — squats the core module's group name |
| 14 | grounded (no-fly) play-mode option | dungeon-realms |
yes — window-capture swallows Q/E while the game runs |
| 15 | peer roster + disconnect hook | dungeon-realms, football |
yes — api.peerIds() (the replicated roster) diffed each second frees a vanished peer's slot; football does exactly this. The name half is api.peerNames() |
| 16 | api.flow.nodes() carries no node POSITION |
collectible |
SHIPPED (core PR #224) — snapshots carry x/y and api.flow.freeRegion({w, h, graphId}) answers where a block lands; the recipe uses it and falls back to its fixed rows on an older app |
| 17 | collectible |
SHIPPED (core PR #224) — api.flow.onChange (graph + trigger log), api.game.onChange, api.peerVars.onChange, coalesced to one call per frame; the manager listens and polls only on an older app (the ~10Hz touch/count sweep stays: it watches positions and the clock) |
|
| 18 | collectible |
SHIPPED — gettriggers/triggers carries the log; the module gained a first-sight rule so arriving history is not banked |
|
| 19 | api.game.setState(state, outcome) — a module cannot move the game shell |
football |
yes — a Football Event node fires start/over/reset and the template wires it to Set Game State |
| 20 | a scene-physics block write (api.physics.setScene({gravity, knock, …})) |
football |
yes — the template carries the block; the "Build pitch" recipe toasts the Inspector rows to set |
| 21 | the XR room bounds and the colocation roomAnchor on the api |
football |
yes — "Fit to room" / "Centre on room" feature-detect api.xrReferenceSpace / api.colocation.roomAnchor and fall back to sliders |
| 22 | hudbutton has no runtime value |
football, dungeon-realms (30), waves |
SHIPPED (core 1.23, 36-dataflow) — hudbutton (and ongamestate) now evaluate to the onclick pulse window and hudtimer to its remaining seconds; a HUD Button wired straight into a module input reads 1 for the press. The Delay bridge still works and can go |
| 23 | dungeon-realms |
ANSWERED by core — a HUD screen with input: 'menu' frees the pointer and core's HUD ring gives arrows/Enter/gamepad A; Dungeon Realms moved its Start/victory menu there in roadmap 30 (the DOM card stays for a graph with no HUD, drmenu show) |
|
| 24 | Live values are ~6 Hz, and a pulse's count cannot be read back synchronously | — | see the request |
| 25 | Two clocks on the api | — | see the request |
| 26 | no per-element HUD visibility (show a list only when it has rows, a panel only while…) | waves |
yes — a list with bg: 'transparent' draws nothing while it is empty |
| 27 | a template cannot place the desktop play spawn (outside a dungeon it is core's fixed [0, 2, 3\], whatever view says) |
football, waves |
yes — the football lamp strip moved onto the crossbar so the spawn's eye line stays clear; the ask is #23's teleportPlayer, or a def/scene play.spawn |
| 28 | style.align unless it wraps |
football, dungeon-realms, waves, untangle |
FIXED in core 1.17 — the text row takes justify-content from align (30-visuals-core); the 30 menus stay left-aligned (still right on 1.16), untangle's titles are wide centred boxes |
| 29 | no pointer seam (pointerdown/up, click-miss) and no module dispose hook | untangle |
yes — window CAPTURE listeners, self-detaching when a newer copy owns the module's hook |
| 30 | api.game cannot say which non-running state (menu vs over) |
untangle |
yes — the rising edge of roundUnderway() is enough for "Next" |
| 31 | no camera on the api (the crosshair ray under a lock) | untangle |
yes — pointerRay().camera, else the scene camera nearest playerPosition() |
| 32 | a module HUD kind's mount gets no editor flag |
untangle |
yes — el.closest('#hud-layer') tells the runtime layer from the artboard |
| 33 | the VR trigger has no editor mode — a {modes} game piece still eats it in the headset editor |
every clickable module | no — owed on device; modules already pass modes, so the fix is core-only |
| 34 | registerListedGroup cannot ask to be PICKED by the Edit select |
dungeon, sabers |
yes — the Kit registers as an INTERACTIVE group (joining every tap's raycast) |
| 35 | a module cannot read the editor's click mode (api.editorMode()) |
car, essentials |
yes — hints say "press I (Interact)" blind |
| 36 | api.loadModel(url) / api.GLTFLoader) |
waves (30b, 30c), dungeon (torch bake) |
yes (30c) — build-gltf.mjs bundles three's GLTFLoader + SkeletonUtils against a shim of the RUNTIME three (globalThis.__wavesTHREE), imported from a blob: +45 kB per module that does it, and it must track core's three version by hand |
| 37 | no way to hide an object's look without touching it (a module-drawn stand-in: a rigged figure over a capsule enemy) | waves (30c) |
yes — the object's meshes hop off layer 0 only between the scene's onBeforeRender and onAfterRender (chained). A persistent hop is NOT safe: the .tpscene save is toJSON, which writes layers. The card (author script) renders a toJSON clone, so there the capsules show under the figures. Ask: a per-object userData.renderHidden core honours in its renders only |
| 38 | A module cannot pause VR snap turn (or teleport) while it owns a stick | — | see the request |
| 39 | A module cannot reseat a dynamic body (put the ball back on the centre spot) | — | see the request |
| 40 | A squeezed grip silences the hand's knock | — | see the request |
| 41 | The per-frame reads a game needs allocate, and "a game view" is re-derived everywhere | — | see the request |
| 42 | No way to move the PLAY-MODE player | — | see the request |