Skip to content

Latest commit

 

History

1,184 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⚔️ COMMANDER IN CHIEF

  ___ ___  __  __ __  __   _   _  _ ___  ___ ___
 / __/ _ \|  \/  |  \/  | /_\ | \| |   \| __| _ \
| (_| (_) | |\/| | |\/| |/ _ \| .` | |) | _||   /
 \___\___/|_|  |_|_|  |_/_/ \_\_|\_|___/|___|_|_\
       ___ _  _    ___ _  _ ___ ___ ___
      |_ _| \| |  / __| || |_ _| __| __|
       | || .` | | (__| __ || || _|| _|
      |___|_|\_|  \___|_||_|___|___|_|
Commander In Chief — key art

Godot 4.7.2 GDScript Tests CI Determinism Milestone Assets Release

A modern remake of the 1986 vertical run-and-gun (Ikari Warriors, SNK) — twin-stick chaos, grenades-vs-armor, one-hit deaths, and the War Chest 💰: a shared coin economy where every kill mints and every revive spends.

🎬 Gameplay demo

Sector 1/6 Staging Ground — opening firefight, rounds bouncing off armor Sector 3/6 Bridge Gunship — boss strafing run under a sniper lock
Sector 5/6 Crashed Convoy — ten-kill streak, elite winding up Sector 6/6 The Foundry Core — minefield telegraphs on the run to the Colossus

☝️ Four sectors, four real runs. Clockwise: 1/6 Staging Ground — the opening push, rounds bouncing off armor · 3/6 Bridge Gunship — a boss strafing run while a sniper paints you · 5/6 Crashed Convoy — a ×10 streak with an elite winding up · 6/6 The Foundry Core — the minefield on the way to the Colossus.
Captured by tools/gif_capture.gd straight off the scripted bot playing the real game (start_arcade() jumps to each zone), so every number on those HUDs was earned — no posed sim states, no mockups, no concept art.

Stills: boarding a tank · the Colossus last stand · the Endless War shop.

⚠️ Status: 1.2 — gold. Campaign runs studio splash → Foundry Colossus 🏭, Endless War is deep, all side modes ship (Boss Rush · Arcade · Chapter Select · Daily Run), and the feel stack is real. Art is owned procedural art (tools/gen_*.py) + bespoke generated boss/vehicle/desert pieces over a Kenney-CC0 FX base — no longer greybox, and no longer third-party-derived (every encumbered sprite was replaced; see ASSETS.md). CI is live: .github/workflows/ci.yml runs a static lint job (tools/lint_sim.gd determinism gate + tools/lint_assets.gd + python3 tools/i18n_check.py string extraction gate), then import + boot-smoke + the full golden-checksum suite (the runner prints the exact method/assertion pair; the pinned figure lives in 🧪 Headless test suite) across Linux-x86_64 · macOS-arm64 · Windows-x86_64, failing on any SCRIPT ERROR or a missing PASS line — plus packaged-export smoke tests on Linux and Windows, an advisory (continue-on-error) perf job, and a nightly 3-hour soak run. The engine version is pinned in tools/versions.lock, the one source CI reads, so the build can't drift from what the determinism goldens were recorded against. docs/PLAN.md is the aspirational P0–P7 master plan — the sim code is the source of truth for what exists.


📥 Download & Play

⬇️ Latest release — v1.2.0 — no build step, just unzip and run. 🎮

🖥️ Platform 📦 File 📏 📝
🍎 macOS — Apple Silicon CommanderInChief-macos-arm64.zip 41 MB arm64-only, recommended for M-series
🍏 macOS — universal CommanderInChief-macos.zip 70 MB Apple Silicon and Intel
🐧 Linux CommanderInChief-linux.zip 39 MB x86_64 — keep the .pck beside the binary
🪟 Windows CommanderInChief-windows.zip 48 MB x86_64 — keep the .pck beside the .exe

🔐 Both macOS builds are ad-hoc signed, not notarized — first launch needs right-click → Open. Godot ships no arm64-only macOS template, so the native build is the universal one thinned with lipo and re-signed (see the note in export_presets.cfg).

🏷️ Version history
gitGraph
   commit id: "v0.1.0 · P0 greybox"
   commit id: "deterministic core"
   commit id: "v0.2.0 · P3 playable"
   commit id: "art swap"
   commit id: "v0.2.5 · rebrand"
   commit id: "history purge"
   commit id: "v0.3.0 · public" tag: "v0.3.0"
   commit id: "v1.0.0 · launch" tag: "v1.0.0"
   commit id: "v1.1.0 · polish" tag: "v1.1.0"
   commit id: "v1.2.0 · gold" tag: "v1.2.0"
Loading
🏷️ 📅 🎯 What it marks
v0.1.0 2026-07-08 P0 — first commit, already carrying the deterministic core, 16.16 fixed-point and 3-OS CI
v0.2.0 2026-07-16 P3 declared — the README stops describing a prototype and starts describing the game
v0.2.5 2026-07-21 Commander In Chief — the rebrand + animated boot splash
v0.3.0 2026-07-27 First public release — clean history, binaries for three platforms
v1.0.0 2026-08-24 1.0 launch — safe-area docs + HUD/menu 1.0 sweep, ultrawide pillarbox verified
v1.1.0 2026-08-24 1.1 polish — HUD Container, GL gate, roll gate, filter NEAREST, safe-area
v1.2.0 2026-08-24 1.2 gold — final AAA sweep, all 5 tells closed, store-ready

🗺️ Table of Contents

🧭
🏗️ Architecture 🎮 Controls 🕹️ Modes
👹 The Roster 🎁 Drops & the Wheel 🧪 Testing
📻 The Radio 🌐 Netcode 🚀 Quick Start
🎨 Art 📥 Download 🗺️ Roadmap
🤝 Contributing 📜 License

🏗️ Architecture — the sim/view split

Everything the game is lives in a deterministic, render-free core. The scene tree is just a camera pointed at it. 📽️

flowchart LR
    subgraph VIEW ["🖼️ src/main.gd + src/view/ — floats allowed"]
        IN["🎮 Input<br/>(floats)"] --> Q["_quantize_axis<br/>clamp to ±256"]
        EV["✨ events → shake, hit-stop,<br/>particles, SFX, banners"]
        HUD["📊 hud.gd · menu.gd<br/>art.gd · sfx.gd"]
    end
    subgraph SIM ["🔒 src/sim/ — deterministic core"]
        Q --> STEP["SimWorld.step()<br/>60 Hz fixed order"]
        STEP --> F["Fixed 16.16 int math<br/>SimRng xoshiro128**<br/>NO floats · NO Time.* · NO scene tree"]
        STEP --> CS["checksum()<br/>FNV-1a over full state"]
    end
    STEP -.->|"events (checksum-EXCLUDED)"| EV
    CS ==>|"golden values"| T["🧪 test_determinism.gd"]
Loading
  • src/sim/SimWorld.step(inputs) advances one tick: players → tanks → projectiles → enemies → bunkers → spawner/boss/colossus/gates/camera/observer. State is plain Array[Dictionary]. Bit-identical across x86_64 and Apple Silicon.
  • src/main.gd + src/view/ — the only view. Owns all floats, reads sim state, draws, and turns per-tick events into feel. The sim never knows juice exists.
  • SimWorld.events — rebuilt every tick, excluded from the checksum: view and audio triggers never break determinism goldens. 🔓
🔐 The golden-checksum discipline (read before touching src/sim/)

Any change to sim logic, hashed state, or step order shifts the committed GOLDEN checksums and fails test_determinism.gdby design. When intentional: set GOLDEN = [], run, paste the printed values back, note why in the comment. test_checksum_coverage.gd is the tripwire — every sim entity field must be classified hashed-or-excluded. If the idempotency or cross-run checks fail instead, that's real non-determinism: fix it, never re-record over it. 🚨


🎮 Controls

Action ⌨️ Keyboard (P1) 🎮 Gamepad
Move WASD Left stick
Aim (decoupled) Mouse or arrow keys Right stick
Fire / tank cannon 🔫 — always on, there is no fire button: aim is the weapon
Grenade 💣 — hold through the apex = AIRBURST 🎈 E (Shift = alt) / RMB L1
Dodge roll (i-frames) 🤸 C B
Interact 🚜 (board/exit tank · gunner seat on an occupied hull · salvage hulks · plant claymores) F X
Revive (spend War Chest) 💸 — dedicated input; works for yourself or a downed partner Space Y
Supply wheel (hold, flick, release) 🎡 Q BACK
Toggle local 2P 👥 F2
Toggle Endless War ♾️ F3
Restart 🔁 R

🕹️ Modes

Mode What it is
🏔️ Campaign 6 gated sectors (SimWorld.FINAL_GATE_INDEX) → the Foundry Colossus finale (Last Stand rule: no revives; kill converts your War Chest to score. VICTOLY!). Gates 2 & 4 fork: < CACHE (free crate ringed by mines) vs BOUNTY > (two elites, one marked) — walking a side IS the choice 🛣️
♾️ Endless War Escalating waves, between-wave shop intermissions, minibosses that fly in over 7s and escalate by tier (tighter spray, extra mortars by w15), contested parachute supply drops rushers try to steal 🪂, and the wave-7+ Broadcast Tower debut
🎯 Arcade / Chapter Select Jump straight to any of the 6 chapters (jump_to_chapter) — the same authored world, entered mid-way by priming every streaming cursor forward. Every streamed pick is a pure function of absolute world position, so chapter 4 looks like chapter 4 always does 🗺️
🥊 Boss Rush BOSS_RUSH_COUNT = 3 gunships back-to-back, then the Colossus caps the run. Non-linear HP escalation (BOSS_RUSH_HP_STEPS = [0, 14, 32]) — the ladder steepens, it does not just scale 📈
👥 Local 2P co-op Shared War Chest, revive tether, per-device input glyphs. The field keeps advancing on a downed player — being down is a state, not a pause ⏱️
📅 Daily Run Seed-of-the-day challenge run (Hall entries wear a *DAILY tag)
🎞️ Watch Last Run Every run records inputs → user://last_run.replay, replayable from the menu
💀 NG+ HARD / 🛟 ASSIST (2-hit) Difficulty toggles, both checksum-honest
flowchart LR
    T([🎮 TITLE]) --> C[🏔️ Campaign]
    T --> M{{🕹️ MODES}}
    T --> D[📅 Daily Run]
    T --> W[🎞️ Watch Last Run]
    M --> A[🎯 Arcade]
    M --> B[🥊 Boss Rush]
    M --> E[♾️ Endless War]
    A --> CS[🗺️ Chapter Select 1-6]
    C --> F([🏭 Foundry Colossus])
    B --> F
    CS --> F
    E --> WV[🌊 Waves + 🛒 Shop]
Loading

All four sim modes — campaign · arcade · endless · boss_rush — share one SimWorld, one checksum, and one golden-checksum suite. 🔒

Persistent carrots 🥕: top-40 Hall of Fame, career totals, and best score/wave/distance survive across runs (user://ikari_best.cfg, atomic tmp+bak writes).


👹 The Roster

Every threat telegraphs before it resolves — that's the readable chaos pillar. 📖

Threat The deal The counter
🏃 Rushers & red elites The 1986 grammar — touch = death, elites drop capsules Shoot, dodge, kite
💥 Grenadier Telegraphed lob, edge-detect markers Move off your ground
🔭 Sniper Laser paint line before the shot Break the line
🌿 Ghillie sniper Cloaked ambush, bullet-immune dug in Flush it out
🛡️ Riot shield Front-arc bullet block Flank, grenade, or Rend through it
🧨 Sapper Lays a live mine trail Mind your feet
🏅 Courier Flees with a fat bounty (4× elite) Cut it off
🐸 Frogman Submerged in rivers, grenades-only until it surfaces to lunge Kill it on the surface
🛸 Drone Hovering strafer, spawns marked (3× trophy bounty + crown) Watch the tether, claim the crown
🔩 MG Nest 3-hit armor, alarm lock-on, teaching card Break its line or flank — it cracks under fire
📡 Broadcast Tower Rooted rally mast (endless w7+): every ground trooper in its 140px aura runs +25%, and it holds the wave open Kill the mast, break the rally — 5 hits or one grenade
🚙 Technical The fastest thing on the field. Cruises, revs 30t (dashed line = still aiming), then locks a charge lane (solid line = committed) — 3 px/t, can't steer Sidestep the lane; one shot drops it; smoke denies the lock; water kills the charge 🌊
🛢️ Explosive barrels Bullets & enemy contact detonate; 8-tick chain fuse goes white-hot Feed the chain — or flee it
🗼 Mortar Observer Shells you for stalling Kill him or push forward
🚁 Bridge Gunship Gate boss every 3rd gate — bullets chip, grenades chunk. Chin-turret charge glow telegraphs each spray; mortars lead your walk while the hull keeps drifting When it dies, see below 👇
🪂 Downed Pilot Ejects from any dead gunship, staggers for the enemy line. TOUCH to rescue (+100¢). Shooting him pays NOTHING. Tank treads grab him, the airstrike spares him, roll-touch works Reach him before the top edge — the label turns red ESCAPING! at the cliff
🏭 Foundry Colossus The world ends here: 3 phases, pure armor, inverts the scroll Grenades only. Last Stand. 🫡

🎁 Drops & the Supply Wheel

Rare capsules (elite drops, weighted table): 🔵 PIERCE · 🟠 SPREAD · 🩷 TRIPLE (permanent 3-fan — stacks with Spread into a 5-fan!) · 🔴 REND (shears riot shields) · 🟢 CLAYMORE · ⚪ SMOKE (breaks locks) · 🟡 FLASHBANG (field-wide stun, fair re-arm).

The wheel 🎡 went 8-way (hold Q / BACK, flick, release — coin prices from the shared War Chest):

Direction Buy Effect
⬅️ AMMO +30 MG belt top-up
⬆️ GRENADES +4 The armor-opener
➡️ FLAK VEST Absorbs exactly one hit 🦺 — campaign price creeps 60→75→90→105→120¢ per buy
⬇️ AIRSTRIKE Called in, telegraphed, spares the submerged and the pilot ✈️
↙️ SANDBAGS 40¢ Plants a 36×10 cover segment along your aim 🧱 — blocks bullets & rusher pathing both ways, dies to one grenade or tank treads, field cap 6
↗️ SUPPLY CALL ★1 Spends a Commendation (never coins) for a free useful supply 🎖️

🎖️ Commendations & field-craft

  • Commendation tokens ★ — minted by play, never bought: the 20-kill surge and every flawless gate pay one (cap 2). A death burns one. Spend on the wheel's ↗️ socket — the score→power bridge the panel voted 9/9 for.
  • Tank Crew 👥🚜 — P2 can INTERACT an occupied tank: independent twin-stick coax gunner seat on his own ammo, +25% fuel tax, driver exit promotes the gunner, the bail window covers the whole crew.
  • Tank Hulks 🛢️ — dead hulls smolder as two-way bullet cover for ~17s; INTERACT salvages +2 grenades and strips the cover. Keep the wall or take the ammo.
  • Airburst 💣🎈 — keep holding the grenade button since the throw and the charge pops at the arc's apex: tap for range, hold for the pop.

📻 The Radio

A single radio-filtered Commander (with a Spotter on the second channel and one panicked Pilot) barks the big beats — 14 lines, ≤8 words each, priority-laddered so the radio never talks over itself and never wears out on the hundredth replay:

flowchart LR
    D["💀 defeat / Last Stand<br/>(interrupts everything)"] --> N["🚫 denials & pilot calls"]
    N --> W["⚠️ tactical warnings"]
    W --> F["🎖️ praise & flavor"]
Loading
Voice Owns Filter
🎖️ Radio Commander "War Chest empty. You're on your own." · "Overlord out." · Last Stand · VICTOLY! 1.1 kHz bandpass + overdrive — tactical radio
🎯 Spotter Pilot down · shop locked · "Clip dry! Bash 'em!" Lighter, crisp
🪂 Downed Pilot "Don't shoot! I'm worth a hundred coins!" (0.4s behind the callout) Dry close-mic — he's HERE

Concussion muffles the radio too — by intent. A stunned soldier hears underwater radio. 🌊


🧪 Headless test suite

godot --headless --path . --import           # 📦 once after cloning, and after ANY new class_name script
tools/run_tests.sh                           # 🧪 full suite, private user://
SUITE=mechanics tools/run_tests.sh           # 🔍 one suite (substring of the filename)
SUITE=perf tools/run_tests.sh                # ⏱️ opt-in timing suite (excluded from the default run)
tools/run_tests.sh -s res://tools/lint_sim.gd   # 🧹 determinism lint — run it yourself after sim edits

🏠 Use tools/run_tests.sh, not raw godot, whenever anything else might be running. user:// is keyed on the project name, not the checkout path, so every worktree and every concurrent session shares one ikari_best.cfg and one godot.log. The wrapper hands the run a private HOME so siblings can't clobber each other — and it owns the shutdown-leak gate, which the in-engine gate structurally cannot see (Godot prints leak diagnostics on stdout, after its file logger is already gone). 🕳️

🔧 The raw invocation still works — here's when it doesn't
godot --headless --path . -s res://tests/run_tests.gd   # ⚠️ no leak gate, shared user://

The wrapper is a wrapper, not a runner change, so this is still a valid way to run the suite when you are definitely alone on the machine. What you give up: the shutdown-leak gate, and the private user://. The save/settings tests stash and restore your real config, so two concurrent raw runs corrupt each other's fixtures — and a sibling Godot rotating the log away makes the engine-error gate fail a perfectly clean diff with "no log carried this run's marker". 🌀

1,166 test methods / 37,400+ assertions — fixed-point math, seeded RNG streams, the 1986 mechanic grammar, the War Chest economy, tank/observer/gates/water/gunship/colossus, every archetype's behavior contract (nest armor, technical charge lock, pilot rescue/grace/forfeit), Endless War waves & shop, lockstep loopback, replay integrity, checksum coverage classification, and the campaign+endless golden determinism runs. (The PASS — line prints the exact pair.)

📐 Why the method count is exact and the assertion count is a floor. The method count is identical on every platform, so it can be pinned — and it is pinned, by test_assets.gd::test_readme_test_method_count_matches_the_suite: that test recounts the suite and fails if this line drifts, so adding a test without updating this number turns the build red. Assertion totals genuinely vary by platform and environment — on commit 4c7ae78 the same code measured 37,436 on Linux and macOS but 37,451 on Windows — so no single exact assertion number is correct everywhere. Pinning one wouldn't just go stale, it would be unfalsifiable; a floor is the honest form. 🎯

test_perf.gd asserts wall-clock microseconds, so it sits in run_tests.gd's OPT_IN_SUITES: the default full run skips it (that's why the method count above is 1000, not 1002), and CI runs it in an advisory continue-on-error job. A shared runner is a noisy neighbour and a randomly-red gate stops being read.

🧷 Gotcha: the runner counts a method green even if a runtime error aborts it mid-way — watch for SCRIPT ERROR lines, and hold dict references across sim.step() (dead entities are swept from the arrays).


🌐 Netcode: deterministic lockstep — a design sketch, not shipped online play

⚠️ Read this before believing the diagram. src/net/lockstep.gd has zero production callers — nothing in src/main.gd or the menus constructs a LockstepSession, and no transport is wired to on_send. Online co-op is not playable. What exists is a ~260-line loop plus tests/test_lockstep.gd, whose FakeWire is a deterministic in-memory wire with latency jitter only: it never drops, duplicates, corrupts-by-network, disconnects, or times out a packet (the one corruption test flips a payload byte by hand to prove the checksum exchange notices). So the test proves the sim is deterministic enough for lockstep and that the loop's stall/catch-up/desync-flag logic is self-consistent — it proves nothing about behavior on a real lossy network. Treat the section below as the intended design. 🎯

src/net/lockstep.gd — transport-agnostic; only encoded inputs cross the wire. 📡

sequenceDiagram
    autonumber
    participant A as 🎮 Peer A (SimWorld)
    participant B as 🎮 Peer B (SimWorld)
    A->>B: SimInput @ tick T+delay (3 ticks ahead)
    B->>A: SimInput @ tick T+delay
    Note over A,B: each sim advances ONLY when both inputs for the next tick exist
    A-->>B: checksum every 60 ticks
    B-->>A: checksum every 60 ticks
    Note over A,B: mismatch ⇒ 🚨 desync flagged — exercised by a two-sim in-memory loopback
Loading

Steam Networking Messages is meant to plug into on_send / receive_remote_input without touching the loop — that transport has not been written, so the seam is untested against a real socket. Per docs/PLAN.md online 2P is a post-launch beat, not a launch promise; local 2P and Remote Play Together are the co-op that actually ships.


🚀 Quick Start (macOS / Apple Silicon)

  1. Grab Godot 4.7.2 (stable) macos.universal.zip from https://godotengine.org/download/archive/4.7.2-stable/ — native on M1–M4. 🍎
  2. Run it:
    /Applications/Godot.app/Contents/MacOS/Godot --path /path/to/commander-in-chief
    …or open the editor, Importproject.godot → ⌘R.
  3. Gatekeeper complains? Right-click → Open once, or xattr -dr com.apple.quarantine Godot.app. 🔓

Determinism across architectures is by construction (pure 64-bit int math) and asserted by the golden suite — run it locally on your arch; the values must match the committed goldens recorded on Apple Silicon. 🤝


📁 Layout

project.godot        Godot 4.7 project (640×360 virtual res, 60 Hz physics)
src/sim/             🔒 Deterministic core — int-only, no engine RNG, no Time.*
src/main.gd|.tscn    🖼️ The view + input quantization boundary
src/view/            art.gd (bake registry/tint/glyphs) · hud.gd · menu.gd · sfx.gd
src/net/             replay.gd (run recorder, live) · lockstep.gd (design sketch, no callers)
tests/               Headless runner + 38 suites (SUITE=<name> filter, golden checksums)
tools/               gen_*.py (the live sprite generators) · lint_sim.gd/lint_assets.gd · i18n_check.py (CI gates)
                     screenshots.gd · smoke.gd · validate_replay.gd
docs/PLAN.md         📜 The aspirational P0–P7 master plan

🖥️ Viewport & Safe Area

Design viewport is 640x360 (project.godot:31 window/size/viewport_width|height = 640×360, window/stretch/mode="viewport" scale_mode="integer" aspect="keep"). The engine keeps integer letterbox — the playfield stays pixel-perfect; only chrome reflows.

Viewport How it presents Bars
16:9 — 640x360, 1280×720, 1920×1080 Native — full window, no bars None
21:9 — 3440x1440 Centered 640x360 viewport at integer scale, pillarboxed Black bars left/right
32:9 — 5120x1440 Same — wider pillarbox, same centered playfield Black bars left/right
Portrait / ultrawide mismatch Letterboxed top/bottom Black bars top/bottom

Safe-area rule (responsive-ui): HUD and menu chrome never sit on black bars or under a notch. Both resolve the safe-area as get_visible_rect()DisplayServer.get_display_safe_area() and apply it as MarginContainer safe insets:

  • src/view/hud.gd:_hud_safe_band() / _resolve_hud_safe_band()_safe_rect()_update_safe_margins() — band the HUD may occupy, then HudOuter margin_left/right/top/bottom = safe inset + PLATE_ORIGIN (2px), so band_rows() and the plate stay centered in visible pixels.
  • src/main.gd:_safe_viewport_rect() / _refresh_safe_rect() / _safe_band_rect() — centered viewport rect (pillarbox excluded) and the rebake on size_changed, delegating to HudIcons._safe_rect() so the HUD's pip resolver stays the single source for notch + pillarbox. _hud_icons._update_safe_margins() is called on every resize.
  • src/view/menu.gdPRESET_FULL_RECT + size_changed → _on_menu_viewport_changed → queue_redraw(), so the menu stays centered in visible pixels on ultrawide (21:9, 32:9).

get_visible_rect() excludes the pillarbox bars; get_display_safe_area() insets for the OS notch / Dynamic Island (zero rect = no notch, no-op). MarginContainer insets keep every band, label plate, and world-space claim_label_slot() rect off the bars and off the notch — 16:9 is byte-identical (band 0..640, 2px inset), ultrawide and notched devices narrow the band and the insets grow.


🎨 Art

Desert firefight Foundry Colossus boss
Desert firefight — one-hit twin-stick The Foundry Colossus 🏭 — campaign finale

The art is a hybrid pipeline, not one source:

Layer Source Where
Units · structures · props Owned procedural art (tools/gen_entities.py) assets/art/{decor,p2,mil2,cast2} + 7 top-level
Player + enemy soldier sprites, action poses, and projectile cards Bespoke generative-AI art (OpenAI built-in image generation; prompt/provenance record included) assets/troops/, assets/projectiles/
Menus · HUD · icons · FX cards Owned procedural art (tools/gen_ui_chrome.py · gen_ui_icons.py · gen_ui_glyphs.py · gen_fx_cards.py) assets/art/{ui,hud,icons,fx}
Desert flora Bespoke generative-AI art (nano-banana; pipeline + prompts recorded in assets/art/desert_assets_source.md) — the painterly cacti, scrub and tumbleweed assets/art/ (owned, gen-AI)
Bosses · player tank Bespoke generative-AI art, service unrecorded — the gunship/colossus/tank silhouettes. See ASSETS.md assets/art/ (owned, gen-AI, unverified)
Ground · projectiles · FX Kenney CC0 assets/cc0/
Radio VO (56 mp3) Commissioned TTS (ElevenLabs) — owned by the project assets/vo/
Combat shouts (118 mp3) Commissioned TTS (ElevenLabs, ar/fa lines) assets/audio/
Text PixelOperator8 — CC0, Jayvee Enaguas assets/fonts/

The desert→jungle look is a per-sprite olive tint + 1px readability outline applied in the view (src/view/art.gd), not baked in — so a new asset joins one palette family regardless of which source it came from.

Every asset is cleared, and the history is clean. All 266 PNGs under assets/ are owned procedural art (tools/gen_*.py), owned generative-AI pieces, or CC0, and the 174 voice mp3 were synthesized in-house and are owned by the project. The old third-party art was purged from every commit on 2026-07-27 — that was the last blocker, and clearing it is what let this repo go public. Full map: ASSETS.md. Generative-AI assets are sanctioned (the earlier no-AI policy was dropped — see CLAUDE.md); the gen-AI boss/vehicle/desert art and the synthesized VO already ship in-game. 📋 Full asset-licensing map + the open-source audit trail: ASSETS.md.

🖼️ Promo / key-art variants
Hero poster variant Comic tank poster variant

Marketing key-art variants (generative-AI). Full 4K titled poster lives in the asset drop, not the repo.

Accessibility baked into the view 🧏: colorblind-safe palette routing (Art.safe), reduce-motion mode (steady lights instead of strobes/pulses), photosensitivity discipline (no kill-flash spam), SFX/music/rumble toggles, device-adaptive glyphs.


🗺️ Roadmap

Three states, and the wording is deliberately unflattering where it has to be: ✅ done · 🔨 in progress · ⬜ planned.

flowchart LR
    A[✅ Deterministic core] --> B[✅ Playable start→finish]
    B --> C[✅ Owned art + public release]
    C --> D[🔨 AAA polish pass]
    D --> E[⬜ Online netplay]
    D --> F[⬜ Steam release]
    E --> G[⬜ 1.0]
    F --> G
Loading
Milestone Where it actually stands
Deterministic core 16.16 fixed-point, seeded xoshiro128**, no floats/RNG/wall-clock/scene-tree in src/sim — enforced by tools/lint_sim.gd in CI, not by convention. Golden-checksum suite proves bit-identical x86_64 ⇄ arm64
Playable start→finish 6 gated sectors → Foundry Colossus, plus Arcade/Chapter Select, Endless War, Boss Rush, Daily Run, replay, local 2P
Owned art + audio Every sprite owned-procedural, owned generative-AI, or CC0; voice lines synthesized in-house. Provenance map in ASSETS.md
Public release Clean git history, MIT on the code, v0.3.0 binaries for macOS (arm64 + universal) · Linux · Windows
🔨 AAA polish pass A blind consumer reviewer plus a sim-reading behaviour lens grade the build each cycle and the findings get fixed. 1 of 16 cycles run. Known-open from cycle 1: boss health bar clips the bottom edge, rebind helper text bleeds its frame, sectors reuse one desert tileset despite distinct names
Online netplay src/net/lockstep.gd is ~260 lines with zero production callers and no transport. The determinism proof it depends on is real; the netplay is not. Don't read the file as a feature
Steam release SteamBridge is an offline-first facade — it no-ops without the SDK. No store page, no Playtest
Localization freeze 3 languages shipped (locale/strings.{es,fr,ja}.po), not the 12-language freeze the plan costs
Human-in-the-loop production No external playtests, no hired artist or art director, no external QA or compat matrix. Built by one owner plus agents — see the caveat at the top of docs/PLAN.md

📌 "P3" in this repo means playable start→finish with all modes in — not that docs/PLAN.md's P3 exit criteria were met. The plan is costed for a 4-person team that does not exist here. The sim code is the source of truth for what ships.


🤝 Contributing

Small, well-tested changes are very welcome. Two things this codebase cares about more than most:

🎯 Rule Why
🔒 Nothing non-deterministic in src/sim No floats, no randi(), no Time.*, no scene-tree access. tools/lint_sim.gd fails CI on any of them
📏 A sim change moves the golden checksums — by design Re-record deliberately: set GOLDEN = [], run, paste back, and write why with the arithmetic. Never re-record to turn a red green
🧪 New behaviour brings a check that fails first Watch it go red for the stated reason before the fix, or it is pinning the wrong thing
📐 Route through the arbiters main.band_rows() owns stacked HUD bands; main.claim_label_slot() owns world-space labels. Drawing directly bypasses their ratchets silently
godot --headless --path . --import        # once after cloning
tools/run_tests.sh                        # full suite under a private user://
tools/run_tests.sh -s res://tools/lint_sim.gd

Looking for something to fix? FINDINGS.md is the open defect inventory — every entry adversarially verified, each with a measured rate. Read its header first: 62 of 115 findings died on their own last time anyone re-checked. 🔎

CONTRIBUTING.md is the long-form guide — dev setup, the determinism commandments, golden re-recording, and the PR checklist. CLAUDE.md carries the hard-won gotcha ledger it points at — each entry once cost someone an hour. 🕳️ Found a security issue? SECURITY.md — please don't file those publicly. 🛡️


📜 License

MIT for the code — see LICENSE. It does not cover the bundled assets.

📄 Covers
LICENSE Source code — plain MIT, nothing else in the file
NOTICE.md What MIT does not grant, third-party components, and the likeness position on assets/vo/ + assets/ui/intro/keyart.png
ASSETS.md Per-directory provenance map for every shipped asset

🎭 If you redistribute this repo, read NOTICE.md first. The commander VO imitates an identifiable public figure and the boot-splash key art depicts themassets/vo/ and assets/ui/intro/keyart.png both. The project owns the recordings and the image; right of publicity does not belong to it, and that question travels with both. 🪪


⚔️ VICTOLY awaits. Feed the War Chest. Rescue the pilot. Sidestep the lane. ⚔️

Built at 60 Hz. Bit-identical everywhere. The sim never knows juice exists.

About

Deterministic twin-stick vertical run-and-gun in Godot 4.7 — a modern Ikari Warriors. Render-free 16.16 fixed-point sim, bit-identical across x86_64/arm64, golden-checksum tested.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages