-- ===================================================================== -- lib-core.player_control v0.2.0 — Input → Actor Movement + AABB-Collision -- See: meta/docs/superpowers/specs/2026-05-09-p0-lib-player_control-design.md -- meta/docs/superpowers/specs/2026-06-09-phase-A-... -- -- v0.2.0 (Phase A.6) — BREAKING REFACTOR: -- Position/size/speed leben jetzt auf einer Actor-Entity (lib-core.actor -- + lib-core.composition); player_control hält nur noch die Action- -- Bindings und das per-frame Update. -- -- Caller sets up: -- composition.define_template{id="player", properties={position, -- movement_speed, sprite_w, sprite_h, sprite_color, ...}, ...} -- local p = actor.create{template="player", properties={...}} -- player_control.bind_movement("move_left", ...) -- player_control.update(p, dt) -- jeden Frame -- -- position.x/.y on the actor is treated as visual CENTER (per Phase-A -- render-convention). The collision-check uses sprite_w/sprite_h -- read from the actor's properties. -- -- Removed in v0.2.0 (use actor.* / composition.* instead): -- M.set_position / M.position / M.set_size / M.size / -- M.set_speed / M.speed — alle obsolete; lese via -- actor.position(handle) / handle:get_property("sprite_w") / -- actor.movement_speed(handle). -- -- DEPRECATED-MVP (unverändert): -- - separate-axis wall-sliding (fluidity slice) -- - sprite-rendering (lebt jetzt in render.draw_entities) -- - click-to-move + drag-select (P.3 RTS slice) -- - velocity/momentum (juice slice) -- - collision against maps.walls field (when walls populated) -- ===================================================================== local maps = require("lib-core.maps") local input = require("lib-core.input") local actor = require("lib-core.actor") local actions = { left = nil, right = nil, up = nil, down = nil } local M = {} function M.bind_movement(left, right, up, down) actions.left = left actions.right = right actions.up = up actions.down = down end -- 4-corner-AABB-check: returns true iff all 4 corners of the rect -- centered at (cx, cy) with size (w, h) sit on walkable tiles per -- maps.is_walkable. cx, cy is the visual center (Phase-A convention). local function can_occupy_centered(cx, cy, w, h) local ts = maps.tile_size() local x0 = cx - w / 2 local y0 = cy - h / 2 local corners = { {x0, y0}, {x0 + w - 1, y0}, {x0, y0 + h - 1}, {x0 + w - 1, y0 + h - 1}, } for _, c in ipairs(corners) do local tx, ty = engine.spatial.pixel_to_tile(c[1], c[2], ts) if not maps.is_walkable(tx, ty) then return false end end return true end -- Per-frame input-driven movement. Reads movement-speed + sprite-size -- from the actor's own properties; applies 4-corner collision check -- against the current map; commits via actor.move on pass, no-op on -- blocked (no wall-sliding in v0.2.0). -- -- Caller is expected to have invoked actor.create + bind_movement -- before the first update. function M.update(actor_handle, dt) if actor_handle == nil then error("player_control.update: actor_handle must not be nil") end if not actions.left then return end -- not bound, silent no-op local d = input.direction(actions.left, actions.right, actions.up, actions.down) if d.x == 0 and d.y == 0 then return end local cx, cy = actor.position(actor_handle) local speed = actor.movement_speed(actor_handle) local w = actor_handle:get_property("sprite_w") local h = actor_handle:get_property("sprite_h") local ncx = cx + d.x * speed * dt local ncy = cy + d.y * speed * dt if can_occupy_centered(ncx, ncy, w, h) then actor.move(actor_handle, ncx - cx, ncy - cy) end -- else: blocked, no movement (no wall-sliding in P.0/v0.2.0) end return M