From 03a536984dad3bf0218f0c521c8d45fa2a9143f0 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Mon, 18 May 2026 19:51:56 +0200 Subject: [PATCH] =?UTF-8?q?fix(vagrant):=20iteration=20=E2=80=94=20body+he?= =?UTF-8?q?ad=20look-at,=20WASD-driven=20leg-facing,=20idle-collapse?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - humanoid.rig.json: root removed from upper track; body look_at + slow speed (180 deg/s, lazy); head fast speed (480 deg/s) + +-90 deg limits; puppet dep bumped to 0.3.1 - animations/idle_lower.anim.json (NEW): leg+foot scl=[mirror, 0] keeps legs invisible at idle (locomotion skips animated bones; foot cascade zeros out) - init.lua: set_procedural 'facing' callback writes root.rot from last_walk_rot_deg each frame; WASD-vector updates last_walk_rot_deg via atan(dx, -dy); idle_lower played parallel to idle, stopped during walk Co-Authored-By: Claude Sonnet 4.6 --- animations/idle_lower.anim.json | 13 +++++++++++++ init.lua | 22 +++++++++++++++++++--- manifest.module | 2 +- rigs/humanoid.rig.json | 6 +++--- 4 files changed, 36 insertions(+), 7 deletions(-) create mode 100644 animations/idle_lower.anim.json diff --git a/animations/idle_lower.anim.json b/animations/idle_lower.anim.json new file mode 100644 index 0000000..0d92f6d --- /dev/null +++ b/animations/idle_lower.anim.json @@ -0,0 +1,13 @@ +{ + "id": "idle_lower", + "track": "lower", + "duration": 1.0, + "loop": true, + "keyframes": [ + { "t": 0.0, + "leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, 0]}, + "foot_l": {"scl": [ 1, 0], "pos": [0, 0]}, + "foot_r": {"scl": [ 1, 0], "pos": [0, 0]} + } + ] +} diff --git a/init.lua b/init.lua index 957ca5c..ad6ea1b 100644 --- a/init.lua +++ b/init.lua @@ -1,4 +1,4 @@ --- sporel-module-vagrant-skeleton v0.2.0 +-- sporel-module-vagrant-skeleton v0.3.1 -- Sprite-mode test-chamber: 13-bone humanoid puppet + sprite-tilemap + -- hardcoded furniture. F.vagrant Tier-1 + Sprite-Upgrade-Bridge state. @@ -17,6 +17,7 @@ local state = { bed_tex = nil, bench_tex = nil, backpack_tex = nil, + last_walk_rot_deg = 0, -- persisted root.rot (degrees) across idle frames } -- CI hook gating (mirrors Phase 1 convention). @@ -34,6 +35,7 @@ local function set_walk_or_idle(moving) if moving then if not puppet.is_playing(state.player, "walk") 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 @@ -41,7 +43,8 @@ local function set_walk_or_idle(moving) 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", { loop = true }) + puppet.play(state.player, "idle_lower", { loop = true }) end end if CI_FRAME_PERF and new_state ~= ci_last_state then @@ -64,10 +67,20 @@ function M.init(ctx) 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.play(state.player, "idle", { loop = true }) + puppet.register_animation(state.player, idle_lower) + puppet.play(state.player, "idle", { loop = true }) + puppet.play(state.player, "idle_lower", { 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 }) + end) -- Furniture textures (preload to avoid first-frame load stutter). local world_lib = aliases.world @@ -113,6 +126,7 @@ 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 local pos = puppet.position(state.player) puppet.move_to(state.player, pos.x + dx * state.player_speed * dt, pos.y) @@ -123,6 +137,8 @@ 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)) local pos = puppet.position(state.player) puppet.move_to(state.player, pos.x + dx / len * state.player_speed * dt, diff --git a/manifest.module b/manifest.module index e31d326..e90b419 100644 --- a/manifest.module +++ b/manifest.module @@ -15,7 +15,7 @@ {"id": "lib-core.camera", "version": "0.3.0"}, {"id": "lib-core.render", "version": "0.1.0"}, {"id": "lib-core.maps", "version": "0.1.2"}, - {"id": "lib-core.puppet", "version": "0.3.0"}, + {"id": "lib-core.puppet", "version": "0.3.1"}, {"id": "lib-asset.prototype-subterrain", "version": "0.1.1"} ] } diff --git a/rigs/humanoid.rig.json b/rigs/humanoid.rig.json index f2da3d3..988fbd5 100644 --- a/rigs/humanoid.rig.json +++ b/rigs/humanoid.rig.json @@ -3,8 +3,8 @@ "asset_pack": "player", "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" }, - { "id": "head", "parent": "body", "rest": {"x": 0, "y": -3, "rot": 0}, "z_order": 6, "texture": "head_001", "look_at": true }, + { "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": "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" }, @@ -17,7 +17,7 @@ ], "tracks": [ { "id": "lower", "bones": ["leg_l", "leg_r", "foot_l", "foot_r"] }, - { "id": "upper", "bones": ["root", "body", "head", "shoulders", "upper_arm_l", "upper_arm_r", "lower_arm_l", "lower_arm_r"] } + { "id": "upper", "bones": ["body", "head", "shoulders", "upper_arm_l", "upper_arm_r", "lower_arm_l", "lower_arm_r"] } ], "locomotion": { "mode": "procedural",