cybertactics
Find a file
manager b391dd0cee
Some checks failed
CI / verify (push) Has been cancelled
fix(ci): install python3 on Alpine runner before setup-python@v5
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.
2026-10-06 20:40:38 -07:00
.forgejo/workflows fix(ci): install python3 on Alpine runner before setup-python@v5 2026-10-06 20:40:38 -07:00
.gsd/gates cybertactics#4: add Godot headless import check to CI 2026-09-22 08:29:35 -07:00
docs cybertactics#14: add hero screenshot to README 2026-09-24 09:28:15 -07:00
godot cybertactics#19: register 11 portrait PNGs in PORTRAIT + SpriteAtlas.PORTRAIT_PATHS 2026-09-24 20:13:49 -07:00
tools merge origin/main: cybertactics#24 sidebar fix + ruff sort in playwright_smoke 2026-10-05 13:54:21 -07:00
web cybertactics#26: snapPause auto-selects the involved unit 2026-10-05 14:07:49 -07:00
.gitignore cybertactics#21: Playwright portrait assertions + sim-advanced flake guard 2026-09-24 20:37:10 -07:00
AGENTS.md cybertactics#7: AGENTS.md — kilo-gsd single-entrypoint contract 2026-09-23 05:39:54 -07:00
LICENSE cybertactics#5: add MIT LICENSE file 2026-09-22 08:32:56 -07:00
Makefile cybertactics#7: root Makefile + README Quickstart 2026-09-22 08:46:49 -07:00
README.md cybertactics#14: add hero screenshot to README 2026-09-24 09:28:15 -07:00

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

cybertactics in-mission

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

  1. Editor → Manage Export Templates → Download (matching your version).
  2. 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.

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.