feat(vagrant): v0.14.0 — TC_Basics first-consumer + UI item icons
ADR-0054 Item-Visual-Identity validated end-to-end.
World-sprite migration off the proprietary Subterrain world-atlas:
- Bed + Bench promoted to composition-entities with renderable tag;
rendered via the existing draw_entities path (the hardcoded
draw_sprite_transform calls for furniture are gone).
- Backpack sprite source: Subterrain backpack_001 → TC_Basics sack1.
- Stick visual: blob_rect_stone slot_04_straight → TC_Basics log_pile1.
- rock_pick crafted-output visual: blob_rect_stone slot_05_tee_open →
TC_Basics spade.
- rock + rock_pile + workbench stay on blob_rect_stone (no TC_Basics
equivalent yet).
Scale resolution is now atlas-level (no more module-side constants):
M.init reads tc_meta.atlas_meta.pixels_per_meter and divides into
PROJECT_PIXELS_PER_METER (128 per puppet shoulder anchor) to derive
tc_scale, applied per-entity via lib-core.render v0.3.0's sprite_
scale property.
UI item icons via ADR-0054 §3 Konsum:
- Backpack + Workbench-Inventory rows now show item icons via the
inventory-list-display v0.2.0 default resolver (fallback chain
icon_atlas → sprite_atlas), fit-to-bounds.
- Backpack-Crafting + Workbench-Crafting panels get an icon column
via crafting-display v0.3.0; vagrant supplies a small recipe-
output → {atlas, uv} lookup (craft_icons) and wires it as
icon_resolver on both widgets.
Deps cascaded: lib-core.render 0.2.0 → 0.3.0, lib-core.inventory-
list-display 0.1.1 → 0.2.0, lib-core.crafting-display 0.2.0 → 0.3.0,
lib-asset.prototype-tc-basics 0.1.0 → 0.3.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
196
init.lua
196
init.lua
@@ -1,6 +1,6 @@
|
||||
-- sporel-module-vagrant-skeleton v0.13.0
|
||||
-- sporel-module-vagrant-skeleton v0.14.0
|
||||
-- Sprite-mode test-chamber: 13-bone humanoid puppet + sprite-tilemap +
|
||||
-- hardcoded furniture. Subterrain-style puppet control model:
|
||||
-- TC_Basics furniture (bed/bench/backpack). Subterrain-style puppet control model:
|
||||
-- multi-look-target (aim/soft), slerp body rotation, hip-static legs,
|
||||
-- foot-body-orientation. Sprint (shift+wasd) + interaction (E near
|
||||
-- pickup-item) added v0.4.4.
|
||||
@@ -63,6 +63,17 @@
|
||||
-- Inventory with layout="right-half"; right-click opens both panels
|
||||
-- simultaneously side-by-side. Replaces the v0.12.0 single-active
|
||||
-- caveat where workbench_inv covered workbench_crafting.
|
||||
--
|
||||
-- v0.14.0: Vagrant migrates Bed + Bench + Backpack visuals off the
|
||||
-- proprietary Subterrain world-atlas onto TC_Basics sprites
|
||||
-- (single_bed1, bench1, sack1) — first concrete consumer of the
|
||||
-- ADR-0054 Item-Visual-Identity weiche. Bed + Bench promoted to
|
||||
-- composition-templates with `renderable` tag, rendered via the
|
||||
-- existing draw_entities path; no more hardcoded draw_sprite_transform
|
||||
-- calls for furniture. lib-asset.prototype-subterrain dep retained
|
||||
-- for the puppet sprite atlas (subterrain_player); the world-atlas
|
||||
-- (subterrain_world) drop closes the last furniture dependency on
|
||||
-- the Subterrain world-tiles.
|
||||
|
||||
local input = require("lib-core.input")
|
||||
local camera = require("lib-core.camera")
|
||||
@@ -89,19 +100,30 @@ local WALK_SPEED_SPRINT = 240 -- 2x
|
||||
local ANIM_SPEED_NORMAL = 0.45
|
||||
local ANIM_SPEED_SPRINT = 0.9 -- 2x
|
||||
|
||||
-- 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 a container-on-bed feature or similar triggers).
|
||||
-- World-furniture positions (all three now composition-entities with
|
||||
-- renderable tag; rendered via draw_entities). Positions are visual
|
||||
-- CENTER per lib-core.render v0.2.0 anchor convention.
|
||||
local BACKPACK_X, BACKPACK_Y = 350, 400
|
||||
local BED_X, BED_Y = 200, 150
|
||||
local BENCH_X, BENCH_Y = 400, 300
|
||||
local INTERACTION_RANGE = 40 -- pixels
|
||||
|
||||
-- World-overlay float colors (pickup confirmation)
|
||||
local COLOR_PICKUP_FLOAT = 0xFFFF80FF
|
||||
|
||||
-- Project canonical pixels-per-meter, anchored to the puppet shoulder
|
||||
-- width (64 px = 0.5 m per ~/sporel_tile_scale.md). All sprite atlases
|
||||
-- declare their own `atlas_meta.pixels_per_meter` in their sprite-mode
|
||||
-- JSON output (atlas-baker v0.4.0+); the per-entity scale-factor is
|
||||
-- derived as PROJECT_PIXELS_PER_METER / atlas_ppm at template-load time
|
||||
-- and applied via lib-core.render v0.3.0's `sprite_scale` property.
|
||||
-- Atlases authored at canonical ppm get scale 1.0 → no-op.
|
||||
local PROJECT_PIXELS_PER_METER = 128
|
||||
|
||||
-- Backpack-Template (Phase A.4 — Atlas-UV render-path).
|
||||
-- v0.8.0: container={kind="list"} declared so the backpack receives
|
||||
-- items via inventory.add (capability-by-declaration).
|
||||
-- v0.14.0: sprite source migrated to TC_Basics `sack1`.
|
||||
-- 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).
|
||||
@@ -110,12 +132,41 @@ composition.define_template{
|
||||
properties = {
|
||||
sprite_atlas = "",
|
||||
sprite_uv = {x = 0, y = 0, w = 1, h = 1},
|
||||
sprite_scale = 1.0,
|
||||
position = {x = 0, y = 0},
|
||||
},
|
||||
tags = {"renderable"},
|
||||
container = { kind = "list" }, -- receives items via inventory.add
|
||||
}
|
||||
|
||||
-- Bed + Bench-Templates (v0.14.0 — ADR-0054 first-consumer slice).
|
||||
-- Static world-furniture, no interaction-trigger, no container-block:
|
||||
-- just sprite + position + `renderable` tag so draw_entities renders
|
||||
-- them via the Atlas-UV path. Migrates off the proprietary Subterrain
|
||||
-- world-atlas onto TC_Basics `single_bed1` + `bench1`. Real
|
||||
-- sprite_atlas + sprite_uv values are filled in M.init after the
|
||||
-- TC_Basics sprite-mode atlas JSON is parsed.
|
||||
composition.define_template{
|
||||
id = "bed",
|
||||
properties = {
|
||||
sprite_atlas = "",
|
||||
sprite_uv = {x = 0, y = 0, w = 1, h = 1},
|
||||
sprite_scale = 1.0,
|
||||
position = {x = 0, y = 0},
|
||||
},
|
||||
tags = {"renderable"},
|
||||
}
|
||||
composition.define_template{
|
||||
id = "bench",
|
||||
properties = {
|
||||
sprite_atlas = "",
|
||||
sprite_uv = {x = 0, y = 0, w = 1, h = 1},
|
||||
sprite_scale = 1.0,
|
||||
position = {x = 0, y = 0},
|
||||
},
|
||||
tags = {"renderable"},
|
||||
}
|
||||
|
||||
-- Rock-Template: an item sitting in the world that the player can pick up.
|
||||
-- sprite_atlas + sprite_uv are filled in M.init after the blob_rect_stone atlas
|
||||
-- is loaded; placeholder defaults here keep the inert-declaration types correct.
|
||||
@@ -205,9 +256,9 @@ local state = {
|
||||
player = nil, -- puppet handle (state-bearing for animation)
|
||||
actor = nil, -- composition-entity owning position
|
||||
map = nil,
|
||||
world_tex = nil, -- subterrain_world atlas diffuse handle (used for bed/bench)
|
||||
furniture_uvs = nil, -- name -> { x, y, w, h } from atlas JSON
|
||||
backpack = nil, -- composition-entity, created in M.init after atlas load
|
||||
bed = nil, -- composition-entity (v0.14.0 — TC_Basics single_bed1)
|
||||
bench = nil, -- composition-entity (v0.14.0 — TC_Basics bench1)
|
||||
rock = nil, -- composition-entity (item), created in M.init after blob_rect_stone load
|
||||
stick = nil, -- composition-entity (item), placeholder for crafting demo
|
||||
workbench = nil, -- composition-entity (container), Phase D Workbench
|
||||
@@ -491,34 +542,61 @@ function M.init(ctx)
|
||||
puppet.write_bone(handle, "foot_r", { rot = foot_local_deg })
|
||||
end)
|
||||
|
||||
-- Furniture atlas (subterrain_world): load JSON + diffuse texture once.
|
||||
local world_lib = aliases.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")
|
||||
state.world_tex = engine.asset.load_texture(world_atlas_png)
|
||||
state.furniture_uvs = {}
|
||||
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] }
|
||||
end
|
||||
-- Furniture atlas (TC_Basics sprite-mode): load JSON + diffuse atlas.
|
||||
-- atlas-baker v0.4.0 sprite-mode output layout — sprites table is
|
||||
-- a map of alias → {x, y, w, h, source_file}; rendering reads
|
||||
-- x/y/w/h via render.draw_entities' Atlas-UV path. atlas_meta.
|
||||
-- pixels_per_meter declares the atlas's native resolution; scale
|
||||
-- against the project canonical to get the per-entity sprite_scale.
|
||||
local tc_lib = aliases.tc_basics
|
||||
local tc_base = tc_lib .. "/assets/atlases/tcbasics"
|
||||
local tc_atlas_png = tc_base .. "/sprites.diffuse.atlas.png"
|
||||
local tc_meta = engine.asset.load_json(tc_base .. "/sprites.uv.json")
|
||||
local tc_sprites = tc_meta.sprites
|
||||
local tc_atlas_ppm = (tc_meta.atlas_meta
|
||||
and tc_meta.atlas_meta.pixels_per_meter)
|
||||
or PROJECT_PIXELS_PER_METER
|
||||
local tc_scale = PROJECT_PIXELS_PER_METER / tc_atlas_ppm
|
||||
|
||||
-- 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"]
|
||||
-- Spawn Backpack-Entity with TC_Basics `sack1` sprite. lib-core.render's
|
||||
-- atlas-cache lazy-loads the texture on first draw_entities call.
|
||||
local sack_uv = tc_sprites.sack1
|
||||
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},
|
||||
sprite_atlas = tc_atlas_png,
|
||||
sprite_uv = {x = sack_uv.x, y = sack_uv.y,
|
||||
w = sack_uv.w, h = sack_uv.h},
|
||||
sprite_scale = tc_scale,
|
||||
position = {x = BACKPACK_X, y = BACKPACK_Y},
|
||||
},
|
||||
}
|
||||
|
||||
-- Spawn Bed + Bench entities (v0.14.0). Static world-furniture
|
||||
-- rendered via the same Atlas-UV path as the backpack.
|
||||
local bed_uv = tc_sprites.single_bed1
|
||||
state.bed = composition.create{
|
||||
template = "bed",
|
||||
properties = {
|
||||
sprite_atlas = tc_atlas_png,
|
||||
sprite_uv = {x = bed_uv.x, y = bed_uv.y,
|
||||
w = bed_uv.w, h = bed_uv.h},
|
||||
sprite_scale = tc_scale,
|
||||
position = {x = BED_X, y = BED_Y},
|
||||
},
|
||||
}
|
||||
local bench_uv = tc_sprites.bench1
|
||||
state.bench = composition.create{
|
||||
template = "bench",
|
||||
properties = {
|
||||
sprite_atlas = tc_atlas_png,
|
||||
sprite_uv = {x = bench_uv.x, y = bench_uv.y,
|
||||
w = bench_uv.w, h = bench_uv.h},
|
||||
sprite_scale = tc_scale,
|
||||
position = {x = BENCH_X, y = BENCH_Y},
|
||||
},
|
||||
}
|
||||
|
||||
-- Blob-rect-stone atlas: load JSON + diffuse texture for the rock sprite.
|
||||
-- blob_rect_stone alias resolves to lib-asset.prototype-blob-geom. The
|
||||
-- "slot_00_isolated" tile is the standalone isolated stone shape — reads as
|
||||
@@ -534,15 +612,15 @@ function M.init(ctx)
|
||||
end
|
||||
|
||||
-- Crafted-output templates: defined here (not top-level) so their
|
||||
-- sprite_atlas defaults can carry the resolved blob_rect_stone PNG path.
|
||||
-- sprite_atlas defaults can carry the resolved atlas PNG path.
|
||||
-- crafting.craft calls composition.create with no overrides, so the
|
||||
-- template defaults are the only carrier for sprite_atlas / sprite_uv
|
||||
-- on the crafted instance. Without this, crafted items render as a
|
||||
-- "?" placeholder in the inventory and a magenta-fallback square on
|
||||
-- drop (lib-core.render path-3).
|
||||
-- slot_05_tee_open reads as a tool-shaped tile (rock_pick).
|
||||
-- slot_13_solid reads as a dense cluster tile (rock_pile).
|
||||
local rock_pick_uv = stone_uvs["slot_05_tee_open"]
|
||||
-- v0.14.0: rock_pick visual switched to TC_Basics `spade`; rock_pile
|
||||
-- stays on blob_rect_stone (no TC_Basics rock-stack equivalent).
|
||||
local spade_uv = tc_sprites.spade
|
||||
composition.define_template{
|
||||
id = "rock_pick",
|
||||
properties = {
|
||||
@@ -553,9 +631,10 @@ function M.init(ctx)
|
||||
volume = 0.0015,
|
||||
["composition.stone"] = 0.9,
|
||||
["composition.wood"] = 0.1,
|
||||
sprite_atlas = stone_atlas_png,
|
||||
sprite_uv = {x = rock_pick_uv.x, y = rock_pick_uv.y,
|
||||
w = rock_pick_uv.w, h = rock_pick_uv.h},
|
||||
sprite_atlas = tc_atlas_png,
|
||||
sprite_uv = {x = spade_uv.x, y = spade_uv.y,
|
||||
w = spade_uv.w, h = spade_uv.h},
|
||||
sprite_scale = tc_scale,
|
||||
position = {x = 0, y = 0},
|
||||
},
|
||||
tags = {"renderable", "item"},
|
||||
@@ -627,14 +706,18 @@ function M.init(ctx)
|
||||
-- on the same Y as rock + backpack). Placeholder visual: slot_04_straight
|
||||
-- from blob_rect_stone reads as a long horizontal bar (Phase C-Q10 atlas
|
||||
-- re-use; echtes Stick-Sprite folgt mit atlas-baker-decals).
|
||||
-- v0.14.0: stick visual switched to TC_Basics `log_pile1`. Sprite_scale
|
||||
-- via property-override since the stick template default (1.0) was set
|
||||
-- before tc_scale was known.
|
||||
local STICK_X, STICK_Y = BACKPACK_X - 160, BACKPACK_Y
|
||||
local stick_uv = stone_uvs["slot_04_straight"]
|
||||
local log_pile_uv = tc_sprites.log_pile1
|
||||
state.stick = composition.create{
|
||||
template = "stick",
|
||||
properties = {
|
||||
sprite_atlas = stone_atlas_png,
|
||||
sprite_uv = {x = stick_uv.x, y = stick_uv.y,
|
||||
w = stick_uv.w, h = stick_uv.h},
|
||||
sprite_atlas = tc_atlas_png,
|
||||
sprite_uv = {x = log_pile_uv.x, y = log_pile_uv.y,
|
||||
w = log_pile_uv.w, h = log_pile_uv.h},
|
||||
sprite_scale = tc_scale,
|
||||
position = {x = STICK_X, y = STICK_Y},
|
||||
},
|
||||
}
|
||||
@@ -674,6 +757,23 @@ function M.init(ctx)
|
||||
description = "Stack rocks for storage.",
|
||||
}
|
||||
|
||||
-- v0.14.0: per-recipe icon resolver for the Crafting + Workbench-Crafting
|
||||
-- widgets. Maps recipe.output.template → {atlas, uv} for the icon
|
||||
-- column added in crafting-display v0.3.0. composition has no
|
||||
-- template-property-getter today (ADR-0001 keeps the read-side
|
||||
-- domain-free), so the module-side that DEFINES the templates is the
|
||||
-- canonical place to know each output's sprite.
|
||||
local craft_icons = {
|
||||
rock_pick = { atlas = tc_atlas_png, uv = tc_sprites.spade },
|
||||
rock_pile = { atlas = stone_atlas_png, uv = stone_uvs.slot_13_solid },
|
||||
}
|
||||
local function craft_icon_for(recipe)
|
||||
if recipe and recipe.output and recipe.output.template then
|
||||
return craft_icons[recipe.output.template]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Inventory-UI Setup
|
||||
panel.set_theme{ panel_width_frac = 0.4, font_size_title = 20 }
|
||||
|
||||
@@ -737,6 +837,7 @@ function M.init(ctx)
|
||||
ctx_factory = function()
|
||||
return { actor = state.actor }
|
||||
end,
|
||||
icon_resolver = craft_icon_for,
|
||||
})
|
||||
|
||||
-- Craft action: invokes crafting.craft on the backpack, posts a
|
||||
@@ -818,6 +919,7 @@ function M.init(ctx)
|
||||
ctx_factory = function()
|
||||
return { actor = state.actor, at_workbench = true }
|
||||
end,
|
||||
icon_resolver = craft_icon_for,
|
||||
})
|
||||
|
||||
-- Craft action: invokes crafting.craft on the resolved locale (multi-
|
||||
@@ -1148,19 +1250,9 @@ end
|
||||
function M.draw(ctx)
|
||||
camera.begin()
|
||||
maps.draw_map_pre_entities()
|
||||
-- Bed + Bench remain hardcoded furniture (no interaction-trigger →
|
||||
-- no entity-Pflicht in Phase A; see Backpack-Template comment).
|
||||
local bed_uv = state.furniture_uvs["bed_001"]
|
||||
local bench_uv = state.furniture_uvs["bench_001"]
|
||||
engine.render.draw_sprite_transform(state.world_tex, 200, 150, 0, 1, 1,
|
||||
bed_uv.w / 2, bed_uv.h / 2, 0xFFFFFFFF,
|
||||
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,
|
||||
bench_uv.w / 2, bench_uv.h / 2, 0xFFFFFFFF,
|
||||
bench_uv.x, bench_uv.y, bench_uv.w, bench_uv.h)
|
||||
-- Backpack (and any other renderable entity) via Phase-A.2 tag-based
|
||||
-- render. The atlas-cache loads tiles.diffuse.atlas.png lazy-on-first
|
||||
-- call (separate handle from state.world_tex — see M.init note).
|
||||
-- All renderable furniture + items via Phase-A.2 tag-based render
|
||||
-- (v0.14.0: Bed + Bench joined Backpack as composition-entities).
|
||||
-- lib-core.render's atlas-cache lazy-loads each sprite_atlas path.
|
||||
r_lib.draw_entities{ tag = "renderable" }
|
||||
-- Puppet (sprite-mode via texture_handles populated by load_rig+load_textures).
|
||||
puppet.render(state.player)
|
||||
|
||||
Reference in New Issue
Block a user