-- ===================================================================== -- 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)