feat(vagrant): Tab-Toggle inventory panel + right-click context menu

- lib-core.panel and lib-core.inventory-list-display added to deps (v0.1.0 each)
- backpack-widget created in M.init with label-resolver returning "Rock"
- Drop action registered: removes from backpack, relocates to player position,
  re-registers pickup trigger
- Inspect action registered: prints reg_id via engine.print
- M.update: panel.update(dt) + is_pausing() early-return added after
  quit_to_launcher check
- interaction.update wrapped in panel.is_open() guard to block world triggers
  while panel is visible
- M.draw: panel.render() added at z-top after CI traces
- Q-drop (LIFO quick-drop via Q key) unchanged and parallel to panel Drop action
This commit is contained in:
Axel Meyer
2026-06-13 23:38:03 +02:00
parent e38d396b87
commit 60eac65bce
3 changed files with 75 additions and 8 deletions

View File

@@ -8,9 +8,9 @@ callback, foot-body-orientation ("tactical twist"), scale-deformation walk
cycle, and inheritance-decoupled foot sprites. End-to-end inventory pickup/drop 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. demo: pick up a rock into a backpack container, drop it back into the world.
**Version:** 0.8.0 **Version:** 0.9.0
**Module-ID:** vagrant-skeleton **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.inventory-list >=0.1.0, lib-asset.prototype-subterrain >=0.1.0 **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 **Tags:** test-chamber, puppet, walking-sim, interaction
## Topology ## Topology
@@ -24,7 +24,9 @@ demo: pick up a rock into a backpack container, drop it back into the world.
- **Shift + WASD** — sprint (2x player speed + 2x walk-anim cadence) - **Shift + WASD** — sprint (2x player speed + 2x walk-anim cadence)
- **Mouse** — body + head look-at targets (same position, different smoothing) - **Mouse** — body + head look-at targets (same position, different smoothing)
- **E** — interact: if within range of the rock, picks it up into the backpack - **E** — interact: if within range of the rock, picks it up into the backpack
- **Q** — drop: places the most-recently-added backpack item at player position (+30px right) - **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 - **ESC** — return to launcher
## Demo Walkthrough ## Demo Walkthrough
@@ -109,6 +111,17 @@ Only one rock is spawned per session.
## CHANGELOG ## CHANGELOG
### 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 ### v0.8.0 — end-to-end inventory demo functional
- **Backpack container**: `composition.define_template{id="backpack"}` now includes - **Backpack container**: `composition.define_template{id="backpack"}` now includes
`container={kind="list"}`. The composition v0.2 validator accepts this; the backpack `container={kind="list"}`. The composition v0.2 validator accepts this; the backpack

View File

@@ -1,4 +1,4 @@
-- sporel-module-vagrant-skeleton v0.8.0 -- sporel-module-vagrant-skeleton v0.9.0
-- Sprite-mode test-chamber: 13-bone humanoid puppet + sprite-tilemap + -- Sprite-mode test-chamber: 13-bone humanoid puppet + sprite-tilemap +
-- hardcoded furniture. Subterrain-style puppet control model: -- hardcoded furniture. Subterrain-style puppet control model:
-- multi-look-target (aim/soft), slerp body rotation, hip-static legs, -- multi-look-target (aim/soft), slerp body rotation, hip-static legs,
@@ -22,6 +22,11 @@
-- position. engine.print shows inventory count + item-IDs after each -- position. engine.print shows inventory count + item-IDs after each
-- add/remove. Tree-check (CI-gated) verifies child placement in -- add/remove. Tree-check (CI-gated) verifies child placement in
-- composition tree. -- composition tree.
--
-- v0.9.0: Inventory panel UI wired in. Tab toggles a panel overlay
-- showing backpack contents. Right-click on an item row opens a context
-- menu with Drop and Inspect actions. Q-drop (LIFO quick-drop) remains
-- available in parallel.
local input = require("lib-core.input") local input = require("lib-core.input")
local camera = require("lib-core.camera") local camera = require("lib-core.camera")
@@ -32,6 +37,8 @@ local interaction = require("lib-core.interaction")
local composition = require("lib-core.composition") local composition = require("lib-core.composition")
local actor = require("lib-core.actor") local actor = require("lib-core.actor")
local inventory = require("lib-core.inventory-list") local inventory = require("lib-core.inventory-list")
local panel = require("lib-core.panel")
local inv_display = require("lib-core.inventory-list-display")
local M = {} local M = {}
@@ -389,6 +396,43 @@ function M.init(ctx)
register_pickup(state.rock) register_pickup(state.rock)
if CI_FRAME_PERF then engine.print("vagrant: rock_spawned") end if CI_FRAME_PERF then engine.print("vagrant: rock_spawned") end
-- Inventory-UI Setup
panel.set_theme{ panel_width_frac = 0.4, font_size_title = 20 }
local backpack_widget = inv_display.create(state.backpack, {
title = "Backpack",
widget_id = "backpack",
pause_on_open = false,
})
-- Module-side label-resolver (display lib's default returns "Item").
-- Vagrant currently has one item template (rock); hardcoded for v0.9.
inv_display.set_label_resolver(backpack_widget, function(item)
return "Rock"
end)
-- Drop action: knows world-reparenting + player position
inv_display.register_action(backpack_widget, "Drop", function(item, c)
inventory.remove(state.backpack, item)
local px = state.actor:get_property("position.x")
local py = state.actor:get_property("position.y")
item:set_property("position.x", px + 30)
item:set_property("position.y", py)
register_pickup(item)
c.close_menu()
if CI_FRAME_PERF then engine.print("vagrant: event=drop_via_menu") end
end)
-- Inspect action: dumps reg_id to console
inv_display.register_action(backpack_widget, "Inspect", function(item, c)
engine.print(string.format("vagrant: inspect: reg_id=%s",
tostring(item:get_property("composition.reg_id"))))
c.close_menu()
end)
panel.register("backpack", backpack_widget)
panel.bind_default_trigger("tab", "backpack")
-- Input bindings. -- Input bindings.
input.bind("walk_left", { "a" }) input.bind("walk_left", { "a" })
input.bind("walk_right", { "d" }) input.bind("walk_right", { "d" })
@@ -420,6 +464,10 @@ function M.update(ctx, dt)
return return
end end
-- Inventory panel input handling
panel.update(dt)
if panel.is_pausing() then return end
-- Drop: Q pops the most-recently-added item from backpack, relocates -- Drop: Q pops the most-recently-added item from backpack, relocates
-- it to world at player position + 30px right offset, and re-registers -- it to world at player position + 30px right offset, and re-registers
-- a pickup-trigger. inventory.remove restores the renderable tag. -- a pickup-trigger. inventory.remove restores the renderable tag.
@@ -436,9 +484,9 @@ function M.update(ctx, dt)
-- Inventory inspection trace (unconditional — user-facing demo output). -- Inventory inspection trace (unconditional — user-facing demo output).
local count = inventory.count(state.backpack) local count = inventory.count(state.backpack)
local items = inventory.contents(state.backpack) local remaining_items = inventory.contents(state.backpack) -- re-read post-remove
local ids = {} local ids = {}
for _, it in ipairs(items) do for _, it in ipairs(remaining_items) do
table.insert(ids, tostring(it:get_property("composition.reg_id"))) table.insert(ids, tostring(it:get_property("composition.reg_id")))
end end
engine.print(string.format("vagrant: backpack count=%d items=[%s]", engine.print(string.format("vagrant: backpack count=%d items=[%s]",
@@ -525,7 +573,9 @@ function M.update(ctx, dt)
-- Interaction: dispatches the nearest matching proximity-trigger when -- Interaction: dispatches the nearest matching proximity-trigger when
-- 'interact' was pressed this frame. Uses actor.position as anchor. -- 'interact' was pressed this frame. Uses actor.position as anchor.
if not panel.is_open() then
interaction.update(actor.position(state.actor)) interaction.update(actor.position(state.actor))
end
-- Camera follows actor position each frame. -- Camera follows actor position each frame.
camera.set_target(actor.position(state.actor)) camera.set_target(actor.position(state.actor))
@@ -572,6 +622,8 @@ function M.draw(ctx)
ci_sprite_traced = true ci_sprite_traced = true
end end
end end
-- Inventory panel overlay (z-top: drawn after all world content).
panel.render()
end end
-- Engine lifecycle globals (engine looks up by name; module-table is bridged). -- Engine lifecycle globals (engine looks up by name; module-table is bridged).

View File

@@ -1,6 +1,6 @@
{ {
"id": "vagrant-skeleton", "id": "vagrant-skeleton",
"version": "0.8.0", "version": "0.9.0",
"kind": "module", "kind": "module",
"api": "^0.1", "api": "^0.1",
"ci_frames": 60, "ci_frames": 60,
@@ -17,6 +17,8 @@
{"id": "lib-core.render", "version": "0.2.0"}, {"id": "lib-core.render", "version": "0.2.0"},
{"id": "lib-core.composition", "version": "0.2.0"}, {"id": "lib-core.composition", "version": "0.2.0"},
{"id": "lib-core.actor", "version": "0.1.0"}, {"id": "lib-core.actor", "version": "0.1.0"},
{"id": "lib-core.panel", "version": "0.1.0"},
{"id": "lib-core.inventory-list-display", "version": "0.1.0"},
{"id": "lib-core.maps","version":"0.5.7"}, {"id": "lib-core.maps","version":"0.5.7"},
{"id": "lib-core.puppet", "version": "0.5.0"}, {"id": "lib-core.puppet", "version": "0.5.0"},
{"id": "lib-core.interaction", "version": "0.1.0"}, {"id": "lib-core.interaction", "version": "0.1.0"},