From 3c5e63ea18454b2a16d1ebb92e3365b69c5bb458 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Tue, 9 Jun 2026 14:14:18 +0000 Subject: [PATCH] feat(0.7.0): Player als Actor-Entity, Puppet syncs (Phase A.6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- init.lua | 73 +++++++++++++++++++++++++++++++++++-------------- manifest.module | 3 +- 2 files changed, 55 insertions(+), 21 deletions(-) diff --git a/init.lua b/init.lua index 8592e06..34e2ce5 100644 --- a/init.lua +++ b/init.lua @@ -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,18 +54,33 @@ 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 - backpack = nil, -- composition-entity, created in M.init after atlas load + 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 -- 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, move_x = 0, move_y = 0, - sprinting = false, -- shift+wasd = 2x speed + 2x anim + sprinting = false, -- shift+wasd = 2x speed + 2x anim } -- CI hook gating (mirrors Phase 1 convention). @@ -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) diff --git a/manifest.module b/manifest.module index 308cade..c948ba0 100644 --- a/manifest.module +++ b/manifest.module @@ -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"},