feat(vagrant): notification system — Inspect modal, pickup-float, toast events, game-log

- manifest: bump to v0.10.0; add deps lib-core.notify, lib-core.notify-display,
  lib-core.world-overlay (all v0.1.0)
- Inspect action migrated from engine.print to notify.post{tags={"inspect"}, data={...}};
  routes to modal detail-panel via notify-display detail-channel
- Pickup callback: world_overlay.spawn fires "+1 <name>" float anchored on player
  (yellow, rise_px=24, ttl=1.2s) + notify.post pickup event-toast/log entry
- Drop action (right-click menu): notify.post{tags={"drop","inventory"}} added
  after existing relocate+close_menu logic
- Setup block: 3 channels (inspect/events/log), 2 panel widgets
  (inspect-detail/game-log), 3 attach_mode calls; L bound to game-log panel
- M.update: notify_display.update + world_overlay.update after panel.update pause-guard
- M.draw: camera_handle constructed after camera.finish(); world_overlay.render between
  camera.finish and panel.render; notify_display.render at end (toasts on top)
- README: version 0.10.0, Requires expanded, Controls section updated, changelog entry added
This commit is contained in:
Axel Meyer
2026-06-14 01:59:28 +02:00
parent f66ffc7f12
commit 6b4c698984
3 changed files with 129 additions and 25 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.9.1 **Version:** 0.10.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.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 **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-core.notify >=0.1.0, lib-core.notify-display >=0.1.0, lib-core.world-overlay >=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
@@ -27,6 +27,10 @@ demo: pick up a rock into a backpack container, drop it back into the world.
- **Q** — quick-drop (LIFO): 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) - **Tab** — toggle inventory panel (Backpack contents)
- **Right-Click on item row** (while panel open): context menu with **Drop** / **Inspect** options - **Right-Click on item row** (while panel open): context menu with **Drop** / **Inspect** options
- **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 <name>" 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
- **ESC** — return to launcher - **ESC** — return to launcher
## Demo Walkthrough ## Demo Walkthrough
@@ -111,6 +115,23 @@ Only one rock is spawned per session.
## CHANGELOG ## CHANGELOG
### 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 ### v0.9.1 — item-template player-facing properties
- **Rock template enriched**: `name`, `description`, `weight` (kg), - **Rock template enriched**: `name`, `description`, `weight` (kg),
`volume` (L), and `composition.stone = 1.0` properties added. Other item `volume` (L), and `composition.stone = 1.0` properties added. Other item

122
init.lua
View File

@@ -1,4 +1,4 @@
-- sporel-module-vagrant-skeleton v0.9.1 -- sporel-module-vagrant-skeleton v0.10.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,
@@ -34,6 +34,13 @@
-- notification system will route this to an in-game text panel. Module- -- notification system will route this to an in-game text panel. Module-
-- side label_resolver override removed — default_label_resolver reads -- side label_resolver override removed — default_label_resolver reads
-- the `name` property. -- the `name` property.
--
-- v0.10.0: Notification system wired in. Inspect routes to a modal
-- detail-panel (notify-display detail-channel); pickup spawns a
-- world-floating "+1 Rock" overlay; Drop emits an event-toast. L
-- opens the game-log panel listing all events chronologically.
-- engine.print mirror remains enabled — dev tools that parse the log
-- continue to work.
local input = require("lib-core.input") local input = require("lib-core.input")
local camera = require("lib-core.camera") local camera = require("lib-core.camera")
@@ -46,6 +53,9 @@ 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 panel = require("lib-core.panel")
local inv_display = require("lib-core.inventory-list-display") local inv_display = require("lib-core.inventory-list-display")
local notify = require("lib-core.notify")
local notify_display = require("lib-core.notify-display")
local world_overlay = require("lib-core.world-overlay")
local M = {} local M = {}
@@ -62,6 +72,9 @@ local ANIM_SPEED_SPRINT = 0.9 -- 2x
local BACKPACK_X, BACKPACK_Y = 350, 400 local BACKPACK_X, BACKPACK_Y = 350, 400
local INTERACTION_RANGE = 40 -- pixels local INTERACTION_RANGE = 40 -- pixels
-- World-overlay float colors (pickup confirmation)
local COLOR_PICKUP_FLOAT = 0xFFFF80FF
-- Backpack-Template (Phase A.4 — Atlas-UV render-path). -- Backpack-Template (Phase A.4 — Atlas-UV render-path).
-- v0.8.0: container={kind="list"} declared so the backpack receives -- v0.8.0: container={kind="list"} declared so the backpack receives
-- items via inventory.add (capability-by-declaration). -- items via inventory.add (capability-by-declaration).
@@ -178,6 +191,25 @@ local function register_pickup(item)
trigger_id = interaction.register(x, y, INTERACTION_RANGE, "interact", trigger_id = interaction.register(x, y, INTERACTION_RANGE, "interact",
function() function()
inventory.add(state.backpack, item) inventory.add(state.backpack, item)
-- World-float "+1 <item-name>" anchored on the player
local item_name = item:get_property("name") or "item"
world_overlay.spawn{
kind = "text",
text = "+1 " .. item_name,
anchor = state.actor, -- follow player
offset = { x = -20, y = -48 },
ttl = 1.2,
font_size = 16,
color = COLOR_PICKUP_FLOAT,
rise_px = 24,
}
-- Pickup event-toast + log entry
notify.post{
tags = {"pickup", "inventory"},
text = string.format("Picked up %s", item_name),
}
interaction.unregister(trigger_id) interaction.unregister(trigger_id)
if CI_FRAME_PERF then engine.print("vagrant: event=pickup") end if CI_FRAME_PERF then engine.print("vagrant: event=pickup") end
@@ -433,39 +465,66 @@ function M.init(ctx)
register_pickup(item) register_pickup(item)
c.close_menu() c.close_menu()
if CI_FRAME_PERF then engine.print("vagrant: event=drop_via_menu") end if CI_FRAME_PERF then engine.print("vagrant: event=drop_via_menu") end
notify.post{
tags = {"drop", "inventory"},
text = string.format("Dropped %s",
item:get_property("name") or "item"),
}
end) end)
-- Inspect action: prints player-facing item info to console. Future -- Inspect action: routes to a modal detail-panel via notify.post.
-- notification-system (lib-core.toast or similar) will route this to -- engine.print mirror kept for dev-tool compatibility.
-- an in-game text panel instead of engine.print.
inv_display.register_action(backpack_widget, "Inspect", function(item, c) inv_display.register_action(backpack_widget, "Inspect", function(item, c)
local name = item:get_property("name") or "(unnamed)" -- Collect composition.<material>.* properties for the detail payload
local description = item:get_property("description") or ""
local weight = item:get_property("weight") or 0
local volume = item:get_property("volume") or 0
engine.print(string.format("=== %s ===", name))
if description ~= "" then engine.print(description) end
engine.print(string.format("weight=%.2fkg volume=%.3fL", weight, volume))
-- Surface material composition by iterating composition.* properties.
local props = item:get_properties() local props = item:get_properties()
local mats = {} local comp = {}
for k, v in pairs(props) do for k, v in pairs(props) do
local mat = k:match("^composition%.(.+)$") local mat = k:match("^composition%.(.+)$")
if mat and mat ~= "reg_id" then if mat and mat ~= "reg_id" then comp[mat] = v end
table.insert(mats, string.format("%s=%.2f", mat, v))
end end
end notify.post{
if #mats > 0 then tags = {"inspect"},
engine.print("composition: " .. table.concat(mats, " ")) text = item:get_property("name") or "(unnamed)",
end data = {
engine.print(string.format("(reg_id=%s)", description = item:get_property("description"),
tostring(item:get_property("composition.reg_id")))) weight = item:get_property("weight"),
volume = item:get_property("volume"),
composition = comp,
},
}
c.close_menu() c.close_menu()
end) end)
panel.register("backpack", backpack_widget) panel.register("backpack", backpack_widget)
panel.bind_default_trigger("tab", "backpack") panel.bind_default_trigger("tab", "backpack")
-- ====== Notification System Setup ======
-- Channels (created BEFORE display attach + widget register)
notify.create_channel{ id = "inspect", filter = {"inspect"},
display_mode = "detail" }
notify.create_channel{ id = "events", filter = {"pickup", "drop", "crafting"},
display_mode = "toast" }
notify.create_channel{ id = "log", filter = {}, -- catch-all
display_mode = "log" }
-- Widgets MUST be registered BEFORE attach_mode("detail"/"log") because
-- attach_mode auto-opens detail-widget on first message (requires registered).
panel.register("inspect-detail",
notify_display.create_detail_widget{
widget_id = "inspect-detail", title = "Item Details" })
panel.register("game-log",
notify_display.create_log_widget{
widget_id = "game-log", title = "Game Log" })
-- Attach display-layer modes
notify_display.attach_mode("detail", { panel_widget_id = "inspect-detail" })
notify_display.attach_mode("toast", { position = "top-right", max_visible = 5 })
notify_display.attach_mode("log", { panel_widget_id = "game-log" })
-- L opens game-log (inspect-detail auto-opens on notify.post; no hotkey)
panel.bind_default_trigger("l", "game-log")
-- Input bindings. -- Input bindings.
input.bind("walk_left", { "a" }) input.bind("walk_left", { "a" })
input.bind("walk_right", { "d" }) input.bind("walk_right", { "d" })
@@ -501,6 +560,9 @@ function M.update(ctx, dt)
panel.update(dt) panel.update(dt)
if panel.is_pausing() then return end if panel.is_pausing() then return end
notify_display.update(dt)
world_overlay.update(dt)
-- 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.
@@ -514,6 +576,11 @@ function M.update(ctx, dt)
item:set_property("position.y", py) item:set_property("position.y", py)
register_pickup(item) register_pickup(item)
if CI_FRAME_PERF then engine.print("vagrant: event=drop") end if CI_FRAME_PERF then engine.print("vagrant: event=drop") end
notify.post{
tags = {"drop", "inventory"},
text = string.format("Dropped %s",
item:get_property("name") or "item"),
}
-- 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)
@@ -655,8 +722,21 @@ function M.draw(ctx)
ci_sprite_traced = true ci_sprite_traced = true
end end
end end
-- Camera handle for world_overlay (built from lib-core.camera state)
local target = camera.target() -- returns {x, y}
local w, h = engine.window.size()
local camera_handle = {
target_x = target.x,
target_y = target.y,
zoom = camera.zoom(),
offset_x = w / 2,
offset_y = h / 2,
}
world_overlay.render(camera_handle)
-- Inventory panel overlay (z-top: drawn after all world content). -- Inventory panel overlay (z-top: drawn after all world content).
panel.render() panel.render()
-- Toast overlay on top of everything.
notify_display.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.9.1", "version": "0.10.0",
"kind": "module", "kind": "module",
"api": "^0.1", "api": "^0.1",
"ci_frames": 60, "ci_frames": 60,
@@ -19,6 +19,9 @@
{"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.panel", "version": "0.1.0"},
{"id": "lib-core.inventory-list-display", "version": "0.1.1"}, {"id": "lib-core.inventory-list-display", "version": "0.1.1"},
{"id": "lib-core.notify", "version": "0.1.0"},
{"id": "lib-core.notify-display", "version": "0.1.0"},
{"id": "lib-core.world-overlay", "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"},