From f7b38d984e0813bcefbe0ced7da5f0c51bd42f33 Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Sun, 17 May 2026 20:30:48 +0200 Subject: [PATCH] feat: add CI-hooks for render-hook + state-transition smoke Two env-var-gated hooks (no-op in normal play): - SPOREL_CI=1: emits 'vagrant: render_frame_ok' per frame from render() and 'vagrant: frame_avg_ms=N' after 60 frames. - SPOREL_VAGRANT_PHASE1_DRIVE=1: scripted move-right input on frames 21-40, idle otherwise. Combined with SPOREL_CI=1 produces observable idle -> walk -> idle state cycle. State-transition trace 'vagrant: state=' emits exactly once per actual change in set_walk_or_idle, so the smoke greps don't get noise from per-frame repeats. Fix camera.set_target_provider (absent from camera API) to use camera.set_target(x, y) called per frame from M.update instead. --- init.lua | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/init.lua b/init.lua index 44ff65a..8554107 100644 --- a/init.lua +++ b/init.lua @@ -19,8 +19,12 @@ local state = { local CI_FRAME_PERF = (os.getenv("SPOREL_CI") == "1") local ci_frame_count = 0 local ci_t_start = nil +local CI_DRIVE = (os.getenv("SPOREL_VAGRANT_PHASE1_DRIVE") == "1") +local ci_drive_frame = 0 +local ci_last_state = nil 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 puppet.stop(state.player, "idle") @@ -32,6 +36,10 @@ local function set_walk_or_idle(moving) puppet.play(state.player, "idle", { loop = true }) end end + if CI_FRAME_PERF and new_state ~= ci_last_state then + engine.print("vagrant: state=" .. new_state) + ci_last_state = new_state + end end function M.init(ctx) @@ -55,10 +63,9 @@ function M.init(ctx) input.bind("walk_down", { "s" }) input.bind("quit_to_launcher", { "escape" }) - -- Camera follows player position (no manual pan in vagrant-style) - camera.set_target_provider(function() - return puppet.position(state.player) - end) + -- Camera follows player on first frame + local init_pos = puppet.position(state.player) + camera.set_target(init_pos.x, init_pos.y) if CI_FRAME_PERF then ci_t_start = engine.time.now() @@ -85,6 +92,21 @@ function M.update(ctx, dt) pos.x + dx / len * state.player_speed * dt, pos.y + dy / len * state.player_speed * dt) end + + if CI_DRIVE then + ci_drive_frame = ci_drive_frame + 1 + -- Frames 1-20: idle (no move), 21-40: move-right, 41-60: idle again + 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 * state.player_speed * dt, pos.y) + else + moving = false + end + end + set_walk_or_idle(moving) -- Headtrack: head looks at mouse world-position @@ -92,6 +114,10 @@ function M.update(ctx, dt) local wx, wy = camera.screen_to_world(mx, my) puppet.set_look_target(state.player, wx, wy) + -- Camera follows player position each frame (vagrant-style no-pan) + local cam_pos = puppet.position(state.player) + camera.set_target(cam_pos.x, cam_pos.y) + camera.update(dt) puppet.update(dt)