Rock template now declares name="Rock", a description, weight (kg), volume (L), and composition.stone=1.0. The module-side label_resolver override is removed — display lib's default reads name now. Inspect action prints a structured block (name, description, weight, volume, composition.<material>, reg_id) to the console. Future notification system will route this to an in-game text panel; for now the console output makes Inspect useful instead of silent.
212 lines
11 KiB
Markdown
212 lines
11 KiB
Markdown
# 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.9.1
|
|
**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.2.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-asset.prototype-subterrain >=0.1.0
|
|
**Tags:** test-chamber, puppet, walking-sim, interaction
|
|
|
|
## Topology
|
|
|
|
<!-- topology:start (auto-generated; do not edit) -->
|
|
<!-- topology:end -->
|
|
|
|
## 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, 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)
|
|
- **Right-Click on item row** (while panel open): context menu with **Drop** / **Inspect** options
|
|
- **ESC** — return to launcher
|
|
|
|
## Demo Walkthrough
|
|
|
|
The world contains three 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.
|
|
|
|
**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.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).
|