Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9e24814
[feat] 23-C1: music-lab - a Speaker and a Piano on the engine
AlexZ005 Sep 4, 2026
54d6841
[feat] 23-C2: music-lab - the beat lab (transport, drum machine, samp…
AlexZ005 Sep 4, 2026
18c9017
[feat] 23-C3: music-fx - a mixer and five pedals on the engine
AlexZ005 Sep 4, 2026
7d453de
[feat] 23-C4: music-dj - two decks and a crossfader on the engine
AlexZ005 Sep 4, 2026
4d9cbd7
[feat] 23-D2: music-lab and music-dj declare their samples to the Sce…
AlexZ005 Sep 4, 2026
b911de6
[test] 23-D3: music-jam-room - the Jam Room template's whole path on …
AlexZ005 Sep 4, 2026
e8bd67f
[feat] 23-C5: music-voice - mic, looper, synth and theremin on the en…
AlexZ005 Sep 4, 2026
8df6017
Merge branch 'lane-c3' into dev
AlexZ005 Sep 5, 2026
aacef5f
Merge branch 'lane-c5' into dev
AlexZ005 Sep 5, 2026
dfedfe4
[fix] dev: one index row per module
AlexZ005 Sep 5, 2026
4926355
[feat] 24-B1: football module - rules, teams, modes, attribution, ser…
AlexZ005 Sep 12, 2026
f758ebd
[feat] 24-B2: physical ui for vr and the dom hud from one set of nodes
AlexZ005 Sep 12, 2026
a6fc86d
[feat] 24-B3: colocation fit - pitch to room, centre on the anchor, s…
AlexZ005 Sep 12, 2026
d91c768
[feat] 24-B4: the football def and the test-flight on two peers plus …
AlexZ005 Sep 12, 2026
5fd78df
[feat] 24-B5: records - session rows on peervars, the saved match log…
AlexZ005 Sep 12, 2026
62b1539
[fix] 24-B: the flight parks before a goal, and a delay bridges the h…
AlexZ005 Sep 16, 2026
722c041
Merge pull request #3 from theprototype-app/feat/24-football
AlexZ005 Sep 18, 2026
9d40c2d
[feat] C6-a: dungeon kit + dungeon realms split - the kit generates, …
AlexZ005 Sep 19, 2026
5b68056
[feat] S4: collectible listens instead of polling, lands on freeRegio…
AlexZ005 Sep 19, 2026
f20961a
[feat] C6-b: the dungeon-realms template def - two modules, the recip…
AlexZ005 Sep 19, 2026
6232662
[feat] C7: untangle as a template - the board node, the vr-only sprit…
AlexZ005 Sep 19, 2026
bb7a407
[docs] 24-b r3: football row, api.onHit / api.hitLog in the api diges…
AlexZ005 Sep 19, 2026
fce0ba5
[feat] H1: health module - hp from counters for objects, own peer row…
AlexZ005 Sep 19, 2026
25ccc1a
[feat] collectible: a group edit is ONE undo step through api.flow.se…
AlexZ005 Sep 19, 2026
e7d9850
[feat] H2: damage sources - hit, touch, zone, speed scaling, respawn …
AlexZ005 Sep 19, 2026
c31eade
[feat] H3: waves module - wave survival composed on health, the wave …
AlexZ005 Sep 19, 2026
567647c
[docs] H4: index rows, README table, AUTHORING notes and DEVX 23-25 f…
AlexZ005 Sep 19, 2026
8b1b66c
Merge feat/29-sdk-collectible into dev (integrate 1.15)
AlexZ005 Sep 19, 2026
13cb972
Merge feat/29-s3-collectible into dev (integrate 1.15)
AlexZ005 Sep 19, 2026
4ea523e
Merge feat/29-football-docs into dev (integrate 1.15)
AlexZ005 Sep 19, 2026
2a76d8f
Merge feat/29-c6-dungeon into dev (integrate 1.15)
AlexZ005 Sep 19, 2026
e087134
Merge feat/29-health-waves into dev (integrate 1.15)
AlexZ005 Sep 19, 2026
090c16f
Merge pull request #9 from theprototype-app/integrate/1.15
AlexZ005 Sep 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 36 additions & 2 deletions AUTHORING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,22 @@ be read start-to-finish by a person **or** pasted whole into an AI assistant.
[`_template`](modules/_template/) (start here) ·
[`door-keypad`](modules/door-keypad/) (replicated objects, a discrete event, a
deterministic animation from one timestamp, late-joiner state) ·
[`dungeon`](modules/dungeon/) (**the worked toolbox example**: `api.registerToolbox`
with a `#dungeon-panel` DOM fallback behind a feature-detect, a node that IS the
recipe, and a published `userData.play` contract other modules read) ·
[`dungeon-realms`](modules/dungeon-realms/) (a game as an OVERLAY on another module:
two modules cannot share code, so they share the scene — the Kit's
`userData.play` / `userData.kit` seam, read through `api.scene()`) ·
[`tutorial-room`](modules/tutorial-room/) (derived content: local geometry,
replicated *intent*) · [`sabers`](modules/sabers/) (per-frame pose streaming,
VR and desktop from one code path) · [`fps-player`](modules/fps-player/)
(input claims, `possess`, capability probing) ·
[`flow-toolkit`](modules/flow-toolkit/) (flow nodes) ·
[`untangle`](modules/untangle/) (a full replicated game).
[`untangle`](modules/untangle/) (a full replicated game) ·
[`health`](modules/health/) (a number several peers can lower, converging with no
authority: pulses counted by core's Counter, a player's own peer row, the first-sight
rule) · [`waves`](modules/waves/) (a mechanic COMPOSED on another module's node types,
the wave derived from counters, a run log every peer appends identically).
- Missing something? [DEVX-REQUESTS.md](DEVX-REQUESTS.md) tracks known SDK gaps —
check it before you work around one.

Expand Down Expand Up @@ -246,18 +256,24 @@ Full signatures live in the docs site; this is the map.
| Netcode | `send(payload)`, `onMessage(fn)`, `registerStateSync({getState, applyState})`, `peerId()` |
| Input | `registerBindings(list)`, `input()`, `onInput(fn)`, `claimInput(scope)`, `releaseInput(scope)` |
| Physics | `physics.isInitiator()`, `applyImpulse`, `applyTorqueImpulse`, `setJointMotor`, `joints()` |
| Knock | `onHit(cb)` — every knock this peer sees (its own hand's and every peer's `hit`) as `{uuid, by, at, speed, point, linvel, angvel, probe, local}`; returns the unsubscribe, torn down with the module · `hitLog()` — a COPY `{last, recent}` (last hit per live body, the last 32 in order; runtime state, a late joiner's starts empty) |
| Player | `possess(uuid, {camera})`, `releasePossess()`, `selectedUuid()` |
| UI | `registerMenu(label, action)`, `registerVRMenuEntry(entry)`, `toast(text)` |
| Misc | `THREE`, `assetUrl(path)`, `now()`, `sceneAssets()` |

Two that are easy to miss:
The worked example for the game SDK (`api.game`, `api.peerVars`, `onHit`, `registerStateSync`, `registerToolbox`) is [football](modules/football/): rules as flow nodes, last-touch attribution evaluated BY EACH PEER from `onHit` (no module message for a touch), the physics initiator as the one goal authority, and each player's goals written to their OWN `peerVars` row.

Three that are easy to miss:

- **`registerClickHandler` covers VR too.** You do not write a second input
path for the headset; the trigger dispatches through the same handler with the
exact mesh that was hit. Return `true` to consume the click (no selection).
- **`claimInput('keys' | 'locomotion')` pauses the editor's own consumers** so
your WASD does not also fly the camera. Always release it when your mode ends,
including on error paths.
- **`onHit` fires on EVERY peer for every hit.** Attribution derived from it is
deterministic without a message of your own; a per-player counter must still bump
only when `hit.by === api.peerId()` (or `hit.local`), or every peer banks it.

---

Expand Down Expand Up @@ -391,6 +407,24 @@ list when something bites you.
- **`api.input()` fires while the user is typing in a panel.** Claim the scope
(`claimInput('keys')`) only while your mode is active, and ignore input when
it is not.
- **An EFFECT node pins its target's pose.** The runtime re-seats an effect target's base
pose every frame while the effect is active (in play), so a `registerEffect` node on an
object that must move — a knocked crate, a walking enemy — fights every move, replicated
or not (football's "no node may target the ball"). If your node only needs to *know* its
object, make it a `registerValueNode` with an `{inputs: {target: 'object'}}` socket and
wire the Object Selector IN; hide/show the object yourself and restore only what you hid
(`health` does this).
- **Two clocks.** `api.now()` and every trigger-log stamp are seconds of day on the synced
clock; `api.game.roundCutoff()` (the round's `startedAt`) is session **milliseconds**.
Convert (`(ms / 1000) % 86400`) before comparing, and never compare either to
`performance.now()`. A joiner's `api.now()` also re-bases on connect — decide "did I
witness this" by identity (the collectible's first-sight rule), never by clock.
- **`api.flow.nodeValue` is ~6 Hz.** The live values republish every 150 ms, so a Counter
you just pulsed still reads the old count for a moment. Firing again "because the count
has not moved" doubles the pulse; remember what you fired (`waves` keeps a per-node
expectation) or derive from the last sweep's numbers (`health`'s kill credit).
- **A second `installModule` on one peer needs the `/^User/` tab locator** — after an
install the tab reads "User (1)" and an exact match hangs (fixed in `helpers.cjs`).
- **Capping `dt` turns a slow frame rate into slow motion.** `Math.min(dt, 0.1)`
is the right way to stop a physics step tunnelling, but at 7fps (headless
Chromium, a background tab) it means sim time advances at 0.7x — a jump that
Expand Down
Loading