Repository navigation
1.15: roadmap 29's modules lanes (collectible 1.1.1, Dungeon Kit + Dungeon Realms 2.0.0, Untangle 2.0.0, health + waves 1.0.0, football docs) - #9
Merged
Conversation
…realms plays it through the scene
- `dungeon` is now the Dungeon Kit 2.0.0 (esbuild-bundled from src/): the 9-stage generator
moved here from dungeon-realms with its node tests (gen/campaign, still green), a pure
`contract.js` builds the userData.play record (v2: raster + spawn-ordered rooms + world-space
props/portals/spawns + grounded + markers) on ONE persistent scene-root group 'dungeon-module'
whose userData.kit is the function seam (generate/showFloor/clear/setMarkers/setGrounded);
netcode generate/floor/clear + a {seed, params, floorIndex} state sync; `#dungeon-panel` is a
registered toolbox (api.registerToolbox, the SDK's worked example) with the DOM kept as the
fallback behind a feature-detect; the `dkdungeon` node IS the recipe and broadcasts nothing;
the key/door objective is gone (Realms is the game). Toolbox writes go to a live Dungeon node
when one owns the recipe (api.flow.setNodeData), otherwise the Kit's own op.
- `dungeon-realms` 2.0.0 is an OVERLAY: it reads the Kit through api.scene(), observes
{seed, floorIndex} every frame and rebuilds its own group 'dungeon-realms' (gems + portals from
the contract), drives travel through kit.showFloor (the Kit replicates), publishes gems/portals
as minimap markers through kit.setMarkers (DEVX #13), writes Game Rules > disableFlight as
play.grounded (DEVX #14 - the capture-phase Q/E swallow is deleted), gates on api.isPlaying()
(DEVX #11). `drhud` and `drdungeon` are deleted, not ported; the HUD is core HUD elements the
template authors, fed by the new `drvalue` (number out), `drrows` (rows into a HUD list by id)
and `drevent` (event out, fireNodeTrigger on the originating peer). The start/victory MENU stays
module DOM (DEVX #23, filed). Pure rules in rules.js with node tests.
- helpers.cjs: openModules matches the User tab by prefix - the label grows a count after the
first install, so a second install on the same peer timed out (found by the two-module flight).
- index.json: dungeon = Dungeon Kit (tool), both rows 2.0.0; AUTHORING.md lists the Kit as the
toolbox example and Realms as the inter-module-seam example; DEVX-REQUESTS notes what 2.0 retired.
Counterfactuals:
- contract.test: grounded default removed -> "a bare Kit publishes grounded:true" red; restored.
- rules.test: the `have` cap removed -> "have is capped at the total" red; restored.
- dungeon-realms flight: Game Rules disableFlight off -> play.grounded false on the next frame,
then true again (in-suite); a collected gem removes exactly one minimap marker on both peers.
- dungeon flight: a bare Kit publishes markers: [] (the "puts nothing" rule) - Realms adds them.
Suites (APP_URL 5216, both zips on every peer): tests/dungeon.test.cjs NEW 23/23;
tests/dungeon-realms.test.cjs 35/35 (old flight 22/22 as base); node tests: dungeon 45/45
(29 moved + 16 contract), dungeon-realms 20/20.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…n, radius on the form - the manager redraws on api.flow.onChange + api.game.onChange (core PR #224) instead of a 500ms interval; a clock runs only while a respawn countdown is on screen, an older recipe chain's latch value is re-read once 300ms after a change, and the panel re-reads on pointerenter (a rename in the viewport is not an event) - the recipe asks api.flow.freeRegion once per pair, so pairs stack under whatever is in the graph instead of on a row derived from collectibles.length (DEVX #16) - both seams are FEATURE-DETECTED: on a 1.14.0-shaped api the module keeps its 500ms poll and fixed rows (asserted by registering the same source against an api with the seams removed) - S4 plan items: the touch radius is on the form (disabled with the reason under a click trigger); a group header whose counts include older recipe chains says "+N older recipe chain(s) counted here (edit in the node editor)" - version 1.1.0 (manifest, module, index.json); DEVX-REQUESTS #16/#17 marked SHIPPED - flights: module-collectible 130/130 (+1 legacy-copy check; the R29 checks moved to their own file because the main flight sits near the runner's 8-minute cap — it was killed at 480s with them in), new module-collectible-signals 14/14, against core feat/29-sdk-polish on :5213 - counterfactual: forcing the poll branch -> "redraws on change signals" and "an idle manager touches nothing" red (12 DOM mutations in 2.5s) - S3 measured here too: one group press over 60 members = 60 nodedata messages, 7.1ms, 0 undo entries -> the batch seam stays held (see core PR #224) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e as a node, the core hud fed by realms nodes
- `modules/dungeon-realms/src/def.js` + `emit-def.mjs`: `npm run build:dungeon-realms` now also emits
`dungeon-realms.def.json`, the def core's scripts/author-templates.cjs reads (the football shape,
PR #218). `modules: [{id:'dungeon'}, {id:'dungeon-realms'}]` - the two-module case the requirement
list exists for. The graph: a Number feeds the Dungeon Kit node's seed (1337, apply on) wired to the
'Entrance plinth' Object Selector, Game Rules / Start Menu / Prop Counter, Realms Value -> HUD Text
(gems, need, level, levels, players), Realms HUD Rows -> HUD lists (objective, players), the gem
event -> Counter -> HUD Text, Realms Event start/victory/reset -> Set Game State, P pause / Resume /
Quit. The HUD document: menu / hud / pause / over screens. Objects: the entrance arch (plinth, two
pillars, lintel, lantern, light), placed by the emitter at the entrance room seed 1337 produces with
the Kit's own generator, so players spawn under it. Night env, click + grounded play block, AGX +
bloom + smaa post, `thumb.sceneGroups: ['dungeon-module']` so the card shows the world.
- The world is regenerated from the graph's Dungeon node on every load: the file carries the recipe,
never the level data (golden rule 5 - scene-root content is not saved).
Counterfactuals (core suite game-dungeon-realms, 64 checks, against the authored scene):
- game.js no longer pulses the start event -> 3.7 "the On start stamp landed in the TRIGGER LOG" red
(and 3.8-3.10, 3.14, 5.2 downstream); restored, module.js byte-identical (md5).
- in-suite: a menu-seeded dungeon (777) stands BEFORE the load; after applySession the Kit shows seed
1337 from the node (the /clear all -> node ordering); the replicated gem event counts ONCE on B.
Authored on 2026-09-19 against 5216: scene 9169 B, thumb 3848 B, staged for the integrator in
cloud-lane-29-staging/games/dungeon-realms/.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e, value/event nodes, the def - `modules/untangle` 2.0.0 is bundled from src/ (esbuild) so the puzzle is a PURE, node-tested module (`puzzle.js`: mulberry32, generate, chordsCross, segsCross, edgeCrossings - 10 checks incl. solvability over 40 levels). Positions are board units (the unit disc) so a radius change re-renders without touching the replicated model. - `utboard` "Untangle Board" (level, radius, boardY, x, z, yaw, autoAdvance, apply): the drdungeon shape, rule ownership. Its level is the STARTING level - applied when the node's value changes (so autoAdvance can move on) and never on first sight over a landed state sync. THE INSTALL GATE: with no node in any graph the module waits EXPIRE_FRAMES and then falls back to today's behaviour (a level-1 board on its own); with a node, the node decides. A scene clear (applySession runs /clear all FIRST) resets to level 1 and re-arms the node so it re-applies on the next tick even when its data did not change. - The canvas SPRITE HUD is the VR-only path (api.isVR()); on desktop the template's HUD Text reads `utvalue` (level, crossings, solved, dots, edges, count) and `utevent` (solved, level) is pulsed with fireNodeTrigger on the peer that dropped the solving dot. - Golden rule 7: the module-state exchange on connect is SYMMETRIC, so an UNTOUCHED fallback board answers getState with null - a joiner's fresh level-1 scramble used to overwrite the room's game (found by the new flight's late-joiner check). - `src/def.js` + `emit-def.mjs` -> `untangle.def.json` (`npm run build:untangle`): the thin, honest template - a room (floor, back wall, the pedestal the node targets, a ring frame, two lamps), the board node at level 2, value readouts into HUD Text, the solved event into a Counter, Start / P pause / Quit, a 3-screen HUD, sunset env, `thumb.sceneGroups: ['untangle-module']`. The file carries the LEVEL, never the positions. - index.json: untangle 2.0.0 (no `template` field until the scenes release - the staged index row has it). Counterfactuals: - puzzle.test: the non-crossing chord filter removed -> "the ring layout solves every graph (0/40)" red. - tests/untangle.test.cjs (25 checks): the joiner's untouched board did NOT overwrite A (in-suite, was a real red before the null-state fix). - core suite game-untangle (46): onSceneClear no longer re-arms the node -> 1.12 "the same file loaded again ... the node re-applied LEVEL 2 (a board exists)" red; restored, module.js byte-identical (md5). Authored on 2026-09-19 against 5216: scene 8946 B, thumb 2802 B, staged in cloud-lane-29-staging/games/untangle/. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…t, DEVX #15 yes - AUTHORING.md section 5: a Knock row (`onHit(cb)` - every knock this peer sees, with its fields, returns the unsubscribe; `hitLog()` - a copy {last, recent}, runtime state), football named as the game-SDK worked example, and an "onHit fires on every peer" note (bump a per-player counter only for your own hit, or every peer banks it). - README.md module table: the football row (it is where the module table lives). - DEVX-REQUESTS.md #15: "yes" in the gap table, and a #15 row in the core-status table (YES, per 24-B D2: api.peerIds() diffed each second is the disconnect signal, football does exactly this; api.peerNames() the name half). - checked: npm run test:football green; the football flight (npm test -- football) 91 PASS against core 1.14.0 + lane 29-football. Docs-only; the other 16 flights not rerun. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… for players - the `health` module (modules/health, bundled from src/ like football): a `health` effect node (max, scope object|player, regen, deathAction hide|respawn|nothing, respawnDelay; inputs damage/heal numbers + respawnAt object), `damage` / `heal` / `healthreset` / `healthevent` event nodes, a `healthvalue` value node (fraction/current/max/alive), the manager toolbox (one chain per selected object or one player health, ONE undo entry each, live rows), a debug line and two HUD actions (bar + hit points) - fork 2 of roadmap 29 as built: an object's hp is DERIVED from core Counters fed by damage pulses (one trigger-log entry per pulse, applied once per peer; the count rides the DEVX #18 handshake so a joiner converges); a player's hp is their own peerVars row {base, at} with regen a pure function of the clock. No authority, no timer sends, no registerStateSync - the one rule for sources: a replicated stamp is fired LOCALLY by every peer, a source only this peer saw (its click) is fired REPLICATED; first sight never fires; the round reset seeds on the first sight of the game shell (a joiner mid-round must not zero the counts it was just handed) - node tests (npm run test:health, 51 checks): ledger + graph walk + recipe, with the shared-add counterfactual in the ledger test - flight tests/module-health.test.cjs (74 checks, two peers + a late joiner): recipe/undo, convergence from two peers, the dead-hit guard, healthvalue + debug line, joiner reads the same numbers and re-applies nothing, per-player rows, regen, death fires once, respawn, a new round restores, heal, the manager rows - counterfactuals: click pulses fired locally instead of replicated -> 8 checks red (B reads 3/3, the kill never reaches the peer, the joiner's hit reaches nobody); round reset acting on the first sight of a round -> the joiner zeroed its counters and read Crate1 alive (seen live before the seed rule); a read-modify-write add from two peers converges on 1 hit (in-flight counterfactual, the ledger read 2) - suites: module-health 74/74; test:health 51/51; core untouched (no seam needed: the spike proved a module authors and pulses core's spawn node) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tNodesData - bulkApply collects the members that differ and hands them to core's api.flow.setNodesData (core R29 S3b) in one call: one Ctrl+Z undoes the whole group change. Feature-detected - on a core without it the module writes per node exactly as before. Members that already agree are still skipped; the wire is unchanged (one nodedata per member). - README: the group-edit paragraph and the toolbox row say so. v1.1.1. - module-collectible-signals section 12: a 20-member group press is ONE flownodes data entry of 20 items attributed to `collectible`, replicates to the peer, one undo restores all twenty on both peers; the 1.14-shaped api (setNodesData removed) still flips the whole group per node. - Counterfactual: batch call disabled (forced fallback) -> 3 red (17 -> 37 entries, one undo restored one member). - module-collectible-signals 21/21 (base 14/14); module-collectible 130/130 (base 130), against core feat/29-s3-followup on :5213. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…at a spawn point - `damage.source`: `hit` rides core's knock feed (api.onHit: one `hit` message, every peer fires its own LOCAL pulses, `scale: speed` = amount x speed/speedRef capped 3x, floored at one point); `touch` is the self-proximity EDGE on the health's object (the toucher fires REPLICATED pulses, leave-and-return touches again); `zone` hurts the local player `amount` every 1/perSecond while inside `radius` of the object wired into `damage.zone` (local + the row). Only a peer in play detects touch/zone - respawn: a player comes back at full on their row and `api.flyTo` flies the EDITOR camera to the object wired into `respawnAt`; in play the rig owns the camera and no seam moves the player (DEVX #23, owed) - the flight proves the editor case - `health` is a VALUE node (its hp) with a `target` object input, NOT an effect: the runtime re-seats an effect target's base pose every frame (football's "no node may target the ball"), which pinned a damageable crate in place the moment the host was in play - observed in the flight (a replicated move did not stick). The engine hides a dead object itself, in play only, and gives back exactly what it hid (21-F2 by hand) - the toolbox form gains Radius; engine.test.mjs runs the engine against a fake api (click/wired/first-sight/reset, the joiner round rule, touch/zone/respawn, the knock feed with its stamp de-dupe) - counterfactuals: touch fired while the peer moved the CAMERA -> nothing (the play rig re-seats the camera at its own spot, so the flight moves the object instead); the health node as an effect -> the crate could not be moved under the player in play, zone never fired (13 red across two runs); a knock stamp replayed -> dropped (unit); a stamp present at first sight -> no pulse (unit); a hit on the dead -> no pulse (unit) - suites: module-health 99/99 (two peers + joiner: knock via a real probe sweep, touch on the peer, zone + death + respawn on the host); test:health 81/81 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…derived from counters - `modules/waves` (src/ bundled like football): a `waves` value node (the current wave; goal object input; the size curve, interval, walk speed/stagger, reach, the spawn-point name prefix), `wavesvalue` (wave/left/size/waves/done), `wavesevent` (start/wave/over, local on every peer), the arena toolbox (enemies from the selection or N boxes; per enemy the health chain + a heal chain + a zone chain into the player's health; the Waves node with its goal; over -> Set Game State; the player health), a debug line, two HUD actions, hud.js (menu/hud/over + the driver graph) and waves.def.json (the template def, author-script shape, staged) - THE LEDGER TRICK: enemies are pre-placed objects with the health module's chains; their hits never reset between waves - each wave every peer fires LOCAL heal pulses (idempotent against the heal counter and a per-node expectation, since nodeValue republishes ~6/s), so a hit counter reads hp x kills and the wave is a pure function of the counters, which a late joiner gets in the handshake - enemies walk spawn -> goal as a pure function of (the wave's start stamp: the round's startedAt in seconds, or the previous wave's last kill + interval; the clock), written locally on every peer; parked poses restored when no run is on - over: every peer fires the local `over` pulse (-> the game shell) and appends the SAME run entry to gameState.vars['waves:<name>'].runs (keyed by the last kill's stamp, idempotent, capped 50) - the football log precedent without an authority - health: the peer that dealt a killing blow (its click, its own hand's knock) bumps its own `kills` peerVars row, read off the last sweep's number (flowValues is ~6 Hz) - tests/helpers.cjs: the Modules > User tab locator is /^User/ (an exact "User" hung a second install on one peer: the tab reads "User (1)") - node tests test:waves 54/54 (the curve with its counterfactuals: a straggler keeps the run open, later kills do not skip a wave; the recipe, the HUD graph, the def byte-identical twice); test:health 84/84 (kill credit, mine vs another hand) - flight tests/module-waves.test.cjs 59/59, two peers + a late joiner: the arena from the toolbox, wave 1 walks (B places the enemy where A does), two peers clear it and the survivors heal into wave 2, the joiner reads wave 2 and the same ledger, three peers clear wave 2 -> done/over on all three, kills 2/2/1, one identical run entry on A, B and C, a new round zeroes everything - counterfactuals: heals into the next wave removed -> 12 red (the ledger, done on every peer, over, the kills, the log); the round stamp compared in ms -> the wave never started (seen live, fixed by one conversion); heals fired again before the value republished -> double heals and uncredited kills (seen live, fixed by the expectation); held: module-health 99/99 after the engine change - the spawner is not used (spike: a transient copy has no nodes, pulses carry no payload, copies live only while the sim runs); enemies are pre-placed (Towers B8) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…or health + waves - index.json: `health` and `waves` rows (category game; no `template` field - the waves def is staged for the integrator, not released here) - README.md: the two modules in the table - AUTHORING.md: both in the worked examples; friction-log entries an effect node pins its target's pose, the two clocks (seconds of day vs the round's ms), nodeValue is ~6 Hz, the /^User/ tab locator - DEVX-REQUESTS.md: #23 api.teleportPlayer (no seam moves the play-mode player), #24 a synchronous trigger count / live Counter read, #25 the two clocks on the api - zips: npm run pack -- --all --force packs 20 modules incl. health.zip and waves.zip Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # DEVX-REQUESTS.md
# Conflicts: # package.json # tests/helpers.cjs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Roadmap 29's finished modules lanes, integrated on
devin stack order (#4 → #7 → #6 → #5 → #8), conflicts resolved as unions (DEVX-REQUESTS rows 15 vs 16/17, package.json scripts, one helpers.cjs comment).What landed
onChange) instead of polling, lands onfreeRegion, group edits are ONE undo step (setNodesData); flights module-collectible 130 + module-collectible-signals 21.dungeon2.0.0 = the Dungeon Kit (generator, contract, toolbox, node),dungeon-realms2.0.0 = the game as an overlay through the scene,dungeon-realms.def.json+untangle2.0.0 +untangle.def.json(the template defs core's author script reads).tests/helpers.cjs:openModulesmatches the User tab by/^User/.Gates (this integration, against core
feat/1.15on :5219)Node tests: dungeon-realms, untangle, health, waves, football — ALL PASS.
npm run pack -- --all --force→ 20 zips.Flights under the lock: dungeon 24 · dungeon-realms 35 · untangle 25 · football 89 · module-collectible 130 · module-collectible-signals 21 · module-health 99 · module-waves 59 — ALL FLIGHTS PASS.
Pre-existing (A/B'd against 1.14.0 by the sdk lane): door-keypad, sabers, template-install, tutorial-room time out on their own
exact: trueUser-tab locator on the manager's second open.Release order: these zips must reach
main(the gallery CDN) BEFORE scenes'v2tag moves, or the Dungeon Realms card installs 1.x.🤖 Generated with Claude Code