From 9de4924706abec06039874bd525f04d9810f25b4 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Mon, 18 May 2026 20:13:44 +0200 Subject: [PATCH] exp(vagrant): try procedural lower-body, drop WASD-to-root coupling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - humanoid.rig.json: removed locomotion-block (apply_locomotion no-ops now). head: removed look_at_speed (instant snap). body: unchanged (look_at_speed=180 lazy). - init.lua: removed set_procedural("facing", ...) — root stays at rest.rot=0. walk.anim + idle_lower.anim no longer loaded/played (files retained on disk for revert). New set_procedural("walk_cycle", ...) drives legs per-frame: walk_phase advances at 1.25Hz, sin-cycle on leg.scl, walk_dir applied to leg.rot. Idle: legs scl=0. - Lazy body + instant head + head-leading: head responds immediately to mouse, body lags via look_at_speed=180. This is an experiment. If procedural feels worse, revert by re-enabling walk.anim + idle_lower.anim + locomotion-block. --- init.lua | 69 ++++++++++++++++++++++++++++-------------- rigs/humanoid.rig.json | 18 ++--------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/init.lua b/init.lua index ad6ea1b..3a719c7 100644 --- a/init.lua +++ b/init.lua @@ -1,4 +1,4 @@ --- sporel-module-vagrant-skeleton v0.3.1 +-- sporel-module-vagrant-skeleton v0.3.2 -- Sprite-mode test-chamber: 13-bone humanoid puppet + sprite-tilemap + -- hardcoded furniture. F.vagrant Tier-1 + Sprite-Upgrade-Bridge state. @@ -17,7 +17,10 @@ local state = { bed_tex = nil, bench_tex = nil, backpack_tex = nil, - last_walk_rot_deg = 0, -- persisted root.rot (degrees) across idle frames + is_walking = false, + walk_phase = 0, -- 0..1 cycle position + walk_dir_rad = 0, -- world-angle of walk direction (sprite-top convention) + walk_dir_set = false, -- true after first move; legs orient even at idle once set } -- CI hook gating (mirrors Phase 1 convention). @@ -33,18 +36,14 @@ local ci_assets_traced = false local function set_walk_or_idle(moving) local new_state = moving and "walk" or "idle" if moving then - if not puppet.is_playing(state.player, "walk") then + if not puppet.is_playing(state.player, "walk_upper") then puppet.stop(state.player, "idle") - puppet.stop(state.player, "idle_lower") - puppet.play(state.player, "walk", { loop = true }) puppet.play(state.player, "walk_upper", { loop = true }) end else if not puppet.is_playing(state.player, "idle") then - puppet.stop(state.player, "walk") puppet.stop(state.player, "walk_upper") - puppet.play(state.player, "idle", { loop = true }) - puppet.play(state.player, "idle_lower", { loop = true }) + puppet.play(state.player, "idle", { loop = true }) end end if CI_FRAME_PERF and new_state ~= ci_last_state then @@ -64,22 +63,43 @@ function M.init(ctx) -- Puppet rig + sprite textures + animations. local rig = puppet.load_rig("rigs/humanoid.rig.json", aliases) state.player = puppet.spawn(rig, { x = 320, y = 240 }) - local walk = puppet.load_animation("animations/walk.anim.json", rig) local walk_upper = puppet.load_animation("animations/walk_upper.anim.json", rig) local idle = puppet.load_animation("animations/idle.anim.json", rig) - local idle_lower = puppet.load_animation("animations/idle_lower.anim.json", rig) - puppet.register_animation(state.player, idle) - puppet.register_animation(state.player, walk) puppet.register_animation(state.player, walk_upper) - puppet.register_animation(state.player, idle_lower) - puppet.play(state.player, "idle", { loop = true }) - puppet.play(state.player, "idle_lower", { loop = true }) + puppet.register_animation(state.player, idle) + puppet.play(state.player, "idle", { loop = true }) - -- Procedural facing: root.rot = state.last_walk_rot_deg every frame. - -- Fires inside update_bone_recursive at root-visit (after reset, before compute_world). - -- Root is not in any track, so write_bone bypasses track-guard. - puppet.set_procedural(state.player, "facing", function(handle, dt) - puppet.write_bone(handle, "root", { rot = state.last_walk_rot_deg }) + -- Procedural lower-body: leg-cycle driven per-frame by walk-direction + phase. + -- Replaces keyframe walk.anim + idle_lower.anim. Runs every frame in + -- update_bone_recursive's procedural callback step. + puppet.set_procedural(state.player, "walk_cycle", function(handle, dt) + if state.is_walking then + -- Advance phase. 0.8s full cycle (1/0.8 = 1.25 Hz, matches substrate walk_fwd_lower). + state.walk_phase = (state.walk_phase + dt * 1.25) % 1.0 + local phase_rad = state.walk_phase * 2 * math.pi + local cycle_l = math.sin(phase_rad) + local cycle_r = -cycle_l + local walk_dir_deg = math.deg(state.walk_dir_rad) + -- Leg-rotation orients along walk direction (root stays at 0; legs rotate directly) + puppet.write_bone(handle, "leg_l", { rot = walk_dir_deg, scl = {-1, cycle_l} }) + puppet.write_bone(handle, "leg_r", { rot = walk_dir_deg, scl = { 1, cycle_r} }) + -- Foot: scale follows leg-extension absolute value; pos along leg axis + puppet.write_bone(handle, "foot_l", { + scl = { 1, math.abs(cycle_l) }, + pos = { 0, cycle_l > 0 and 25 or 0 } + }) + puppet.write_bone(handle, "foot_r", { + scl = { 1, math.abs(cycle_r) }, + pos = { 0, cycle_r > 0 and 25 or 0 } + }) + else + -- Idle: legs collapsed (scl=0), feet collapsed and centered + local walk_dir_deg = state.walk_dir_set and math.deg(state.walk_dir_rad) or 0 + puppet.write_bone(handle, "leg_l", { rot = walk_dir_deg, scl = {-1, 0} }) + puppet.write_bone(handle, "leg_r", { rot = walk_dir_deg, scl = { 1, 0} }) + puppet.write_bone(handle, "foot_l", { scl = { 1, 0 }, pos = { 0, 0 } }) + puppet.write_bone(handle, "foot_r", { scl = { 1, 0 }, pos = { 0, 0 } }) + end end) -- Furniture textures (preload to avoid first-frame load stutter). @@ -126,7 +146,8 @@ function M.update(ctx, dt) if ci_drive_frame > 20 and ci_drive_frame <= 40 then dx = 1; dy = 0 moving = true - state.last_walk_rot_deg = math.deg(math.atan(dx, -dy)) -- atan(1, 0) = pi/2 = 90deg + state.walk_dir_rad = math.atan(dx, -dy) -- atan(1, 0) = pi/2 = 90deg + state.walk_dir_set = true local pos = puppet.position(state.player) puppet.move_to(state.player, pos.x + dx * state.player_speed * dt, pos.y) @@ -137,13 +158,15 @@ function M.update(ctx, dt) if moving and not CI_DRIVE then local len = math.sqrt(dx * dx + dy * dy) - -- Update last_walk_rot_deg (sprite-top convention: angle=atan(dx,-dy)). - state.last_walk_rot_deg = math.deg(math.atan(dx / len, -dy / len)) + -- Update walk_dir_rad (sprite-top convention: angle=atan(dx,-dy)). + state.walk_dir_rad = math.atan(dx / len, -dy / len) + state.walk_dir_set = true local pos = puppet.position(state.player) puppet.move_to(state.player, pos.x + dx / len * state.player_speed * dt, pos.y + dy / len * state.player_speed * dt) end + state.is_walking = moving set_walk_or_idle(moving) -- Headtrack: head looks at mouse world-position. diff --git a/rigs/humanoid.rig.json b/rigs/humanoid.rig.json index 988fbd5..b987ee2 100644 --- a/rigs/humanoid.rig.json +++ b/rigs/humanoid.rig.json @@ -4,7 +4,7 @@ "bones": [ { "id": "root", "parent": null, "rest": {"x": 0, "y": 0, "rot": 0}, "z_order": -1 }, { "id": "body", "parent": "root", "rest": {"x": 0, "y": 0, "rot": 0}, "z_order": 2, "texture": "body_001", "look_at": true, "look_at_speed": 180 }, - { "id": "head", "parent": "body", "rest": {"x": 0, "y": -3, "rot": 0, "rot_min": -90, "rot_max": 90}, "z_order": 6, "texture": "head_001", "look_at": true, "look_at_speed": 480 }, + { "id": "head", "parent": "body", "rest": {"x": 0, "y": -3, "rot": 0, "rot_min": -90, "rot_max": 90}, "z_order": 6, "texture": "head_001", "look_at": true }, { "id": "shoulders", "parent": "body", "rest": {"x": 0, "y": 0, "rot": 0}, "z_order": 5, "texture": "shoulder_001" }, { "id": "upper_arm_l", "parent": "shoulders", "rest": {"x": -19, "y": -3, "rot": 0, "scl": [-1, 1]}, "z_order": 3, "texture": "upper_arm_001" }, { "id": "upper_arm_r", "parent": "shoulders", "rest": {"x": 20, "y": -4, "rot": 0}, "z_order": 3, "texture": "upper_arm_001" }, @@ -18,19 +18,5 @@ "tracks": [ { "id": "lower", "bones": ["leg_l", "leg_r", "foot_l", "foot_r"] }, { "id": "upper", "bones": ["body", "head", "shoulders", "upper_arm_l", "upper_arm_r", "lower_arm_l", "lower_arm_r"] } - ], - "locomotion": { - "mode": "procedural", - "step_trigger": [6, 24], - "step_placement": [16, 28], - "step_duration": 0.18, - "step_height": 0, - "idle_return_delay": 0.5, - "idle_return_stagger": 0.3, - "idle_step_duration": 0.25, - "legs": [ - { "leg": "leg_l", "foot": "foot_l", "group": 0 }, - { "leg": "leg_r", "foot": "foot_r", "group": 1 } - ] - } + ] }