Skip to content

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
AlexZ005 merged 15 commits into
devfrom
integrate/1.15
Sep 19, 2026
Merged

AlexZ005 merged 15 commits into
devfrom
integrate/1.15

Conversation

@AlexZ005

Copy link
Copy Markdown
Contributor

Roadmap 29's finished modules lanes, integrated on dev in 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

Gates (this integration, against core feat/1.15 on :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: true User-tab locator on the manager's second open.

Release order: these zips must reach main (the gallery CDN) BEFORE scenes' v2 tag moves, or the Dungeon Realms card installs 1.x.

🤖 Generated with Claude Code

AlexZ005 and others added 15 commits September 19, 2026 13:48
…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:
#	package.json
#	tests/helpers.cjs
@AlexZ005
AlexZ005 merged commit 090c16f into dev Sep 19, 2026
@AlexZ005
AlexZ005 deleted the integrate/1.15 branch September 19, 2026 20:58
AlexZ005 added a commit that referenced this pull request Oct 5, 2026
AlexZ005 added a commit that referenced this pull request Oct 5, 2026
1.23: football 1.4.0 + dungeon-realms 2.4.0 (rules nodes on Main), DEVX #9 #10 #22
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