|
Some checks failed
CI / verify (push) Has been cancelled
The forgejo-runner-user container (data.forgejo.org/forgejo/runner:13.0) is Alpine 3.23 and ships no Python at all. setup-python@v5 was failing because pyenv needs an existing Python interpreter to bootstrap from. Add an explicit apk/apt step that installs python3 + pip before setup-python@v5 runs, so the Python install path actually works. Closes fixit/t_6db7a5a7. |
||
|---|---|---|
| .forgejo/workflows | ||
| .gsd/gates | ||
| docs | ||
| godot | ||
| tools | ||
| web | ||
| .gitignore | ||
| AGENTS.md | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
CYBERTACTICS
Top-down XCOM-like tactics prototype set in a human cyberpunk future. Semi-realtime: you queue orders and playbooks, the mission executes in accelerated time, and focus-snap events (contact, wounded operatives, order failures) pause the clock so you can adjust the plan — strategic-level control with faster missions than classic turn-by-turn.
Quickstart
Every verification step has one Make target. From a fresh clone:
make test # run every CI-verified test (smoke x2 + saveload + teams + research + strategic + xp)
make lint # run the Godot lint gate
make ci # make test + make lint + browser-level Playwright smoke + Godot headless import check
make run-web # serve web/ on http://localhost:8000 (open in a browser)
make godot # open the Godot editor
make test prints PASS / FAIL per script and exits non-zero on the first failure,
so it is safe to wire into CI or a kilo-gsd Verify phase.
Repository layout
web/ Playable HTML5/JS prototype (no build step, no dependencies)
godot/ Godot 4 project — one codebase, exports to Linux, Windows, Android, Web
tools/ smoke.js — headless mission simulator that runs the real game scripts
Run the web prototype now
cd web && python3 -m http.server 8000
# open http://localhost:8000
The title screen offers a 3-mission campaign (Sector 7 Underworks, Rooftop Relay, The Foundry), a squad selection of 4 from 6 operatives, and persistent best times. Wins unlock the next mission (localStorage).
Headless verification
Every invocation below matches a run: line in .forgejo/workflows/ci.yml.
node tools/smoke.js 0 # headless mission simulator: scripted plan on map 0
# asserts pathing, combat, events, support, win/lose
node tools/smoke.js 1 # map 1 scripted plan wins: RELAY CORE BREACHED
node tools/smoke.js 2 # map 2 is the hard finale; the scripted rush loses by design
node tools/saveload.js # mid-mission save/load roundtrip
node tools/teams.js # strike teams: assignment, broadcast, scoping
node tools/research.js # research tree + shard economy
node tools/strategic.js # smoke / phalanx / salvage / intel
node tools/xp.js # progression: awards XP, buys upgrades, redeploys;
# asserts the mods land on the unit
python3 tools/playwright_smoke.py
# browser-level UI smoke — drives the title
# screen + in-mission surface in headless
# Chromium (DOM event wiring + canvas
# rendering that the headless tools can't).
# Requires: pip install playwright
bash tools/godot_import_check.sh
# headless Godot import sanity check:
# `godot --headless --import --quit` plus a
# SCRIPT/Parse/ERROR pattern scan on the
# output. Gates GDScript regressions the
# same way the node tools gate JS ones.
# Requires: godot 4.x on PATH
Mid-mission save/load (web)
The game auto-saves at every Command Phase and offers SAVE & TITLE in the top
bar. The title screen shows CONTINUE (mission, elapsed time) while a save
exists; it is cleared when the mission concludes. tools/saveload.js proves the
roundtrip: unit positions/HP/queues are restored byte-identical and the loaded
mission keeps running.
Progression — XP and between-battle upgrades
Operatives earn XP for kills (by hostile type), terminal hacks (+15), core breaches (+50), salvage (+5), and surviving the mission (+10, +30 more on a win). Each rank-up (Streetrunner → Operative → Specialist → Ghost → Legend, at 0/60/150/280/450 XP) grants a promotion point, spent in the BARRACKS on the title screen or the after-action report:
| Upgrade | Max | Effect |
|---|---|---|
| Vitality | 5 | +15 max HP |
| Reflex | 3 | +0.15 speed |
| Awareness | 2 | +1 sight |
| Tactics | 1 | +1 AP |
| Overcharge | 3 | +10% weapon damage |
| Stabilizer | 2 | −8% refire time |
| Adrenal | 2 | −15% melee refire — CC classes only (CC Specialist, Breach Adept) |
| Longbarrel | 2 | +2 weapon range — sniper classes only (Marksman, DM) |
| Meshweave | 3 | −10% damage taken — vanguard classes only (Pointman, Rifleman) |
The roster persists (localStorage ct_roster); upgrades apply when an operative is
deployed. Verified by node tools/xp.js. The Godot build mirrors this with the same
rank thresholds and upgrade effects in main.gd (user://cybertactics.cfg roster).
Strategic view and research
A STRATEGIC VIEW screen (title button and after-action report) shows the sector
map — campaign nodes with lock/clear/best-time status plus a repeatable Supply
Cache shard raid — beside the research tree. Research is funded by data
shards: +3 per mission win, +1 per loss, +1 per terminal hack, +1 (+bonus) per core
breach. Eight nodes with prerequisites (Wide-Spectrum Optics, Caseless Ballistics,
Trauma Nanites, Ghost Recon Drones, Data Miners, Orbit Logistics, Neural Uplinks, War
Protocol) grant global effects — stat bumps, extra support uses per mission, longer
reveals, XP multipliers — applied to every deployed operative. Shards, research, and
selection persist via localStorage. Verified by node tools/research.js. The Godot
build mirrors the economy and tree (cfg-persisted), with the research panel on the
title screen.
Capability research, salvage, and the sector graph
Two research nodes unlock capabilities rather than stats: Ghost Cell Dispersal grants the SMOKE DROP support ability (concealment cloud that genuinely blocks line of sight for 12s — engine-level, works for both sides) and Aegis Doctrine unlocks the PHALANX playbook (tight defensive ring). Locked playbooks show in the playbook panel with their research requirement.
A second strategic resource, salvage (crates +1, destroyed barrels +1), feeds the REQUISITIONS column in the strategic view: Field Surgery (clear all infirmary wounds, 3⚙), Black-Market Conversion (2⚙ → 1 shard), Mission Intel (next mission starts with hostiles revealed for 15s, 2⚙).
The strategic view renders the campaign as a node graph — FOB, campaign chain, and the repeatable shard raid connected by SVG links that light up as you clear missions.
Strike teams
The roster splits into ALPHA and BRAVO strike teams, assigned on the title screen (α/β tags next to each operative — persisted). In mission:
- F1 / F2 / F3 select ALPHA / BRAVO / ALL UNITS. Team orders: RMB broadcasts to every member — moves use formation offsets around the click, attacks become focus fire, and interacts put the nearest operative on point while the rest take covering positions.
- Stances and playbooks scope to the selected team — set BRAVO to defensive overwatch while ALPHA breaches.
- The squad panel groups members under team headers (click to select the team); the queue panel shows per-member stacks with a team-wide clear. Team-colored rings on the map show who's in the current selection; unit tags carry α/β prefixes.
- Downed members are automatically excluded from team orders.
Verified by node tools/teams.js. The Godot build carries team assignment at spawn,
team_move() formation broadcast, F1–F3 selection state, and title-screen assignment
dropdowns.
Wounds and the infirmary
Operatives who go down in a mission are sent to the infirmary: they miss exactly
the next mission, then return automatically. The title screen marks them INFIRMARY
(unselectable); you can deploy 2–4 operatives to cover absences. The after-action
report flags who was wounded. The Godot build mirrors this (wounded_until in the
roster, meta/missions counter in the cfg).
Audio
Synthesized placeholder SFX via WebAudio (web/js/audio.js) — shots, melee,
explosions, snap-focus alerts, hacks, pickups, door breaches, win/lose stingers.
No audio files; it unlocks on the first click and never blocks gameplay.
Controls
| Input | Action |
|---|---|
| LMB | Select squad unit / inspect a visible hostile |
| RMB (ground) | Queue MOVE to tile |
| RMB (enemy) | Queue ATTACK |
| RMB (object) | Queue INTERACT (crates, terminals, doors, core, pods) |
| SPACE | Pause / resume |
| 1 / 2 | Execution speed 1x / 2x |
| TAB | Cycle squad members |
| F1 / F2 / F3 | Select strike team ALPHA / BRAVO / ALL UNITS |
| DEL | Clear selected unit's order queue |
Every 9 seconds of execution a Command Phase pauses the game and refreshes AP; orders are editable whenever time is paused. Units can only start as many orders per execution window as they have AP.
Systems
- Order queue: move / attack / interact per unit, A* pathing, automatic repositioning to firing positions, door breaching en route.
- Focus-snap events: hostile contact (either direction), squad wounded below 40%, unit down, order failure — time pauses and the camera rings the event.
- Playbooks: Breach & Clear, Overwatch Line, Pincer, Hunt, Fallback — one-click stance + formation orders against your last right-clicked point.
- Stances / unit AI prefs: Aggressive (engage on sight, close to optimal range), Defensive (hold, fire in range), Recon (halt near contact), Hold Fire.
- Interactable scenery: loot crates (med-gel / weapon overclock), hackable terminals (reveal hostiles 10s, then 20s lockout), breachable blast doors, chain-exploding fuel barrels, supply pods, and the mission core (hack to win).
- Weapons: Mono-Edge Blade (CC), PDW-9 (sidearm), AR-77 Pulse (long gun), Longeye Railgun (sniper), Arc Baton (fast CC), DMR-13 (marksman). Range falloff, optimal-range bonus, moving-target penalty.
- Hostiles: Razor Drones, Chromegangers, Kill-Switch Heavies, Wisp Scouts (fast flankers), Sentry Guns (immobile emplacements that hold fire until you enter their arc). Hostiles patrol, hunt last-known positions after contact, and never path-block-stall.
- Support abilities: airstrike (3 uses, 40s cooldown, telegraphed, friendly fire) and supply drop (3 uses, 25s cooldown; pod grants +60 HP / +1 AP, needs an interact).
- Art: programmatic sprite atlas (
web/assets/sprites.png); renderer falls back to placeholder shapes when the atlas is unavailable (keeps the headless tests green).
Godot project (native builds)
Open godot/project.godot in Godot 4.2+. The GDScript mirrors the web prototype
system-for-system (TimeDirector = semi-realtime clock + command phases + snap-focus,
unit.gd = order queue + stances, playbook_library.gd, abilities.gd, grid_map.gd =
AStarGrid2D + Bresenham LOS, scenery.gd). Rendering is placeholder rectangles.
The Godot side now mirrors the web flow: maps_data.gd holds the same 3-mission
campaign table (ported from web/js/maps.js), main.gd builds the title screen
(squad select of 4 from 6, mission list with locks and best times, user://
ConfigFile persistence), TacGrid.build_from_def() constructs each mission layout,
and unit.gd has the hostile patrol/hunt director. Verified on import with
Godot 4.7.2 headless (--import compiles all scripts clean; a 120-frame
headless run of the main scene raises no runtime errors).
Export
- Editor → Manage Export Templates → Download (matching your version).
- Project → Export → Add Preset, once per target:
- Linux: preset "Linux" →
cybertactics.x86_64 - Windows: preset "Windows Desktop" →
cybertactics.exe - Android: install Android SDK + JDK (Editor Settings), set a unique package name, export APK or AAB. Touch input is enabled.
- Web: the standalone
web/prototype is the web build for now.
- Linux: preset "Linux" →
Deferred (by design)
Advanced graphics, larger tactical maps, save/load mid-mission, audio, and the
Godot-side campaign structure. Tuning knobs live in web/js/data.js (weapons,
operatives, hostiles) and web/js/maps.js (missions).
Development
Forgejo CI (.forgejo/workflows/ci.yml) runs every headless verification tool
on every push: the seven node tools (smoke.js maps 0–1 + saveload.js,
teams.js, research.js, strategic.js, xp.js), the browser-level
playwright_smoke.py, and godot_import_check.sh.
Balance notes
Mission 1 trades evenly with cover + airstrike; mission 2 rewards playbook use; mission 3 (Foundry) punishes rushes — bait heavies through barrels, silence sentries with the railgun, and call the strike on clumped patrols.
