units[1].x/y are floats after movement; %d requires integers in Lua 5.4. Caused update hook error on frame 30, silencing the position trace. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
160 lines
5.8 KiB
Lua
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-sporel.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)
|