-- ===================================================================== -- lib-core.player_control — Player Movement + AABB-Wall-Collision (P.0) -- See: meta/docs/superpowers/specs/2026-05-09-p0-lib-player_control-design.md -- -- Scope: 1 player, top-left position + size + speed, 4-corner-AABB -- collision against maps.is_walkable, action-bindings via lib-core.input. -- DEPRECATED-MVP for: wall-sliding, actor-integration, sprite-rendering, -- click-to-move, drag-select, animation, velocity/momentum, multi-player. -- ===================================================================== local maps = require("lib-core.maps") local input = require("lib-core.input") local player = { x = 0, y = 0, -- top-left in pixel-coords w = 24, h = 24, speed = 200, -- px/sec } local actions = { left = nil, right = nil, up = nil, down = nil } local M = {} function M.set_position(x, y) if type(x) ~= "number" or type(y) ~= "number" then error("player_control.set_position: x and y must be numbers") end player.x = x; player.y = y end function M.position() return { x = player.x, y = player.y } end function M.set_size(w, h) if type(w) ~= "number" or type(h) ~= "number" or w <= 0 or h <= 0 then error("player_control.set_size: w and h must be positive numbers") end player.w = w; player.h = h end function M.size() return { w = player.w, h = player.h } end function M.set_speed(s) if type(s) ~= "number" or s < 0 then error("player_control.set_speed: speed must be a non-negative number") end player.speed = s end function M.speed() return player.speed end 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 (x, y, w, h) -- sit on walkable tiles per maps.is_walkable. local function can_occupy(x, y) local ts = maps.tile_size() local corners = { {x, y}, {x + player.w - 1, y}, {x, y + player.h - 1}, {x + player.w - 1, y + player.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 function M.update(dt) 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 nx = player.x + d.x * player.speed * dt local ny = player.y + d.y * player.speed * dt if can_occupy(nx, ny) then player.x = nx player.y = ny end -- else: blocked, no movement (no wall-sliding in P.0) end -- DEPRECATED-MVP: separate-axis wall-sliding -- fluidity slice -- DEPRECATED-MVP: actor-integration (player as Actor) -- lib-core.actor slice -- DEPRECATED-MVP: sprite-rendering -- texture-render slice -- DEPRECATED-MVP: click-to-move + drag-select -- P.3 RTS slice -- DEPRECATED-MVP: animation-state-machine -- animation slice -- DEPRECATED-MVP: velocity/momentum -- juice slice -- DEPRECATED-MVP: collision against maps.walls field -- when walls populated -- DEPRECATED-MVP: multi-player (multi-instance) -- multiplayer slice return M