feat(0.7.0): Player als Actor-Entity, Puppet syncs (Phase A.6)

Refactor: Player gesplittet in zwei Handles. state.actor (composition-
Entity) ist canonical position-owner. state.player (puppet handle)
bleibt für Animation + Render zuständig und syncs each frame via
puppet.move_to(state.player, actor.position(state.actor)).

vagrant_player-Template ist UNTAGGED — render.draw_entities sieht ihn
nicht, da puppet.render(state.player) das Sprite-Rendering schon macht.
Tag-Konflikt vermieden.

Movement-Refactor in M.update:
- CI_DRIVE-Branch: actor.move(state.actor, dx*speed*dt, 0)
- Normal-Movement: actor.move(state.actor, mx*speed*dt, my*speed*dt)
- Sync-Punkt nach beiden Branches: puppet.move_to(state.player,
  actor.position(state.actor))
- interaction.update + camera.set_target lesen actor.position

Sprint-Logic unverändert (eigene WALK_SPEED constants; movement_speed
auf actor ist informational/120).

Bumps: 0.6.0 → 0.7.0; +actor v0.1.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Axel Meyer
2026-06-09 14:14:18 +00:00
parent c976353637
commit 3c5e63ea18
2 changed files with 55 additions and 21 deletions

View File

@@ -1,4 +1,4 @@
-- sporel-module-vagrant-skeleton v0.6.0
-- sporel-module-vagrant-skeleton v0.7.0
-- Sprite-mode test-chamber: 13-bone humanoid puppet + sprite-tilemap +
-- hardcoded furniture. Subterrain-style puppet control model:
-- multi-look-target (aim/soft), slerp body rotation, hip-static legs,
@@ -9,6 +9,12 @@
-- 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).
--
-- v0.7.0 (Phase A.6): Player split — actor-entity (state.actor) owns
-- canonical position; puppet (state.player) syncs from actor each frame
-- via puppet.move_to. Movement-Logic moves the actor; puppet renders.
-- Untagged actor — render.draw_entities doesn't see it; puppet handles
-- the render via puppet.render(state.player) unchanged.
local input = require("lib-core.input")
local camera = require("lib-core.camera")
@@ -17,6 +23,7 @@ local maps = require("lib-core.maps")
local puppet = require("lib-core.puppet")
local interaction = require("lib-core.interaction")
local composition = require("lib-core.composition")
local actor = require("lib-core.actor")
local M = {}
@@ -47,8 +54,23 @@ composition.define_template{
tags = {"renderable"},
}
-- Player-Actor-Template (Phase A.6). Vagrant's player is an actor whose
-- position drives the puppet (sync each frame). NOT tagged renderable —
-- the puppet handles rendering, render.draw_entities doesn't see this
-- entity. movement_speed is informational; vagrant's sprint-logic uses
-- its own WALK_SPEED constants directly for the per-frame move.
composition.define_template{
id = "vagrant_player",
properties = {
position = {x = 0, y = 0},
movement_speed = 120, -- WALK_SPEED_NORMAL
},
tags = {}, -- intentionally untagged: not renderable
}
local state = {
player = nil,
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
@@ -170,6 +192,16 @@ function M.init(ctx)
maps.set_current(state.map)
maps.load_textures(aliases)
-- Player-Actor owns canonical position (Phase A.6). Puppet is spawned
-- at the same coords and stays in sync via puppet.move_to each frame.
state.actor = actor.create{
template = "vagrant_player",
properties = {
position = {x = 320, y = 240},
movement_speed = 120, -- WALK_SPEED_NORMAL
},
}
-- Puppet rig + sprite textures.
local rig = puppet.load_rig("rigs/humanoid.rig.json", aliases)
state.player = puppet.spawn(rig, { x = 320, y = 240 })
@@ -300,28 +332,30 @@ function M.update(ctx, dt)
if ci_drive_frame > 20 and ci_drive_frame <= 40 then
dx = 1; dy = 0
moving = true
local pos = puppet.position(state.player)
puppet.move_to(state.player,
pos.x + dx * WALK_SPEED_NORMAL * dt, pos.y)
actor.move(state.actor, dx * WALK_SPEED_NORMAL * dt, 0)
else
moving = false
end
end
-- Move puppet.
-- Move actor (position-owner). Puppet syncs from actor below.
if moving and not CI_DRIVE then
local len = math.sqrt(dx * dx + dy * dy)
state.move_x = dx / len
state.move_y = dy / len
local pos = puppet.position(state.player)
puppet.move_to(state.player,
pos.x + state.move_x * current_walk_speed * dt,
pos.y + state.move_y * current_walk_speed * dt)
actor.move(state.actor,
state.move_x * current_walk_speed * dt,
state.move_y * current_walk_speed * dt)
elseif not moving then
state.move_x = 0
state.move_y = 0
end
-- Sync puppet position from actor (Phase A.6: actor is source-of-truth).
-- Done once per frame after movement, before puppet.update consumes
-- position for animation.
puppet.move_to(state.player, actor.position(state.actor))
-- CI_DRIVE move_x/move_y for anim selection.
if CI_DRIVE and moving then
state.move_x = dx
@@ -344,12 +378,11 @@ function M.update(ctx, dt)
puppet.set_look_target(state.player, "soft", wx, wy)
-- Interaction: dispatches the nearest matching proximity-trigger when
-- 'interact' was pressed this frame. Uses player.position as actor.
local pos = puppet.position(state.player)
interaction.update(pos.x, pos.y)
-- 'interact' was pressed this frame. Uses actor.position as anchor.
interaction.update(actor.position(state.actor))
-- Camera follows puppet position each frame.
camera.set_target(pos.x, pos.y)
-- Camera follows actor position each frame.
camera.set_target(actor.position(state.actor))
camera.update(dt)
puppet.update(dt)

View File

@@ -1,6 +1,6 @@
{
"id": "vagrant-skeleton",
"version": "0.6.0",
"version": "0.7.0",
"kind": "module",
"api": "^0.1",
"ci_frames": 60,
@@ -16,6 +16,7 @@
{"id": "lib-core.camera", "version": "0.3.0"},
{"id": "lib-core.render", "version": "0.2.0"},
{"id": "lib-core.composition", "version": "0.1.0"},
{"id": "lib-core.actor", "version": "0.1.0"},
{"id": "lib-core.maps","version":"0.5.7"},
{"id": "lib-core.puppet", "version": "0.5.0"},
{"id": "lib-core.interaction", "version": "0.1.0"},