fix(vagrant): substrate leg.scl cycle restored; feet inherit_scale=false

User feedback after iteration-4: legs SHOULD cycle through +1/0/-1
(substrate-style — leg renders forward, under body, then behind body).
But feet must NOT Y-flip when leg.scl_y goes negative, and must NOT
collapse when leg.scl_y=0. They should follow leg-end position only.

Three changes:
1. walk_fwd_lower keyframes restored to substrate-style (leg.scl_y
   sign-flip on both halves: leg_l +1↔-1, leg_r -1↔+1). foot.pos
   animations REMOVED — cascade through leg.world.scl now correctly
   moves foot to leg-end position automatically (foot.local.pos
   stays at rest 25 in y).
2. foot.rest.scl: [1, -1] → [1, 1] (no Y-flip in rest, natural
   sprite orientation). inherit_scale=false → foot.world.scl stays
   at foot.local.scl=[1,1] regardless of leg.scl_y. Foot sprite
   never flips, never collapses.
3. Procedural extended: writes foot.local.rot = -leg.local.rot so
   foot.world.rot = body.world.rot (feet stay body-oriented even
   when legs rotate to walk-direction).

Speed slowed further from 0.5 → 0.4 (user: still too fast at 0.5).
This commit is contained in:
Axel Meyer
2026-05-18 22:31:42 +02:00
parent bf5fe12e4c
commit b66c4ca4dd
4 changed files with 29 additions and 27 deletions

View File

@@ -5,24 +5,19 @@
"loop": true, "loop": true,
"keyframes": [ "keyframes": [
{ "t": 0.0, { "t": 0.0,
"leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, 0]}, "leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, 0]}
"foot_l": {"pos": [0, 0]}, "foot_r": {"pos": [0, 0]}
}, },
{ "t": 0.2, { "t": 0.2,
"leg_l": {"scl": [-1, -1]}, "leg_r": {"scl": [ 1, 0]}, "leg_l": {"scl": [-1, -1]}, "leg_r": {"scl": [ 1, 1]}
"foot_l": {"pos": [0, 25]}, "foot_r": {"pos": [0, 0]}
}, },
{ "t": 0.4, { "t": 0.4,
"leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, 0]}, "leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, 0]}
"foot_l": {"pos": [0, 0]}, "foot_r": {"pos": [0, 0]}
}, },
{ "t": 0.6, { "t": 0.6,
"leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, -1]}, "leg_l": {"scl": [-1, 1]}, "leg_r": {"scl": [ 1, -1]}
"foot_l": {"pos": [0, 0]}, "foot_r": {"pos": [0, 25]}
}, },
{ "t": 0.8, { "t": 0.8,
"leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, 0]}, "leg_l": {"scl": [-1, 0]}, "leg_r": {"scl": [ 1, 0]}
"foot_l": {"pos": [0, 0]}, "foot_r": {"pos": [0, 0]}
} }
] ]
} }

View File

@@ -113,11 +113,10 @@ local function update_lower_anim(moving, move_x, move_y)
end end
puppet.stop(state.player, "idle_lower") puppet.stop(state.player, "idle_lower")
puppet.stop(state.player, "idle") puppet.stop(state.player, "idle")
-- Slow walk to ~0.5x substrate speed (user preference: too fast for -- Slow walk to 0.4x substrate speed (user pref: still too fast at 0.5).
-- actual map traversal at substrate's 0.8s/cycle). puppet.play(state.player, anim_id, { loop = true, speed = 0.4 })
puppet.play(state.player, anim_id, { loop = true, speed = 0.5 })
if not puppet.is_playing(state.player, "walk_upper") then if not puppet.is_playing(state.player, "walk_upper") then
puppet.play(state.player, "walk_upper", { loop = true, speed = 0.5 }) puppet.play(state.player, "walk_upper", { loop = true, speed = 0.4 })
end end
state.walk_anim_current = anim_id state.walk_anim_current = anim_id
if CI_FRAME_PERF and ci_last_state ~= "walk" then if CI_FRAME_PERF and ci_last_state ~= "walk" then
@@ -156,17 +155,25 @@ function M.init(ctx)
puppet.play(state.player, "idle", { loop = true }) puppet.play(state.player, "idle", { loop = true })
puppet.play(state.player, "idle_lower", { loop = true }) puppet.play(state.player, "idle_lower", { loop = true })
-- Procedural leg-orientation: hips are body-relative (legs.parent=body, so -- Procedural leg+foot orientation:
-- cascade-position follows body); the leg ROTATION is overridden each -- * Hips are body-relative (legs.parent=body, position cascade follows body).
-- frame to align with walk-direction (independent of body rotation). -- * leg.world.rot = walk_dir (independent of body). Set leg.local.rot =
-- leg.local.rot = walk_dir_world - body.world.rot → leg.world.rot = walk_dir. -- walk_dir - body.world.rot so cascade gives correct world rot.
-- write_bone allowed on leg.rot because walk_fwd_lower only writes leg.scl -- * foot.world.rot = body.world.rot (user pref: feet stay body-oriented
-- (per-channel guard v0.4.2). -- even when legs swing to walk-direction = "tactical twist"). Cascade:
puppet.set_procedural(state.player, "leg_orientation", function(handle, dt) -- foot.world.rot = leg.world.rot + foot.local.rot = body.rot.
-- → foot.local.rot = body.rot - leg.world.rot = body.rot - walk_dir
-- = -leg_local_deg.
-- Per-channel guard v0.4.2 allows leg.rot + foot.rot writes alongside
-- walk_fwd_lower (which only writes leg.scl).
puppet.set_procedural(state.player, "leg_foot_orientation", function(handle, dt)
local _, _, body_rot = puppet.bone_world_transform(handle, "body") local _, _, body_rot = puppet.bone_world_transform(handle, "body")
local leg_local_deg = state.lower_control_rot_deg - math.deg(body_rot) local leg_local_deg = state.lower_control_rot_deg - math.deg(body_rot)
local foot_local_deg = -leg_local_deg
puppet.write_bone(handle, "leg_l", { rot = leg_local_deg }) puppet.write_bone(handle, "leg_l", { rot = leg_local_deg })
puppet.write_bone(handle, "leg_r", { rot = leg_local_deg }) puppet.write_bone(handle, "leg_r", { rot = leg_local_deg })
puppet.write_bone(handle, "foot_l", { rot = foot_local_deg })
puppet.write_bone(handle, "foot_r", { rot = foot_local_deg })
end) end)
-- Furniture textures. -- Furniture textures.

View File

@@ -15,7 +15,7 @@
{"id": "lib-core.camera", "version": "0.3.0"}, {"id": "lib-core.camera", "version": "0.3.0"},
{"id": "lib-core.render", "version": "0.1.0"}, {"id": "lib-core.render", "version": "0.1.0"},
{"id": "lib-core.maps", "version": "0.1.2"}, {"id": "lib-core.maps", "version": "0.1.2"},
{"id": "lib-core.puppet", "version": "0.4.2"}, {"id": "lib-core.puppet", "version": "0.4.3"},
{"id": "lib-asset.prototype-subterrain", "version": "0.1.1"} {"id": "lib-asset.prototype-subterrain", "version": "0.1.1"}
] ]
} }

View File

@@ -12,8 +12,8 @@
{ "id": "lower_arm_r", "parent": "upper_arm_r", "rest": {"x": 13, "y": -17, "rot": 0}, "z_order": 2, "texture": "lower_arm_001" }, { "id": "lower_arm_r", "parent": "upper_arm_r", "rest": {"x": 13, "y": -17, "rot": 0}, "z_order": 2, "texture": "lower_arm_001" },
{ "id": "leg_l", "parent": "body", "rest": {"x": -9, "y": 1, "rot": 0, "scl": [-1, -1]}, "z_order": 1, "texture": "leg_001" }, { "id": "leg_l", "parent": "body", "rest": {"x": -9, "y": 1, "rot": 0, "scl": [-1, -1]}, "z_order": 1, "texture": "leg_001" },
{ "id": "leg_r", "parent": "body", "rest": {"x": 9, "y": 1, "rot": 0, "scl": [ 1, -1]}, "z_order": 1, "texture": "leg_001" }, { "id": "leg_r", "parent": "body", "rest": {"x": 9, "y": 1, "rot": 0, "scl": [ 1, -1]}, "z_order": 1, "texture": "leg_001" },
{ "id": "foot_l", "parent": "leg_l", "rest": {"x": 0, "y": 25, "rot": 0, "scl": [ 1, -1]}, "z_order": 0, "texture": "foot_001" }, { "id": "foot_l", "parent": "leg_l", "rest": {"x": 0, "y": 25, "rot": 0, "scl": [ 1, 1]}, "z_order": 0, "texture": "foot_001", "inherit_scale": false },
{ "id": "foot_r", "parent": "leg_r", "rest": {"x": 0, "y": 26, "rot": 0, "scl": [ 1, -1]}, "z_order": 0, "texture": "foot_001" } { "id": "foot_r", "parent": "leg_r", "rest": {"x": 0, "y": 26, "rot": 0, "scl": [ 1, 1]}, "z_order": 0, "texture": "foot_001", "inherit_scale": false }
], ],
"tracks": [ "tracks": [
{ "id": "lower", "bones": ["leg_l", "leg_r", "foot_l", "foot_r"] }, { "id": "lower", "bones": ["leg_l", "leg_r", "foot_l", "foot_r"] },