feat(0.6.0): Backpack as composition-Entity (Phase A.4)

Refactor: Backpack ist composition-Entity mit Atlas-UV-Render-Pfad.
Template at top-level declares sprite_atlas + sprite_uv.{x,y,w,h} +
position. create() in M.init nach atlas-load setzt reale UV-Werte aus
state.furniture_uvs["backpack_001"].

render.draw_entities{tag="renderable"} ersetzt den direkten
draw_sprite_transform-Call für Backpack. interaction.register liest
position aus entity-properties.

Bed + Bench bleiben hardcoded Atlas-Furniture — kein
Interaction-Trigger, kein Demo-Pflicht zur Entity-Promotion in
Phase A. Phase B (container-on-bed) oder Phase D wäre der Trigger.

Atlas-Texture wird doppelt geladen (state.world_tex für bed/bench,
render-internal-cache für backpack) — ineffizient aber nicht
kaputt; single-handle-merge ist späterer Slice.

Bumps: render 0.1.0 → 0.2.0; +composition v0.1.0; module 0.4.4 → 0.6.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-06-09 13:51:02 +00:00
parent 77550149cb
commit c976353637
2 changed files with 69 additions and 21 deletions

View File

@@ -1,9 +1,14 @@
-- sporel-module-vagrant-skeleton v0.5.0 -- sporel-module-vagrant-skeleton v0.6.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,
-- foot-body-orientation. Sprint (shift+wasd) + interaction (E near -- foot-body-orientation. Sprint (shift+wasd) + interaction (E near
-- backpack) added v0.4.4. -- backpack) added v0.4.4.
--
-- v0.6.0 (Phase A.4): Backpack promoted to composition-Entity (Atlas-UV
-- render-path via render.draw_entities{tag="renderable"}). Bed + Bench
-- remain hardcoded atlas-furniture in v0.6.0 (no interaction-trigger,
-- no demo-pflicht to be entities yet).
local input = require("lib-core.input") local input = require("lib-core.input")
local camera = require("lib-core.camera") local camera = require("lib-core.camera")
@@ -11,6 +16,7 @@ local r_lib = require("lib-core.render")
local maps = require("lib-core.maps") local maps = require("lib-core.maps")
local puppet = require("lib-core.puppet") local puppet = require("lib-core.puppet")
local interaction = require("lib-core.interaction") local interaction = require("lib-core.interaction")
local composition = require("lib-core.composition")
local M = {} local M = {}
@@ -20,15 +26,33 @@ local WALK_SPEED_SPRINT = 240 -- 2x
local ANIM_SPEED_NORMAL = 0.45 local ANIM_SPEED_NORMAL = 0.45
local ANIM_SPEED_SPRINT = 0.9 -- 2x local ANIM_SPEED_SPRINT = 0.9 -- 2x
-- Furniture positions (matches M.draw hardcoded sprites). -- Furniture positions (Bed/Bench remain hardcoded for v0.1; Backpack
-- promoted to Inactive-Entity via composition. Bed/Bench have no
-- interaction-trigger and no demo-pflicht to be entities — they stay
-- atlas-furniture until Phase B (container-on-bed) or similar triggers).
local BACKPACK_X, BACKPACK_Y = 350, 400 local BACKPACK_X, BACKPACK_Y = 350, 400
local INTERACTION_RANGE = 40 -- pixels local INTERACTION_RANGE = 40 -- pixels
-- Backpack-Template (Phase A.4 — Atlas-UV render-path).
-- Real sprite_atlas + sprite_uv values are filled in M.init after the
-- atlas-JSON is parsed; defaults here are placeholders so the inert-
-- declaration types are correct (string + numbers).
composition.define_template{
id = "backpack",
properties = {
sprite_atlas = "",
sprite_uv = {x = 0, y = 0, w = 1, h = 1},
position = {x = 0, y = 0},
},
tags = {"renderable"},
}
local state = { local state = {
player = nil, player = nil,
map = nil, map = nil,
world_tex = nil, -- subterrain_world atlas diffuse handle world_tex = nil, -- subterrain_world atlas diffuse handle (used for bed/bench)
furniture_uvs = nil, -- name -> { x, y, w, h } from atlas JSON furniture_uvs = nil, -- name -> { x, y, w, h } from atlas JSON
backpack = nil, -- composition-entity, created in M.init after atlas load
-- Subterrain-style controller state: -- Subterrain-style controller state:
walk_anim_current = nil, -- "walk_fwd_lower" / "walk_back_lower" / etc. (nil = idle_lower) walk_anim_current = nil, -- "walk_fwd_lower" / "walk_back_lower" / etc. (nil = idle_lower)
lower_control_rot_deg = 0, lower_control_rot_deg = 0,
@@ -191,13 +215,31 @@ function M.init(ctx)
-- Furniture atlas (subterrain_world): load JSON + diffuse texture once. -- Furniture atlas (subterrain_world): load JSON + diffuse texture once.
local world_lib = aliases.subterrain_world local world_lib = aliases.subterrain_world
local world_base = world_lib .. "/assets/atlases/subterrain_world" local world_base = world_lib .. "/assets/atlases/subterrain_world"
local world_atlas_png = world_base .. "/tiles.diffuse.atlas.png"
local world_meta = engine.asset.load_json(world_base .. "/tiles.atlas.json") local world_meta = engine.asset.load_json(world_base .. "/tiles.atlas.json")
state.world_tex = engine.asset.load_texture(world_base .. "/tiles.diffuse.atlas.png") state.world_tex = engine.asset.load_texture(world_atlas_png)
state.furniture_uvs = {} state.furniture_uvs = {}
for _, t in ipairs(world_meta.tiles) do for _, t in ipairs(world_meta.tiles) do
state.furniture_uvs[t.name] = { x = t.uv[1], y = t.uv[2], w = t.uv[3], h = t.uv[4] } state.furniture_uvs[t.name] = { x = t.uv[1], y = t.uv[2], w = t.uv[3], h = t.uv[4] }
end end
-- Spawn Backpack-Entity with real Atlas-UV values now that the
-- atlas-json is parsed. lib-core.render's atlas-cache will load
-- the texture lazy-on-first-draw (separate from state.world_tex
-- which serves bed/bench's direct draw_sprite_transform call;
-- ineffizient aber kein-Schaden, single-texture-handle-merge ist
-- ein späterer Slice).
local backpack_uv = state.furniture_uvs["backpack_001"]
state.backpack = composition.create{
template = "backpack",
properties = {
sprite_atlas = world_atlas_png,
sprite_uv = {x = backpack_uv.x, y = backpack_uv.y,
w = backpack_uv.w, h = backpack_uv.h},
position = {x = BACKPACK_X, y = BACKPACK_Y},
},
}
-- Input bindings. -- Input bindings.
input.bind("walk_left", { "a" }) input.bind("walk_left", { "a" })
input.bind("walk_right", { "d" }) input.bind("walk_right", { "d" })
@@ -207,13 +249,18 @@ function M.init(ctx)
input.bind("interact", { "e" }) -- fires nearest proximity-trigger input.bind("interact", { "e" }) -- fires nearest proximity-trigger
input.bind("quit_to_launcher", { "escape" }) input.bind("quit_to_launcher", { "escape" })
-- Proximity trigger on the backpack sprite (drawn at BACKPACK_X/Y in M.draw). -- Proximity trigger on the backpack-Entity. Anchor read from the
-- Range 40px; press E while within that radius to fire the callback. -- entity's position-Properties; sign is stationary so a snapshot is
interaction.register(BACKPACK_X, BACKPACK_Y, INTERACTION_RANGE, "interact", function(ctx) -- correct (interaction-API takes raw numbers, not closures, in v0.1).
engine.print(string.format( interaction.register(
"vagrant: interact with backpack (distance=%.1f)", state.backpack:get_property("position.x"),
ctx.distance)) state.backpack:get_property("position.y"),
end) INTERACTION_RANGE, "interact",
function(ctx)
engine.print(string.format(
"vagrant: interact with backpack (distance=%.1f)",
ctx.distance))
end)
-- CI trace: assets loaded count (3 atlas textures: player + tiles + world). -- CI trace: assets loaded count (3 atlas textures: player + tiles + world).
if CI_FRAME_PERF and not ci_assets_traced then if CI_FRAME_PERF and not ci_assets_traced then
@@ -321,20 +368,20 @@ end
function M.draw(ctx) function M.draw(ctx)
camera.begin() camera.begin()
maps.draw_map_pre_entities() maps.draw_map_pre_entities()
-- Hardcoded furniture placements (test-chamber spirit). -- Bed + Bench remain hardcoded furniture (no interaction-trigger →
-- Render via source-rect primitive using subterrain_world atlas UVs. -- no entity-Pflicht in Phase A; see Backpack-Template comment).
local bed_uv = state.furniture_uvs["bed_001"] local bed_uv = state.furniture_uvs["bed_001"]
local bench_uv = state.furniture_uvs["bench_001"] local bench_uv = state.furniture_uvs["bench_001"]
local backpack_uv = state.furniture_uvs["backpack_001"]
engine.render.draw_sprite_transform(state.world_tex, 200, 150, 0, 1, 1, engine.render.draw_sprite_transform(state.world_tex, 200, 150, 0, 1, 1,
bed_uv.w / 2, bed_uv.h / 2, 0xFFFFFFFF, bed_uv.w / 2, bed_uv.h / 2, 0xFFFFFFFF,
bed_uv.x, bed_uv.y, bed_uv.w, bed_uv.h) bed_uv.x, bed_uv.y, bed_uv.w, bed_uv.h)
engine.render.draw_sprite_transform(state.world_tex, 400, 300, 0, 1, 1, engine.render.draw_sprite_transform(state.world_tex, 400, 300, 0, 1, 1,
bench_uv.w / 2, bench_uv.h / 2, 0xFFFFFFFF, bench_uv.w / 2, bench_uv.h / 2, 0xFFFFFFFF,
bench_uv.x, bench_uv.y, bench_uv.w, bench_uv.h) bench_uv.x, bench_uv.y, bench_uv.w, bench_uv.h)
engine.render.draw_sprite_transform(state.world_tex, 350, 400, 0, 1, 1, -- Backpack (and any other renderable entity) via Phase-A.2 tag-based
backpack_uv.w / 2, backpack_uv.h / 2, 0xFFFFFFFF, -- render. The atlas-cache loads tiles.diffuse.atlas.png lazy-on-first
backpack_uv.x, backpack_uv.y, backpack_uv.w, backpack_uv.h) -- call (separate handle from state.world_tex — see M.init note).
r_lib.draw_entities{ tag = "renderable" }
-- Puppet (sprite-mode via texture_handles populated by load_rig+load_textures). -- Puppet (sprite-mode via texture_handles populated by load_rig+load_textures).
puppet.render(state.player) puppet.render(state.player)
maps.draw_map_post_entities() maps.draw_map_post_entities()

View File

@@ -1,6 +1,6 @@
{ {
"id": "vagrant-skeleton", "id": "vagrant-skeleton",
"version": "0.4.4", "version": "0.6.0",
"kind": "module", "kind": "module",
"api": "^0.1", "api": "^0.1",
"ci_frames": 60, "ci_frames": 60,
@@ -14,7 +14,8 @@
"deps": [ "deps": [
{"id": "lib-core.input", "version": "0.4.0"}, {"id": "lib-core.input", "version": "0.4.0"},
{"id": "lib-core.camera", "version": "0.3.0"}, {"id": "lib-core.camera", "version": "0.3.0"},
{"id": "lib-core.render", "version": "0.1.0"}, {"id": "lib-core.render", "version": "0.2.0"},
{"id": "lib-core.composition", "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"},