Files
Axel Meyer 2c7edc0762 ESC -> lib-management.launcher (renamed per ADR-0045)
The launcher migrated from lib-sporel.launcher to lib-management.launcher
per ADR-0045 (three-tier library naming). Update ESC handler + README
reference to match the new module id.
2026-05-31 00:22:27 +02:00

160 lines
5.8 KiB
Lua

-- =====================================================================
-- rts-prototype v0.1.0 — Pure-RTS Composer-Demo (P.3.8)
-- See: meta/docs/superpowers/specs/2026-05-14-p3-8-rts-prototype-design.md
--
-- Composer-Module per §3.2 Read-vs-Write-Linie: orchestriert alle
-- cross-lib-Setter-Aufrufe. Libs bleiben unabhängig.
--
-- Demo: 8 units, 3 speed-classes (100/150/200), LMB-select + RMB-move
-- via selection.list as target_provider. ESC → Launcher (ADR-0028).
-- SPOREL_CI=1: 60-frame self-exit + active move auf Frame 5 für smoke.
-- =====================================================================
local maps = require("lib-core.maps")
local r_lib = require("lib-core.render")
local camera = require("lib-core.camera")
local input = require("lib-core.input")
local selection = require("lib-core.selection")
local command = require("lib-core.command")
local rts_map = maps.load("maps/rts.map.json")
maps.set_current(rts_map)
local m_size = maps.size()
local t_size = maps.tile_size()
local map_center_x = (m_size.w * t_size) / 2
local map_center_y = (m_size.h * t_size) / 2
camera.set_target(map_center_x, map_center_y)
camera.set_zoom(1.0)
-- ===== Input Bindings (top-level, init-time) =====
input.bind("quit", { "escape" })
input.bind("pan_left", { "a", "left" })
input.bind("pan_right", { "d", "right" })
input.bind("pan_up", { "w", "up" })
input.bind("pan_down", { "s", "down" })
input.bind("pan_drag", { "mouse_middle" })
input.bind("lmb", { "mouse_left" })
input.bind("mod_add", { "shift" })
input.bind("mod_toggle", { "ctrl" })
input.bind("rmb", { "mouse_right" })
-- ===== Camera (free-pan, no bounds-clamp) =====
camera.bind_pan_keys("pan_left", "pan_right", "pan_up", "pan_down")
camera.bind_pan_drag("pan_drag")
camera.enable_pan_edge()
-- ===== Selection =====
selection.bind_action("lmb")
selection.bind_modifier_add("mod_add")
selection.bind_modifier_toggle("mod_toggle")
-- ===== Command =====
command.bind_move_action("rmb")
command.bind_target_provider(function() return selection.list() end)
-- ===== Units (8 total, 3 speed-classes) =====
local units = {
-- 3 graue Krieger (speed 100, slow)
{ x = 64, y = 96, w = 16, h = 16, color = {180, 60, 60}, speed = 100 },
{ x = 64, y = 128, w = 16, h = 16, color = {180, 60, 60}, speed = 100 },
{ x = 64, y = 160, w = 16, h = 16, color = {180, 60, 60}, speed = 100 },
-- 3 braune Skouts (speed 150, mid)
{ x = 96, y = 96, w = 16, h = 16, color = {200, 130, 60}, speed = 150 },
{ x = 96, y = 128, w = 16, h = 16, color = {200, 130, 60}, speed = 150 },
{ x = 96, y = 160, w = 16, h = 16, color = {200, 130, 60}, speed = 150 },
-- 2 gelbe Späher (speed 200, fast)
{ x = 128, y = 96, w = 16, h = 16, color = {220, 200, 60}, speed = 200 },
{ x = 128, y = 128, w = 16, h = 16, color = {220, 200, 60}, speed = 200 },
}
-- ===== CI state =====
local frames = 0
local TARGET_FRAMES = 60
local CI_MOVE_FRAME = 5
function init(ctx)
engine.print("rts-prototype: init ok")
engine.print(string.format("map: %dx%d tiles, render: %d tiles per frame",
m_size.w, m_size.h, m_size.w * m_size.h))
for _, u in ipairs(units) do
u.sel_handle = selection.register(function()
return { x = u.x, y = u.y, w = u.w, h = u.h }
end)
u.cmd_handle = command.register(
function() return { x = u.x, y = u.y } end,
function(nx, ny) u.x = nx; u.y = ny end,
u.speed
)
end
engine.print(string.format("rts-prototype: %d units registered (selectable + movable)",
#units))
engine.print(string.format("input: %d actions bound", input.action_count()))
end
function update(ctx, dt)
if input.was_action_pressed("quit") then
engine.print("rts-prototype: ESC -> launcher")
engine.switch_module("lib-management.launcher")
return
end
camera.update(dt)
selection.update(dt)
command.update(dt)
-- CI active-demo: programmatic move auf Frame 5 (smoke-Coverage)
frames = frames + 1
if frames == CI_MOVE_FRAME and os.getenv("SPOREL_CI") == "1" then
local handles = {}
for _, u in ipairs(units) do handles[#handles + 1] = u.cmd_handle end
command.move_to(handles, 500, 500)
engine.print("rts-prototype: CI move dispatched (8 units -> 500,500)")
end
-- Position trace für smoke-Assert (frame 30 — units sollten sich bewegt haben)
if frames == 30 and os.getenv("SPOREL_CI") == "1" then
engine.print(string.format("rts-prototype: frame 30 unit[1] pos=(%d,%d)",
math.floor(units[1].x), math.floor(units[1].y)))
end
-- CI self-exit
if os.getenv("SPOREL_CI") == "1" and frames >= TARGET_FRAMES then
engine.exit(0)
end
end
function render(ctx)
engine.render.clear_color(engine.render.rgb(20, 20, 30))
camera.begin()
r_lib.draw_map()
for _, u in ipairs(units) do
engine.render.draw_rect(u.x, u.y, u.w, u.h,
engine.render.rgb(u.color[1], u.color[2], u.color[3]))
end
selection.render_highlights()
camera.finish()
selection.render_drag_box()
end
function cleanup(ctx)
engine.print(string.format("rts-prototype: cleanup (total_frames=%d)", frames))
end
-- DEPRECATED-MVP: pathfinding (A*/nav-mesh) — units walk through walls (P.4)
-- DEPRECATED-MVP: faction-system (color-clusters are cosmetic only, no API)
-- DEPRECATED-MVP: win/lose-conditions (sandbox demo)
-- DEPRECATED-MVP: resource-nodes / harvest (P.4)
-- DEPRECATED-MVP: enemy-AI (P.4+)
-- DEPRECATED-MVP: save/load (no persistent state)
-- DEPRECATED-MVP: HUD/UI overlay (selection-count, unit-info)
-- DEPRECATED-MVP: mini-map (RTS polish)
-- DEPRECATED-MVP: bounds-clamp camera (currently free-roam)
-- DEPRECATED-MVP: attack-command (with faction-system, P.4)
-- DEPRECATED-MVP: stop-command (with attack, P.4)