Axel Meyer f16c8954d9 fix: notify channel hygiene + craft-output sprites
- Drop "craft" tag from Inspect-Recipe payload; multi-line detail
  posts now route only to the inspect/detail channel, not the toast
  events channel.
- Wire rock_pick + rock_pile templates to blob_rect_stone atlas
  slots (slot_05_tee_open + slot_13_solid) so crafted items render
  in inventory + on drop instead of magenta fallback.
- Drop dead "crafting" tag from events filter (no post emitted it).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-14 14:09:17 +02:00
2026-06-14 13:57:19 +02:00

sporel-module-vagrant-skeleton

F.vagrant Tier-1 test-chamber: single player as a 13-bone humanoid puppet, WASD movement, camera-chase, multi-target look-at (body lazy-tracks mouse via slerp, head snaps to mouse with ±60° clamp). Demonstrates the Subterrain-style puppet control model: leg orientation decoupled from body via procedural callback, foot-body-orientation ("tactical twist"), scale-deformation walk cycle, and inheritance-decoupled foot sprites. End-to-end inventory pickup/drop demo: pick up a rock into a backpack container, drop it back into the world.

Version: 0.11.0 Module-ID: vagrant-skeleton Requires: lib-core.input >=0.4.0, lib-core.camera >=0.3.0, lib-core.render >=0.1.0, lib-core.maps >=0.1.2, lib-core.puppet >=0.4.4, lib-core.interaction >=0.1.0, lib-core.composition >=0.3.0, lib-core.actor >=0.1.0, lib-core.inventory-list >=0.1.0, lib-core.panel >=0.1.0, lib-core.inventory-list-display >=0.1.0, lib-core.notify >=0.1.0, lib-core.notify-display >=0.1.0, lib-core.world-overlay >=0.1.0, lib-core.crafting >=0.1.0, lib-core.crafting-display >=0.1.0, lib-asset.prototype-subterrain >=0.1.0 Tags: test-chamber, puppet, walking-sim, interaction

Topology

Controls

  • W A S D — direct movement (4-way, normalized for diagonals)
  • Shift + WASD — sprint (2x player speed + 2x walk-anim cadence)
  • Mouse — body + head look-at targets (same position, different smoothing)
  • E — interact: if within range of the rock or stick, picks it up into the backpack
  • Q — quick-drop (LIFO): places the most-recently-added backpack item at player position (+30px right)
  • Tab — toggle inventory panel (Backpack contents)
  • C — toggle Crafting panel (lists known recipes; availability dimmed by inventory)
  • Right-Click on item row (inventory panel): context menu with Drop / Inspect
  • Right-Click on recipe row (crafting panel): context menu with Craft / Inspect
  • L — toggle game-log (chronological list of all events)
  • Right-Click on item row → Inspect — opens a modal Item Details panel (instead of console-only output)
  • Pickup (E) — shows "+1 " floating above the player + toast at top-right + log entry
  • Drop (right-click on item → Drop, or Q for LIFO quick-drop) — toast at top-right + log entry
  • Craft (right-click on recipe → Craft) — consumes inputs from backpack, adds output, posts a success/failure toast
  • ESC — return to launcher

Demo Walkthrough

The world contains four notable entities at startup:

  1. Player — spawns at (320, 240), controlled via WASD + mouse.
  2. Backpack — a visible sprite at (350, 400). Acts as a container (kind="list"). Not directly interactable; it receives items via inventory.add.
  3. Rock — a standalone stone tile at (270, 400), 80px to the left of the backpack. Tagged "renderable" + "item"; starts visible on the ground.
  4. Stick — a placeholder pickup-item at (190, 400), 160px to the left of the backpack. Re-uses the slot_04_straight blob_rect_stone tile for its visual (Phase C-Q10 pragmatic resolution — echtes Stick-Sprite folgt mit dem ersten atlas-baker-decals-Slice).

Pickup flow:

  • Walk toward the rock (it sits to the left of the backpack).
  • Press E within 40px of the rock.
  • The rock moves into the backpack via inventory.add; its "renderable" tag is removed so it no longer appears in the world.
  • The console prints: vagrant: backpack count=1 items=[<reg_id>]

Drop flow:

  • Press Q at any time when the backpack has items.
  • The most-recently-added item is removed from the backpack (inventory.remove) and placed at the player position +30px to the right. Its "renderable" tag is restored so it reappears in the world.
  • A new pickup trigger is registered at the drop position.
  • The console prints: vagrant: backpack count=0 items=[]

Multiple pickups: Each E → Q → E cycle moves the same rock in and out. Only one rock is spawned per session.

Demonstrates

  • lib-core.puppet v0.4.3 integration:
    • Multi-look-target: body "soft" (slerp=6, lazy) + head "aim" (instant-snap, ±60° clamp)
    • Scale-deformation walk cycle: leg.scl_y cycles +1/0/-1 via keyframe animation
    • Procedural leg orientation: legs face walk direction independent of body rotation
    • Foot-body-orientation ("tactical twist"): feet face body direction, not leg direction
    • inherit_scale: false on feet: feet do not Y-flip when leg.scl_y goes negative
    • Per-channel conflict guard: walk animation (scl) + procedural (rot) on same leg bones
    • Camera in camera.begin()/finish() block for correct world-space rendering
  • WASD movement → walk_fwd_lower + walk_upper animation pair (speed = 0.45x)
  • Shift+WASD sprint → puppet.set_play_speed doubles cadence to 0.9x mid-cycle (no animation restart on toggle); player movement speed doubled (120 → 240)
  • Idle state → idle + idle_lower animations
  • Camera-chase via lib-core.camera target-provider
  • Sprite-tilemap via lib-core.maps
  • lib-core.interaction proximity trigger on the rock at (270, 400), range 40px, "interact" action bound to E — fires inventory.add on dispatch
  • lib-core.inventory-list: backpack is a container (kind="list"); rock is an individual-stack item; add/remove toggle renderable tag

Interactions

  • WASD active → legs swing in walk direction; body slowly rotates to mouse; head snaps to mouse
  • Shift held while moving → 2x player speed; walk cadence doubles (smooth mid-cycle transition)
  • WASD released → idle_lower collapses legs (scl=0); upper body returns to idle sway
  • Mouse moved → body lazy-rotates via slerp (look_at_slerp: 6); head snaps within ±60° of body
  • Walking east while body faces north → legs perpendicular to body, hips body-relative, feet face north
  • E pressed within 40px of the rock → fires "interact" action; rock moves into backpack; console prints vagrant: backpack count=N items=[...]
  • Q pressed with items in backpack → most-recent item drops at player position; pickup trigger re-registered at drop location; console prints updated backpack state

CI Hooks

  • SPOREL_CI=1 → 60-frame perf canary. Emits vagrant: frame_avg_ms=N and vagrant: render_frame_ok (per-frame, from render hook), then exits. Used by smoke stages P0-S25a + P0-S25b.
  • SPOREL_VAGRANT_PHASE1_DRIVE=1 → scripted synthetic WASD (frames 20-40 move east). Used by P0-S25 puppet-test integration.

References

  • superpowers/specs/2026-05-18-puppet-subterrain-model-design.md (design basis)
  • docs/adrs/0040-puppet-subterrain-model-pivot.md (architectural rationale)
  • architecture/puppet.md (lib-core.puppet reference)
  • audit/2026-05-18-puppet-subterrain-model-manual-test.md (manual verification checklist)
  • ADR-0028 (Launcher-Flow)
  • ADR-0036 (Lib-Override for multi-module reuse)
  • ADR-0037 (Tests-as-Libs)
  • ADR-0038 (API-Doc-Convention)

CHANGELOG

v0.11.0 — crafting integration

  • New: stick pickup-item neben rock am Boden (Position 190,400 — 160px links vom Backpack). Re-uses slot_04_straight aus blob_rect_stone als Visual- Placeholder (Phase C-Q10).
  • New: 2 Recipes via lib-core.crafting:
    • Stone Pick = 1 rock + 1 stick → rock_pick
    • Rock Pile = 3 rocks → rock_pile
  • New: C-Taste öffnet ein Crafting-Panel via lib-core.crafting-display. Recipe-Zeilen sind dimmed, wenn inputs im Backpack fehlen.
  • New: Right-Click auf Recipe-Row → Context-Menu mit Craft + Inspect. Craft konsumiert inputs aus dem Backpack und legt das Output-Template als neues item-Entity in den Backpack. Inspect routet recipe details (name, description, inputs, output) zum notify-detail-channel.
  • New: 3 item-templatesstick, rock_pick, rock_pile — jedes mit player-facing properties (name, description, weight, volume, composition.). Placeholder atlas-slots: stick=slot_04_straight, rock_pick=slot_05_tee_open, rock_pile=slot_13_solid.
  • Deps: +lib-core.crafting 0.1.0, +lib-core.crafting-display 0.1.0. composition bleibt auf 0.3.0 (gebumpt in Phase C.0).
  • Notify events-Channel: filter um "craft" erweitert, damit Craft- Erfolgs- und Fehler-Toasts im events-channel landen.

v0.10.0 — notification system integration

  • Inspect modal: Inspect action replaced with notify.post{tags={"inspect"}, ...} routing to a modal detail-panel (notify-display detail-channel). Item name, description, weight, volume, and material composition are passed as structured data. Inspect no longer calls engine.print directly; the notify lib's engine-log mirror routes the event to engine.log for backward-compat with dev tools.
  • Pickup world-float: after inventory.add, a world_overlay.spawn fires a "+1 Rock" floating text anchored to the player (yellow, rises 24px, fades in 1.2s). A notify.post simultaneously emits a pickup event-toast and log entry.
  • Drop toast: the Drop action (both right-click menu and Q quick-drop) emits a notify.post{tags={"drop","inventory"}} event-toast after the item is relocated.
  • Game-log panel: a catch-all log channel (filter={}) captures every pickup, drop, and inspect event chronologically. L toggles the panel.
  • New deps: lib-core.notify v0.1.0, lib-core.notify-display v0.1.0, lib-core.world-overlay v0.1.0.
  • Render order: camera.finish() → world_overlay.render(camera_handle) → panel.render() → notify_display.render() (toasts on top of all UI).

v0.9.1 — item-template player-facing properties

  • Rock template enriched: name, description, weight (kg), volume (L), and composition.stone = 1.0 properties added. Other item templates can follow the same convention (optional with fallbacks).
  • Inspect prints structured info: name, description, weight, volume, and a composition: <material>=<value> line; reg_id parenthetical at the end. Future notification system will route this to an in-game text panel.
  • Module-side label_resolver override removed: inventory-list-display v0.1.1's default_label_resolver reads the name property; the hardcoded "Rock" override is obsolete.

v0.9.0 — inventory panel UI

  • Panel overlay: lib-core.panel + lib-core.inventory-list-display wired in. Tab key toggles a panel overlay showing backpack contents.
  • Context menu actions: right-clicking an item row opens a context menu with Drop (re-parents item to world at player position, re-registers pickup trigger) and Inspect (prints reg_id to console).
  • Q-drop unchanged: LIFO quick-drop via Q remains available in parallel to the panel-based drop action.
  • Interaction guard: interaction.update is suppressed while the panel is open so world triggers don't fire during menu navigation.

v0.8.0 — end-to-end inventory demo functional

  • Backpack container: composition.define_template{id="backpack"} now includes container={kind="list"}. The composition v0.2 validator accepts this; the backpack can now receive items via inventory.add.
  • End-to-end pickup/drop: E on the rock calls inventory.add(state.backpack, rock); inventory-list removes the "renderable" tag so the rock disappears from the world. Q calls inventory.remove, restores "renderable", relocates item to player position +30px right, and re-registers the pickup trigger. Full loop functional.
  • Inventory inspection trace: after every add or remove, engine.print emits vagrant: backpack count=N items=[...] (unconditional; user-facing demo feedback).
  • Tree-check traces (CI-gated): after pickup, vagrant: tree_check_after_pickup=ok verifies item appears as item.* child of the backpack in the composition tree. After drop, vagrant: tree_check_after_drop=ok verifies no item.* children remain.
  • Backpack interaction.register removed: the old print-only proximity trigger on the backpack sprite has been deleted. The rock's pickup trigger is the only interaction trigger registered.

v0.7.3

  • Rock entity: composition.define_template{id="rock"} added (stack_mode=individual, tags=renderable+item). One rock entity spawned at (270, 400) — 80px left of the backpack. Uses slot_00_isolated UV from the blob_rect_stone atlas (lib-asset.prototype-blob-geom). register_pickup(state.rock) wires an interaction trigger so pressing E near the rock fires the pickup callback. render.draw_entities{tag="renderable"} (existing Phase A render path) draws the rock without any special-case render code.

v0.4.4

  • Sprint (Shift+WASD): doubles WALK_SPEED (120 → 240) and walk-anim speed (0.45 → 0.9). Mid-walk sprint toggle uses puppet.set_play_speed (new v0.4.4 API) — no animation cycle restart.
  • Backpack interaction: lib-core.interaction.register(350, 400, 40, "interact", callback) registered at init. Pressing E within range prints log message via engine.print with actor distance.
  • New deps: lib-core.interaction ^0.1.0. lib-core.puppet bumped to >=0.4.4.

v0.4.3

  • inherit_scale: false on foot_l, foot_r: feet no longer Y-flip when leg.scl_y inverts during walk cycle. foot.rest.scl reset to [1,1].
  • foot procedural callback writes foot.rot to cancel leg.rot → foot.world.rot = body.world.rot.

v0.4.2

  • Procedural orientation callback writes leg.rot + foot.rot (per-channel guard allows this alongside walk_fwd_lower which writes only leg.scl).

v0.4.1

  • head look-at uses target_local normalization (-π, π]) to fix flick at body south-facing.

v0.4.0

  • Multi-look-target: body tracks "soft", head tracks "aim" (both currently raw mouse).
  • body.look_at_slerp = 6 (lazy rotation); head instant-snap with rot_min/max ±60°.
  • Legs re-parented from root to body (hip-static body-relative positioning).
  • Procedural "leg_foot_orientation" callback: leg faces walk direction, foot cancels leg rotation to stay body-aligned.
  • walk_fwd_lower plays at speed = 0.45x for human-feel cadence.
  • Rendering wrapped in camera.begin()/camera.finish() block.

v0.3.3

  • sprite_rot applied to leg bones to compensate for sprite natural orientation.

v0.3.2

  • Persistent look_at_state; fixes rate-limit reset each frame.

v0.3.1

  • look-at atan math corrected: atan(dx, -dy) for sprite-top convention.

v0.3.0

  • Sprite-mode rendering: 13-bone humanoid puppet with multi-channel animation.
  • walk_fwd_lower (lower track): leg.scl_y cycle +1/0/-1 (substrate walk_fwd_lower).
  • walk_upper (upper track): arm-swing animation.
  • idle + idle_lower: upper body sway + leg collapse.
  • Sprite-tilemap with 4 floor variants; hardcoded furniture.

v0.2.0

  • Sprite-mode rendering throughout: 13-bone humanoid puppet via lib-core.puppet v0.2 with PROTOTYPE asset-pack textures; sprite-tilemap via lib-core.maps v0.1.2 with 4 floor variants; hardcoded furniture (bed, bench, backpack).
Description
F.vagrant Tier-1 test-chamber: single player + puppet + tilemap
Readme 666 KiB
Languages
Lua 100%