feat(vagrant): sprint (shift+wasd) + backpack interaction

Sprint: holding shift while WASD doubles player_speed (120 → 240) and
walk-anim play-speed (0.45 → 0.9). Mid-walk sprint toggle uses puppet
v0.4.4 set_play_speed → no cycle restart. Releasing shift or stopping
WASD reverts to normal speed in next frame.

Interaction: 'interact' bound to E. Proximity trigger registered on
the backpack sprite at (350, 400) with range 40px. Pressing E within
range fires callback that prints a log message with the actor distance.
Uses existing lib-core.interaction (registered at init, polled by
interaction.update(player.x, player.y) each frame).

New dep: lib-core.interaction ^0.1.0. Module version 0.4.0 → 0.4.4
(syncs with puppet bump).
This commit is contained in:
Axel Meyer
2026-05-18 23:46:23 +02:00
parent a064825160
commit c4f2c9cda9
2 changed files with 60 additions and 18 deletions

View File

@@ -1,20 +1,31 @@
-- sporel-module-vagrant-skeleton v0.4.0
-- sporel-module-vagrant-skeleton v0.4.4
-- 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, virtual lower_control bone,
-- 4 directional walk animations, HandleAnimationLower port.
-- multi-look-target (aim/soft), slerp body rotation, hip-static legs,
-- foot-body-orientation. Sprint (shift+wasd) + interaction (E near
-- backpack) added v0.4.4.
local input = require("lib-core.input")
local camera = require("lib-core.camera")
local r_lib = require("lib-core.render")
local maps = require("lib-core.maps")
local puppet = require("lib-core.puppet")
local interaction = require("lib-core.interaction")
local M = {}
-- Walk + sprint speed tunings.
local WALK_SPEED_NORMAL = 120
local WALK_SPEED_SPRINT = 240 -- 2x
local ANIM_SPEED_NORMAL = 0.45
local ANIM_SPEED_SPRINT = 0.9 -- 2x
-- Furniture positions (matches M.draw hardcoded sprites).
local BACKPACK_X, BACKPACK_Y = 350, 400
local INTERACTION_RANGE = 40 -- pixels
local state = {
player = nil,
player_speed = 120,
map = nil,
bed_tex = nil,
bench_tex = nil,
@@ -24,6 +35,7 @@ local state = {
lower_control_rot_deg = 0,
move_x = 0,
move_y = 0,
sprinting = false, -- shift+wasd = 2x speed + 2x anim
}
-- CI hook gating (mirrors Phase 1 convention).
@@ -113,10 +125,11 @@ local function update_lower_anim(moving, move_x, move_y)
end
puppet.stop(state.player, "idle_lower")
puppet.stop(state.player, "idle")
-- Slow walk to 0.4x substrate speed (user pref: still too fast at 0.5).
puppet.play(state.player, anim_id, { loop = true, speed = 0.45 })
-- Play with current speed (normal or sprint).
local anim_speed = state.sprinting and ANIM_SPEED_SPRINT or ANIM_SPEED_NORMAL
puppet.play(state.player, anim_id, { loop = true, speed = anim_speed })
if not puppet.is_playing(state.player, "walk_upper") then
puppet.play(state.player, "walk_upper", { loop = true, speed = 0.45 })
puppet.play(state.player, "walk_upper", { loop = true, speed = anim_speed })
end
state.walk_anim_current = anim_id
if CI_FRAME_PERF and ci_last_state ~= "walk" then
@@ -187,8 +200,18 @@ function M.init(ctx)
input.bind("walk_right", { "d" })
input.bind("walk_up", { "w" })
input.bind("walk_down", { "s" })
input.bind("sprint", { "shift" }) -- held while walking → 2x speed
input.bind("interact", { "e" }) -- fires nearest proximity-trigger
input.bind("quit_to_launcher", { "escape" })
-- Proximity trigger on the backpack sprite (drawn at BACKPACK_X/Y in M.draw).
-- Range 40px; press E while within that radius to fire the callback.
interaction.register(BACKPACK_X, BACKPACK_Y, INTERACTION_RANGE, "interact", function(ctx)
engine.print(string.format(
"vagrant: interact with backpack (distance=%.1f)",
ctx.distance))
end)
-- CI trace: assets loaded count (puppet+tilemap+furniture textures).
if CI_FRAME_PERF and not ci_assets_traced then
-- 7 puppet textures + 4 tile textures + 3 furniture textures = 14
@@ -215,6 +238,13 @@ function M.update(ctx, dt)
if input.is_action_down("walk_down") then dy = dy + 1 end
local moving = (dx ~= 0 or dy ~= 0)
-- Sprint = shift held + actually moving. Updated BEFORE move so the
-- selected speed matches; also feeds update_lower_anim's anim-speed
-- pick at walk-start transitions.
local sprint_held = input.is_action_down("sprint")
state.sprinting = moving and sprint_held
local current_walk_speed = state.sprinting and WALK_SPEED_SPRINT or WALK_SPEED_NORMAL
-- CI_DRIVE: synthetic scripted move (preserve existing P0-S25b semantics).
if CI_DRIVE then
ci_drive_frame = ci_drive_frame + 1
@@ -223,7 +253,7 @@ function M.update(ctx, dt)
moving = true
local pos = puppet.position(state.player)
puppet.move_to(state.player,
pos.x + dx * state.player_speed * dt, pos.y)
pos.x + dx * WALK_SPEED_NORMAL * dt, pos.y)
else
moving = false
end
@@ -236,8 +266,8 @@ function M.update(ctx, dt)
state.move_y = dy / len
local pos = puppet.position(state.player)
puppet.move_to(state.player,
pos.x + state.move_x * state.player_speed * dt,
pos.y + state.move_y * state.player_speed * dt)
pos.x + state.move_x * current_walk_speed * dt,
pos.y + state.move_y * current_walk_speed * dt)
elseif not moving then
state.move_x = 0
state.move_y = 0
@@ -251,14 +281,25 @@ function M.update(ctx, dt)
update_lower_anim(moving, state.move_x, state.move_y)
-- Sprint toggle mid-walk: adjust play-speed of running anims without
-- restarting their cycle (puppet v0.4.4 set_play_speed). Silent no-op
-- if anim not currently playing.
local anim_speed = state.sprinting and ANIM_SPEED_SPRINT or ANIM_SPEED_NORMAL
puppet.set_play_speed(state.player, "walk_fwd_lower", anim_speed)
puppet.set_play_speed(state.player, "walk_upper", anim_speed)
-- Look-targets: head tracks raw mouse (aim), body tracks same for now (soft).
local mx, my = engine.input.get_mouse_pos()
local wx, wy = camera.screen_to_world(mx, my)
puppet.set_look_target(state.player, "aim", wx, wy)
puppet.set_look_target(state.player, "soft", wx, wy)
-- Camera follows puppet position each frame.
-- 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)
-- Camera follows puppet position each frame.
camera.set_target(pos.x, pos.y)
camera.update(dt)

View File

@@ -1,6 +1,6 @@
{
"id": "vagrant-skeleton",
"version": "0.4.0",
"version": "0.4.4",
"kind": "module",
"api": "^0.1",
"ci_frames": 60,
@@ -15,7 +15,8 @@
{"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.4.3"},
{"id": "lib-core.puppet", "version": "0.4.4"},
{"id": "lib-core.interaction", "version": "0.1.0"},
{"id": "lib-asset.prototype-subterrain", "version": "0.1.1"}
]
}