尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证OVERDRIVE: an engine-less 3D arena shooter in C++17 and OpenGL 3.3, with fixed-timestep physics and Quake-style movement; also runs in the browser via WebAssembly + WebGL2
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗▶ Play it in your browser, no install needed.
A fast 3D arena shooter in the spirit of ULTRAKILL, written from scratch in C++17 with SDL2, OpenGL 3.3 and GLM: no engine. Grapple, dash, slide and slam through waves of enemies built from animated boxes, parry their shots back at them, and take on distinct bosses, each with its own trick to learn.
make test runs the physics, the AI, every level's data and simulated full runs, plus a boss simulator that fights the bosses' real AI with scripted players (CI on Linux, macOS and the web build)| Mode | What it is |
|---|---|
| ARENA | A page (like FAST's) of the whole of Act I, the whole of Act II once Act I is beaten, or any arena of either act on its own (always open: the upgrades a run would have by then, your best clear time kept). Act I: four themed arenas of three waves each (the Sunset Yard, the Foundry, the Spire, the Core and its boss the Warden), then the Sanctum, a duel under an eclipse with the Sovereign. Scored and ranked on the leaderboard. |
| ACT II | (On the ARENA page.) The whole act unlocks when the Sovereign falls in an Act I run. Down beneath the eclipse: the Drowned Nave (rising water), the Orrery (a sun whose flare sweeps the arena), the Descent (a cage riding down a shaft to the Penitent), the Reliquary (pieces of Act I drifting over a void) and the Maw (the Leviathan). Then the encore, !!!!!: the Absolute, who comes back as MORNINGSTAR. Keeps local best times; not ranked yet. |
| STORY | Three chapters: THE CRADLE, THE DEAD SPAN and THE LIVING WARD. Escape the factory and ash wasteland, then descend through ten spacious medical test chambers into incompatible dimensions. Chapter 3 adds the first skippable cutscene, four flesh chassis families, observation bridge choices, two secrets and THE SUCCESSOR, a fast humanoid boss with a robot face and original choir/organ score. Equipment and upgrades carry forward. |
| FAST | The Gauntlet: a time trial through seven rooms joined by boost tubes, a mini-boss in each room and the Pacemaker at the end, with splits against your best run, par ranks, a ghost of your best route and medals for every room. ROOM ATTACK runs any one room on its own for its medal. |
| ENDLESS | Generated waves in the Core until you die, tougher every wave, with an objective every 4th wave and the Warden every 10th. |
| BOSS | Any boss on its own, with a level and upgrades to match. Your best kill time for each is kept. |
Kills earn XP (more for stylish play) that buys weapon upgrades in the Armory. Every ranked run is scored (style, minus damage taken, plus a time or wave bonus, times the difficulty) and good runs go on a leaderboard shared by every player on the website.
Story starts unarmed. UNIT 01 holds the revolver, UNIT 02 the Lancer, and UNIT 06 the shotgun.
The grapple is recovered at the Inspection Line. Guns and found logs survive a checkpoint retry;
a new run resets discoveries. Enter on Chapter 1's completion card continues into Chapter 2
with your equipment. STORY also offers direct Chapter 2 and Chapter 3 starts with the appropriate equipment.
See the Chapter 1 implementation and playtest notes for the route, controls,
source files and developer starts. Chapter 2 notes cover the wasteland
route, Blender assets and verification. Launch Chapter 2 directly with ./shooter --story2, or the Living Ward with ./shooter --story3 (?story3 in the web build). Chapter 5, THE COLD STORE (an icy planet, RECALL, 24-K and THE RESTORER), starts with ./shooter --story5 (sections with --arena N, from 1); its design and build notes cover what is built. Chapter 3 build notes cover its controls, assets, tests and tuning limits. The current Story revision documents the writing, research and encounter direction. Settings → VIDEO → AMBIENT DETAIL reduces optional ash, grit, vents and moving decoration independently of resolution; required attack tells and objectives remain visible. Settings → VIDEO → LIGHTING: LOW is the plain shading (sun, sky, baked shadows), MEDIUM adds the baked glow light (neon, lava and lamps lighting what is round them, blocked by walls) and the sun's bounce off the ground, HIGH also gives every surface its relief (normal maps made from its texture). Settings → GAMEPLAY → ENEMY MARKERS selects LOW (small cleanup icons, one short direction label) or DETAILED (full names, distance and cover labels). Bosses do not receive tracking icons in either mode.
Combat Practice is available from F3 on the main menu or the Story page. Choose repeatable lessons for parries, finishers, grapples, shotgun jumps and more; F4 reopens the Field Guide during any run. Story now autosaves local checkpoint progress and offers Continue. The Armory offers two alternative mods per gun, with free swaps after buying the mod; M (controller X) previews its alternative. The Boss page also includes the Pacemaker, Sorter and Ballast. See the improvement notes for controls, save behaviour, relay isolation and the turbine winch, and Chapter 3 directions for the surreal-world brainstorm.
| Boss | Where | The trick |
|---|---|---|
| The Sorter | the Sorting Floor (Story, Chapter 1) | Evade its gantry rush, staggered presses and furnace purges; parry the white claw, aim for the open blue strip and survive progressive belt failures |
| Ballast | the Cut (Story, Chapter 2) | A pursuing clearance grader: jump its sweep and torn-road slabs, evade pivoting charges, parry its harpoon or shoot the cable, then hit the exposed rear turbine |
| The Successor | Final Assessment (Story, Chapter 3) | Evade committed incision/vector paths, cut Suture anchors, watch announced crossings and parry white thrusts. One health bar, three phases, original choir and organ score. |
| The Warden | the Core (Act I) | Cut the four conduits feeding it; hit its core when it vents; survive its meltdown |
| The Sovereign | the Sanctum (Act I) | A sword duellist: parry (F) his sweeps and cleave as they land to break his guard |
| The Penitent | the Descent (Act II) | Chained to the pit wall: break the chains, jump its low sweeps, slide under the high ones |
| The Leviathan | the Maw (Act II) | Dash out of its crash, then shoot its eye while it lies beached |
| The Absolute | !!!!! (the encore) | A boxer of white light. White strikes: parry. Gold: dodge or block. Red: get out. Each also has a sound cue, so you can fight him by ear. Key 5 draws the sword Oathbreaker here |
| MORNINGSTAR | inside the eclipse | The Absolute's second form: bigger, faster, and only his white strikes show on his body |
| The Pacemaker | the Tower (FAST) | The machine that set the par times. It duels you in the yard, races you up the Tower's balconies to the roof and overclocks there; parry its white cuts, jump its rings, and break its channel when it tries to light the beacon |
Full details on every enemy and boss are under Features in depth.
brew install sdl2 sdl2_mixer glm llvm
The Makefile prefers Homebrew LLVM (some Command Line Tools installs ship a
clang++that can't find the C++ stdlib headers) and falls back to Apple clang if LLVM isn't installed. The macOS SDK path is detected withxcrun, so nothing needs editing.
sudo apt install g++ make libsdl2-dev libsdl2-mixer-dev libglew-dev libglm-dev
make && make test
pacman -S mingw-w64-ucrt-x86_64-gcc \
mingw-w64-ucrt-x86_64-SDL2 \
mingw-w64-ucrt-x86_64-SDL2_mixer \
mingw-w64-ucrt-x86_64-glm \
mingw-w64-ucrt-x86_64-glew \
make
make # compile → ./shooter
make run # compile + run
make test # headless tests: physics, level data, AI, a simulated full run
make clean # delete binaries
./shooter --play # skip the main menu and start an ARENA run
./shooter --fast # skip the main menu and start the FAST time trial
./shooter --endless # an ENDLESS run
./shooter --boss 3 # BOSS mode: one boss on its own (1 the Warden ... 6 MORNINGSTAR)
./shooter --act2 # an ACT II run (ignores the unlock)
./shooter --arena 4 --wave 3 # jump straight to an arena / wave (here: the Warden)
./shooter --arena 5 # the Sanctum: the Sovereign
./shooter --dev # open the DEV level select (also ` or F2 on the main menu)
./shooter --fast --arena 3 # jump to a FAST room (here: the Ascent)
./shooter --room 7 # ROOM ATTACK: one FAST room on its own, for its medal (here: the Tower and the Pacemaker)
./shooter --god # take no damage (for recording footage)
./shooter --lighting 1 # LIGHTING: 0 LOW, 1 MEDIUM, 2 HIGH (--skip glow / --skip bump / --skip cull to compare)
Open the MSYS2 UCRT64 shell (C:\msys64\ucrt64.exe), navigate to the project folder, then:
make # compile → shooter.exe
make run # compile + run
make clean # delete binary
Important: use the MSYS2 UCRT64 shell, not PowerShell or cmd. The Makefile auto-detects the platform.
Runtime DLLs: The game links against DLLs in
C:\msys64\ucrt64\bin(SDL2.dll,glew32.dll,libstdc++-6.dll, etc.).make runworks automatically because the UCRT64 shell already has that directory onPATH. If you want to runshooter.exeoutside the shell (e.g. by double-clicking), either addC:\msys64\ucrt64\binto your systemPATH, or copy those DLLs next toshooter.exe.
The same C++ compiles to WebAssembly with Emscripten, rendering through WebGL2:
source ~/emsdk/emsdk_env.sh # once per shell
make web # → web/dist/ (≈3.2 MB: wasm + preloaded shaders/sounds; ≈1.7 MB served compressed)
python3 -m http.server -d web/dist 8000 # open http://localhost:8000 (?play skips the menu, ?fast starts the time trial; ?arena=4&wave=3 jumps to the Warden, ?arena=5 to the Sovereign, ?dev opens the level select)
What the port needed (all behind #ifdef __EMSCRIPTEN__, so the desktop build is unchanged):
gl.h includes GLES3; ShaderProgram rewrites #version 330 core to #version 300 es plus default precision at load time, so one set of shaders serves both builds (keep them free of implicit int→float conversions and uniform initializers, which GLSL ES rejects).main.cpp runs one App::frame() per requestAnimationFrame instead of a blocking loop.postMessage).devicePixelRatio), capped at the graphics quality's resolution; SDL's web port reports a fixed window size, so main.cpp asks for the canvas size directly.web/index.html fetches the portfolio site's /api/overdrive-board into localStorage, where the game reads it like its own board, and posts finished runs to it. With no API (a local server) the game shows its own board.localStorage (the Emscripten filesystem is in-memory and forgotten on reload), through Persist.h.unadjustedMovement (raw mouse input) where supported; together with MouseFilter.h this stops the occasional huge bogus mouse delta Chrome reports, which used to snap the view round 180°.| Input | Action |
|---|---|
| W A S D | Move |
| Mouse | Look |
| Space | Jump / Double jump |
| Left Shift | Dash (directional) |
| Left Mouse | Fire weapon |
| Right Mouse | Aim down sights / scope (Lancer / Longshot) |
| Q | Grapple hook (rebind in Settings: Q, E, Mouse 4, Mouse 5 or Middle Mouse) |
| 1 2 3 4 / Scroll | Revolver, Shotgun, Lancer, Longshot |
| 5 | Oathbreaker, the sword (the Zenith only): Left Mouse strokes (hold, release: a thrust), Right Mouse parries then blocks, a slam with it out plunges |
| Tab | Armory: spend upgrade points (pauses the game) |
| G | Throw grenade |
| R | Reload / Retry the arena (on death) |
| F | Punch: parries projectiles, breaks a Juggernaut's smash, detonates your grenade, hits what's in front (and finishes a glowing enemy when there's nothing to parry) |
| E | Finish a glowing enemy in reach (never parries); otherwise interact |
| Left Ctrl / C | Crouch / Ground slam |
| Enter | New run (on death/win) |
| Escape / P | Pause / Resume (Escape quits to menu from death/win screens; in a browser, Escape releases the mouse and pauses). The pause menu also toggles fullscreen |
| V | Inspect your weapon |
| Backspace | Restart the current arena / room |
Gamepad (Xbox layout; PlayStation and others map onto it, in browsers too):
| Input | Action |
|---|---|
| Left stick / right stick | Move / look |
| RT / LT | Fire / aim |
| A | Jump |
| B | Slide / ground slam |
| RB | Dash |
| LB | Grapple |
| Y | Punch / parry |
| R3 (right stick click) | Finish |
| X | Reload |
| D-pad left / right | Previous / next weapon |
| D-pad up / down | Grenade / inspect |
| Start / Back | Pause / Armory |
In menus the D-pad moves, A confirms and B goes back; on the main menu Back opens the dev level select.
The game compiles as one translation unit: src/main.cpp includes everything else, and all the code is in headers, grouped by area. The Makefile finds headers by itself (find src -name '*.h'), so a new file only needs an #include. Includes are written from src/ (#include "enemies/Enemy.h").
src/
├── main.cpp entry point: SDL/OpenGL setup, command-line flags, the App::frame() loop (desktop + web)
├── core/ the platform layer: gl.h, Camera, Mesh, ShaderProgram, Display (render canvas),
│ Collision (AABB, Wall, SpatialGrid), Settings, Persist (files / localStorage),
│ Gamepad, MouseFilter, Difficulty, GameState (the mode / state base)
├── game/ the live game
│ ├── GameplayState.h the game's state and an index of everything it does (declarations)
│ ├── Gameplay_*.h its member functions by topic: Flow (the run), Tick (the 60 Hz step),
│ │ Combat, Menus (input, pause, armory), Render, HUD, Dev, Shifts, Reliquary, Sword
│ ├── WaveDirector.h the arena → wave → arena state machine
│ ├── DevFlags.h the command line's globals (--arena, --god, --cam, --overlay ...)
│ └── Progression.h ... XP and upgrades, records, leaderboards, the BOSS table; Score, Ghost, EndlessWaves
├── player/ Player (the movement controller), GrappleHook, Projectile, Weapons, Sword, StyleSystem
├── enemies/ Enemy.h (the shared state, physics and every regular enemy's AI),
│ EnemyTypes.h (the roster, attacks, base stats), the Reliquary's Revenant and Weaver
├── bosses/ one folder per boss, each built the same way:
│ ├── sovereign/ <Boss>State.h its state, constants and events (inherited by Enemy / EnemyEvents)
│ ├── warden/ Enemy<Boss>.h its mind (Enemy::think<Boss>): decides, emits events
│ ├── penitent/ <Boss>Hazards.h what it leaves in the arena (no OpenGL, unit-tested)
│ ├── leviathan/ Gameplay_<Boss>.h its fight in the game: events → damage, effects, sound, drawing
│ └── absolute/ ...plus the Absolute's rig (AbsoluteRig.h), the feel of each move (AbsoluteFeel.h)
│ and MORNINGSTAR (Gameplay_Morning.h)
├── world/ the maps: Level.h (Act I), LevelAct2.h, LevelGauntlet.h (FAST), ArenaShifts, Shapes, Interactable
├── render/ BoxRenderer (one instanced draw), PostProcess (bloom, CRT), TextureGen, WorldMesh,
│ Effects, EnemyModel (every enemy's box rig), GunKit / GunMotion / ViewModel (the guns),
│ GunModel / GunFile (a gun's shape as data: assets/guns/*.glb)
├── audio/ AudioSystem, SfxMixer (positional mixing), MixTable, MusicSynth (the live soundtrack),
│ VoiceSynth / VoiceTable / EnemyVoice (the enemies' voices, built in code)
├── ui/ MenuState (main menu), SettingsMenu, UIRenderer (HUD and overlays), UIBatch, PixelFont,
│ LeaderboardView, GunViewer (--viewer), BalancePanel (F1)
├── shaders/ GLSL 3.30 (rewritten to GLSL ES 3.00 for the web at load time)
└── third_party/imgui/ Dear ImGui 1.91.9 (MIT), only for the balance panel
tests/ test_physics.cpp, test_game.cpp, test_zenith.cpp, BossSim.h (see Testing)
tools/ sound generators (gen_sfx.py, gen_arsenal.py, sfx_absolute.py, import_sfx.py),
record_showcase.sh (re-records docs/overdrive.gif),
blender/overdrive_guns.py (the Blender add-on for assets/guns)
assets/guns/ the four guns' shapes, glTF (see "Edit a gun")
web/index.html the browser shell for `make web`
docs/superpowers/ the design specs and plans each feature was built from
Rule of thumb: nothing in enemies/, world/, or a boss's State, Enemy and Hazards files touches OpenGL or SDL, and player/Player.h only reads SDL key codes. That's what lets the tests run the real game logic without a window. (GrappleHook.h draws its own rope, so it's the exception in player/.)
Enemies never touch the renderer or the sound engine. Each tick, Enemy::update clears ev (an EnemyEvents) and the enemy's AI fills in what happened: shots fired, a melee hit, a slam, a boss's crash marked on the floor. GameplayState reads those events and turns them into projectiles, damage, particles and sound. Each boss adds its own event fields (LeviathanEvents, AbsoluteEvents …) in its <Boss>State.h.
Physics runs at exactly 60 Hz regardless of display framerate. Elapsed time is accumulated and drained in fixed 1/60 s steps. Leftover time becomes an interpolation factor for rendering, so motion looks smooth at any framerate.
accumulator += elapsed_time # GameplayState::update (game/Gameplay_Flow.h)
while accumulator >= PHYSICS_DT:
physics.update(PHYSICS_DT)
accumulator -= PHYSICS_DT
alpha = accumulator / PHYSICS_DT # 0..1, used to lerp camera
Instead of velocity = direction * speed, acceleration is applied incrementally each tick:
currentSpeed = dot(velocity_horizontal, wishDir)
addSpeed = clamp(maxSpeed - currentSpeed, 0, accel * dt)
velocity += wishDir * addSpeed
Momentum is preserved in the air, so strafe-jumping can gain a small speed boost — the same mechanic behind Quake/Titanfall/ULTRAKILL movement feel.
Each frame:
World geometry is batched into as few draw calls as possible. Everything dynamic is a unit cube instance, so a wave of enemies plus their debris is still one glDrawElementsInstanced call.
The player is an AABB. Each wall/platform is also an AABB. Overlaps are pushed out along one axis, and a spatial grid accelerates queries so only nearby walls are tested. The axis isn't simply the one of least penetration: the player remembers where it started the tick (Player::tickFrom), and if it was clear of a box along an axis and now overlaps it, it came in from that side and is pushed back out there. Least penetration alone pushed fast moves (a 40 m/s slam, a long drop, a full-speed grapple) out the far side of slabs and thin walls; make test drops the player onto thin slabs at up to 70 m/s to keep that fixed. Overlaps it was already in at the start of the tick fall back to least penetration. Perimeter walls are low enough to stand on; what keeps you in the fight is a zone check that only allows the current arena (plus the corridor and next arena once it's cleared), with the zone's max.y as an invisible ceiling and an optional void plane below.
Moving platforms are ordinary walls flagged dynamic: LevelData::updateMovers() moves them every physics tick and records how far each went. They're left out of the spatial grid and tested every tick instead, the player remembers which wall it's standing on, and GameplayState adds that platform's movement to the player before the physics step, which is all "riding a platform" needs.
All knobs are public members of Player in src/player/Player.h:
| Variable | Default | Effect |
|---|---|---|
horizontalSpeed | 7.0 | Max ground speed (m/s) |
gravity | -24.0 | Fall acceleration |
jumpForce | 8.5 | Jump height |
acceleration | 80.0 | Ground responsiveness |
airAcceleration | 42.0 | Mid-air steering |
friction | 10.0 | Ground deceleration |
make test
Three headless test programs, no window or GPU needed (CI runs them on Linux and macOS, and builds the web version):
tests/test_physics.cpp: the player controller. Landing, ledges, ceilings, slides, and fast landings on thin floors at up to 70 m/s (the case that used to push you through slabs).tests/test_game.cpp: every level's data (no spawn inside a wall, every jump pad lands, every moving platform stays clear of walls), the enemy AI, rigs, weapons, XP, the mix table, and simulated full ARENA, FAST and ACT II runs.tests/test_zenith.cpp: the encore. The sword, the Zenith, and BossSim (tests/BossSim.h), which pits the real AI of the Warden, the Sovereign, the Leviathan and the Absolute (both forms) against scripted players: cheese strategies that must fail, a solid player whose kill time must land in a target range, and a dodger. BossSim is deterministic, so a change that shifts its numbers changed the fight.Story boss combat, counters and simulation limits are documented in the combat overhaul. make test-story-bosses compares sustained weapon fire with real movement, reloads, healing and reactions to committed attacks.
Story also has make test-story-overhaul for objective pacing, Ballast, weather budgets and record layout, and native make test-dev for checkpoint inputs, actual brake shots, gate/retry behavior and settings screenshots.
Everything below runs without touching the mouse, so it works from scripts.
# Story weather/detail override for testing: 0 OFF, 1 LOW, 2 MEDIUM, 3 HIGH
./shooter --story2 --ambient 0
# Render N frames, save the last as a BMP and quit
./shooter --arena 3 --cam 0 3 -129 -90 18 --weapon 4 --aim --shot 90 shot.bmp
# --cam X Y Z YAW PITCH --weapon 1-4 --aim --res LINES
# --overlay armory | pause | settings | victory | poseN (hold the Sovereign in pose N: 0-9)
# reloadNN | inspectNN | switchNN | punch (hold the gun NN% through it)
# penitentN (hold the Penitent: 0 kneeling, 1 low sweep, 2 high sweep, 3 risen, 4 scourging)
# leviathan2 | leviathan3 (start the Leviathan in its hunt / its eclipse)
# absolute2 | absolute3 | absolute8 (the Absolute's ascension / his blade / the last judgment)
# abposeN (hold the Absolute mid-tell: 1 jab ... 10 judgment, 11 reeling, 12 broken, 13-20 cross, backfist,
# elbow, roundhouse, haymaker, shoulder charge, axe kick, clap, 21 skyfall, 22 meteor, 23 aloft; 0 his guard)
# ascend (the act's card, then the rise into the Zenith)
# morning | morning5 | morning6 (start MORNINGSTAR / in RISING / in TOTALITY), swallowNN (the swallow, NN% through)
# --act2 --arena 3 --wave 4: straight to the Penitent (the cage is simply there with --cam)
# --act2 --arena 5: straight into the Maw and the Leviathan
# --act2 --arena 6: straight into !!!!! (the Zenith) and the Absolute (add --cam ... so the armory doesn't open)
# --sword: the sword on key 5 in any arena; --weapon 5: start with it drawn
# --story4: Chapter 4, THE BLOOD MOON PLAIN (in progress); --overlay plainimpact: a big meteor lands 160 m ahead; --overlay plainrevealNN: the opening from NN s (0-13 the prologue); --overlay plainclose / plaindeck: straight to the Gleaner fight / onto its back
# --splinter: UNIT 24's SPLINTER (Chapter 4) on the grenade button in any mode: G throws, shoot it to bounce the round
# --story5: Chapter 5, THE COLD STORE; --recall: RECALL (X) in any mode; --overlay coldride: the capsule ride out;
# --overlay rspose: the Restorer stands still (screenshots); --overlay bossdown-rsfinish: the Restorer falls and the RECALL finisher plays
# --overlay swingSU: hold sword stroke S (0 forehand, 1 backhand, 2 overhead) U/10 through it; guard: its guard
# --swordorbit DEG: see the sword and the arms holding it from the side (DEG round them), to check the wrists
# --spawn TYPE (repeatable): enemies in an arc in front of the camera
# 0 Husk 1 Ripper 2 Sentinel 3 Raptor 4 Brute 5 Mite 6 Juggernaut 7 Warden 8 Sovereign 9 Shieldbearer
# 10 Conduit 11 Conductor 12 Seraph 13 Anchor 14 Penitent 15 Revenant 16 Weaver 17 Leviathan 18 Absolute
# 19 Courier 20 Bulwark 21 Cantor 22 Harrier 23 Plumb Line 24 Foreman 25 Pacemaker (the Gauntlet's)
# --hollow N: --spawn enemies are Hollowed (1 Enraged, 2 Twinned, 3 Haloed)
# Footage: fixed 1/30 s frames; skip SKIP, then save FRAMES as PREFIX_0001.bmp ...
./shooter --arena 5 --god --cam 0 1.8 -310 -90 2 --spawn 8 --autoaim --kite --clean --record 180 30 out/boss
# --campath X Y Z YAW PITCH glide the camera there over the recording
# --autoaim turn onto the nearest enemy in sight and fire (and take any finisher in reach) --kite back off from whatever gets close
# --clean no title cards or enemy cards
# OVERDRIVE_RUNLOG=1 ./shooter ... write runs.jsonl from dev runs too (normally only real play)
# --overlay driveNN the OVERDRIVE screen look at NN %
# --overlay clearwaves every wave cleared for you, one every 4 s (with --audiodump: hear the room clears)
# --overlay hook | alt with --autoaim: grapple whatever it's aimed at every 2.5 s / use alt fire instead of the trigger
# --overlay teach | teach2 the NEW MOVE cards, every time (screenshots)
tools/record_showcase.sh # re-record docs/overdrive.gif (needs gifski)
# The gun viewer: one gun on a stand, reloaded whenever its file changes (see "Edit a gun")
./shooter --viewer [revolver|shotgun|lancer|longshot]
# Frame times
./shooter --bench 2000 --arena 4 --wave 2 --god [--cap HZ] [--res LINES]
# prints a per-zone breakdown (events, sim parts, render passes, present, swap) and the worst frames
# --frametimes FILE per-zone CSV of every frame, written on exit (works in normal play too)
# --gpu-timer add GPU time via GL timer queries; on macOS reading them stalls the CPU, so
# frame times run ~2x slower while it's on. ACT II needs --cam or it times the armory
# --skip sky,world,boxes,water,fx,viewmodel,bloom,hud leave render passes out to see what each costs
# --inflight N frames the CPU may run ahead of the GPU (default 2; 0 = no fence, 1 halves the rate on macOS)
# --aa N MSAA samples whatever the settings say (0 = off; the setting offers OFF / 2X / 4X)
# (--fast means FAST mode, not "start quickly": use --play for ARENA)
# Record the final mix (music + effects) to a WAV, then quit
./shooter --arena 5 --god --kite --audiodump mix.wav 15
OVERDRIVE_SFXLIST=1 ./shooter ... # print every loaded sound and its length
./shooter --mixreport # every sound's level against its mix target, then quit
In the game: ` or F2 on the main menu opens the DEV level select (also --dev, or ?dev on the web build): any arena and wave (Act I or Act II), any FAST room, or any STORY checkpoint in all three chapters, with god mode on or off. These are practice runs (no records, no leaderboard); in one, F5 clears the current wave and F6 refills health. STORY starts skip the wake-up and include equipment from earlier rooms. Scroll over the STORY column for Chapters 2 and 3; rows use chapter.room numbering.
In world/Level.h, inside the arena's block in buildLevel():
wall(x0, y0, z0, x1, y1, z1, color); // solid, rendered
prop(x0, y0, z0, x1, y1, z1, color); // rendered only
neon(x0, y0, z0, x1, y1, z1, color); // self-lit trim
make test then checks that no spawn point ended up inside it, that every jump pad still lands on its platform, and that no tall scenery sits inside an arena.
In world/Level.h (or LevelGauntlet.h, LevelAct2.h): B.mover(centre, halfSize, Mover::Path::PINGPONG, offsetA, offsetB, period, phase, glowColour), or Path::ORBIT with two radius vectors. make test sweeps every mover through two full periods and fails if it ever passes through a wall.
WeaponId and a row to the table in player/Weapons.h (damage, pellets, spread, fire rate, magazine, reload, aim zoom, pierce, recoil, mod).assets/guns/<name>.glb (see "Edit a gun"), add the name to gunName() in render/GunKit.h, write its rig (rigOf(): what moves, what glows), its extras (extrasOf(): cells, muzzle flash, reload props) and the joints it needs (jointsNeeded()), and its motion in render/GunMotion.h.SND[] in GameplayState::fireWeapon() (game/Gameplay_Combat.h).UIRenderer::drawWeaponIcon(). The slots, Armory, ammo and reload all follow from the table.Every gun's stats, every enemy's and boss's health, speed and timing, OVERDRIVE and the style meter, and the four difficulties are on sliders: press F1 in the desktop game. The game freezes and the mouse comes free; drag a slider (Ctrl+click to type a value) and press F1 again to play with it. Changes apply at once (enemy health from the next spawn, magazines from the next reload); a changed value turns amber, with a reset beside it. SAVE writes what you've changed to assets/balance.txt, REVERT goes back to that file, DEFAULTS to the code's numbers.
assets/balance.txt is plain text (weapon.revolver.damage = 50 # default 42), loaded at startup by the game (desktop and web) and by the tests, so make test checks the game as it ships: if a change breaks a promise the tests hold (a scoped Longshot one-shots any regular enemy, say), they'll say so. The names and ranges are in src/core/Balance.h; adding a value there puts it on the panel. The web build leaves the panel out (it adds ~190 KB to the download); make web WEB_FLAGS=-DWEB_BALANCE_PANEL puts it in, opened with ?balance.
Each gun's shape is a file, assets/guns/<name>.glb (glTF, made of boxes), so you can work on it in Blender. The code still owns how a gun moves and glows; the file owns what it's made of.
tools/blender/overdrive_guns.py. Its panel is in the 3D view's sidebar (N) > OVERDRIVE../shooter --viewer <gun> beside Blender. Each Save gun reloads it in the viewer; if the file has a problem, the viewer says what and keeps the last good version.Save gun checks the scene first and won't save a gun over a different one (the scene remembers which gun it was opened from), a gun missing a joint its rig needs (a copied crane.001 isn't crane), or an unknown glow channel or show flag; a mesh that isn't a box saves with a warning (the game uses its bounds). Work in Object Mode; a box mirrored with a negative scale is fine.
Live link (the panel's button, off until you click it) lets Claude edit the open scene: Claude runs python3 tools/blender/live.py -c "...", which drops the script in ~/.overdrive_blender/inbox; Blender runs it as one undo step (Ctrl+Z takes it back) and writes back what happened. The add-on's helpers (add_box, add_joint, set_glow, set_show, describe, open_gun, save_gun...) work in game space. Quitting Blender or clicking the button again turns it off.
What the game reads from the file (render/GunFile.h has the details):
| In Blender | In the game |
|---|---|
| a mesh object | one box: the mesh's bounds, placed by the object (move, rotate and scale it in Object Mode) |
an object named crane, cylinder, hammer, trigger, needle, pump, bolt, mag | a joint the gun's rig moves; whatever is parented under it moves with it, about its origin |
an object named muzzle or cells | a socket: where the muzzle flash and the ammo cells go (the sword: its fuller's guard-meter cells) |
material glow or glow*0.5 | the gun's glow colour (times 0.5); any other material: its base colour |
custom property glow = const glow accent breath stripe rim needle port gem charge | the box glows on that channel, k times as strongly (k a number or an RGB); gem and charge are the sword's (its gems; its edges, lit by a charged thrust and a riposte) |
custom property phase | its glow pulses along a wave (the revolver's glyphs) |
custom property show = loaded / !loaded / bigmag / !bigmag / broken / !broken | the box only shows while that holds (broken: the sword's guard) |
Each gun needs its joints (jointsNeeded() in GunKit.h): the revolver its crane, cylinder, hammer, trigger and needle; the shotgun its pump; the rifles their bolt (and the Longshot its mag); every gun a muzzle and cells. The sword, OATHBREAKER, is a file too (assets/guns/sword.glb, rebuilt from tools/blender/sword_build.py; ./shooter --viewer sword), needing only its cells socket. make test checks every file loads, has its joints, and survives a write and read back. The ammo cells, muzzle flash, falling brass, speedloader, shells and stripper clip are drawn by code (extrasOf()), because they depend on the magazine and the moment.
Your arms are a file as well (assets/guns/arm.glb, rebuilt from tools/blender/arm_build.py; ArmFile.h): the sleeve, the vambrace, and a gloved hand with five fingers of three joints each (thumb1-3, index1-3, middle1-3, ring1-3, pinky1-3, laid flat in the file), with the parrying gauntlet's plates on every finger bone (show = gauntlet). The code bends the fingers into poses (handGrip, handFist, handFlat, handRelaxed, handSpread, handFan) and every joint follows its pose on its own spring (HandSpring), so the fingers settle with a little overshoot, out of step with each other, and flex slightly at rest. To see a hand up close: --overlay handPV (pose P: 0 grip, 1 fist, 2 flat, 3 relaxed, 4 spread, 5 fan, +6 in the gauntlet; view V: 0 back, 1 palm, 2 knuckles, 3 thumb side). --overlay fanNN holds the revolver's fanning hand NN% of the way between two slaps (faninNN / fanoutNN: coming up into place / being put away).
The levels are built in C++ (world/Level.h, LevelAct2.h, LevelGauntlet.h); a layout made in Blender goes on top (assets/levels/<level>.json, world/LevelLayout.h): walls, props, neon, prop files placed anywhere, spawns, and pieces that break. New walls go after the code's, so nothing the code points at by number moves.
make level-dump writes every level as the game draws it into build/levels/<level>/ (its faces, textures, collision boxes, arenas).blender --python tools/blender/level_import.py -- act1 (or act2, fast) opens it: World (what the game draws, for looking at), Arenas (bounds, starts, spawns), and OD_LAYOUT, the collection you edit. Each object in it carries od_kind: a mesh is a wall, prop, neon or destructible (its box; od_mat its surface, od_name/od_hp a destructible's prop and health), an empty a spawn_ground, spawn_air or model (od_name: assets/props/<name>.glb).tools/blender/layout_export.py (Scripting tab) to write assets/levels/<level>.json, then make light to rebake (below). A broken layout is reported and left out; make test checks it.Every level's ambient occlusion and its arenas' sun shadows are baked into a 1 m grid (render/LightBake.h, make light → assets/levels/<level>.light, about 20 s for all three) and looked up by the world shader. Rebake after changing a level: a bake of an older level is left out (the game says so, and make test fails until it's rebaked). Roofed arenas keep their hand-set light (no baked sun). --skip bake draws without it, --overlay bake shows what's baked (red open, green sun).
A move can be keyed in Blender and played over the code's pose (render/AnimClip.h): tools/blender/anim_build.py writes a rig's clips to assets/enemies/anim/<rig>.glb (only its joints and their keys: the model file is untouched) and saves build/anim/<rig>.blend to keep working in (then -- --export-only <rig>). A clip plays over the move's wind-up, eased in from whatever came before; root moves the whole body. The Sovereign's JUDGMENT is the first.
tools/blender/render_art.py renders every arena (in its own light) and every boss into assets/art/ (after make level-dump); the menus draw them behind their pages (ui/MenuArt.h, stb_image for the JPEGs).
tools/blender/destruct_build.py makes a prop whole (<name>.glb) and cut into chunks (<name>_pieces.glb, each a closed convex piece). Place it with a layout (destructible); shots and blasts wear it down (it darkens as it goes), then its box is gone and its pieces fly, settle and fade (game/Gameplay_Destruct.h). A retry puts it back. --overlay broken breaks them all for a screenshot. The Sunset Yard has four.
EnemyType and a row to the stats table in enemies/EnemyTypes.h (health, size, speed, colours, tip text).think…() in enemies/Enemy.h; report attacks through ev (shots, melee, slam…).buildEnemy() in render/EnemyModel.h.world/Level.h.Make a folder under bosses/ with the four files every boss has: <Boss>State.h (add its State and Events structs to the base lists of Enemy and EnemyEvents in enemies/Enemy.h), Enemy<Boss>.h (its think…(), included at the end of Enemy.h), <Boss>Hazards.h, and Gameplay_<Boss>.h (included at the end of game/GameplayState.h). Add it to isBoss(), give it a BOSS mode row in game/Progression.h, and a BossSim case if its fight should be held to a kill time. Its body can be a file made in Blender, like the Sovereign's, the Absolute's and Morningstar's (assets/enemies/<name>.glb): give it joints in render/EnemyFile.h and restJointsOf(), its motion in filePoseOf(), write a skeleton with tools/enemy_export --joints <name> <file>, and build on it with a script in tools/blender/ (sovereign_build.py is one); ./shooter --viewer <name> shows it as you go.
unlocked by killing the Sovereign. Fall beneath the eclipse into the Drowned Nave, a flooded cathedral: the water slows you on foot but slides skim across it, and it rises every wave until only the galleries, bridges and the organ loft stay dry. Then down a shaft into the Orrery: a terrace round a void where two rings turn over a captive sun. The sun's flare sweeps the arena (get behind a pillar), and in wave 2 you hold a circle that rides the inner ring. Then through the north arch into the Descent: a cage that rides down a vast shaft, stopping at a different floor for each wave (bell galleries, the brake clamps you shoot to release it, a furnace ring), and at the bottom, the Penitent: a kneeling colossus chained to the pit's walls. Break its six chains (shoot the anchors, or grapple onto one and hang on to rip it out), jump its low censer sweeps and slide under the high ones; unchained it rises and stalks you, and near death it scourges itself, opening a wound on its back. Its death opens the pit's south wall onto the Reliquary: pieces of the four Act I arenas (the Yard, the Foundry, the Spire, the Core) drifting over a void, regrouping between waves (scattered, a ring, a stack, then a path to a dark hole), fought on with jumps, dashes and the grapple; fall and you're back on the last relic you stood on. Its two new enemies: the Revenant, whose soul flees to another relic when it dies (catch it or it comes back), and the Weaver, which strings snaring wires across your way. Clear it and the hole opens: jump in, and fall into the Maw, a cavern of cracked stone round a black pool, where the Leviathan rises: a colossal plated serpent with an eclipse for an eye. It crashes its head down along a strip it burns into the floor (dash out, then shoot its eye while it lies beached, or stand your ground and punch it as it lands), spits orbs you can parry back into its eye, and rolls tides over the ring to jump. Under 65 % it hunts from beneath the floor and breaches out of the well nearest you; under 30 % the Maw floods, its eye stays open, and it tries to swallow you whole (run, or shoot down its throat until it chokes). Its death ends the act (the clear is saved there and then), and the eclipse breaks: you rise into !!!!!, the encore, a floating ruin at sunset (the eclipse low on the horizon, the sun's corona burning round it), to face the Absolute: a figure of white light with red eyes and rune-lit gauntlets, a boxer faster than you, who fights in combos and takes to the air. Every strike flashes a colour: white, parry it (F, or the sword); gold, dodge or block it; red, get out of where it lands. And every one can be fought by ear: a wind-up that rises into the blow, then a cue on the beat to act on (a bright ting: parry; a low : dodge or block; a double : get out), then a boom whether it lands or not. Here, and only here, key 5 draws , a sword: three-stroke combos and a charged thrust that heal you as they land, right click to parry (the first 0.15 s) or block (a guard meter that breaks). ACT II runs are not ranked yet; the act and the Zenith keep local best times.
seven rooms, each reached down a ribbed boost tube (a breather: health waiting, nothing shooting at you) that fires you along at 26 m/s. Step into the room and the fight starts: the door behind you locks, and the exit unlocks when the room is clear. A waypoint points to the exit.
Every room is two waves, then its mini-boss: the second wave is shorter, comes from a new angle and sets up what's next (a Shieldbearer before the Bulwark, Raptors before the Harrier, a Juggernaut before the Foreman). Kill a room's mini-boss and the room is won: whatever's still standing falls apart with it, and each one left standing costs a second on the clock. Later waves come at you from ahead, never back where you've been.
A 3-2-1 countdown starts the clock; every room clear rings an anvil and shows a split (green ahead / red behind your best run) and the room's medal (bronze, silver, gold, and a hidden dev time, timed from the room's fight starting to its clear). The clock stops at the beacon. Ranked S/A/B/C/D against par times; the end screen lists every room's time and medal. Dying (or Backspace) restarts the room with the clock still running. A glowing ghost runs the route of your best time alongside you (FAST GHOST in Settings turns it off). Each room's best time and medal are kept; ROOM ATTACK (the FAST page on the menu, or --room N) runs one room on its own, with the level a full run would have by then.
Every room is dressed with props made in Blender (tools/blender/props_build.py): a lit arch with a beacon frames each exit, and a medical cabinet marks each breather's potion.
GunMotion.h): a kick that settles by the gun's weight, a switch where the old gun drops and the new one rises as its cells boot up with a chime, and reloads in three beats (open, feed, close) with every sound on its beat. The gunshots are one family too (tools/gen_arsenal.py): the crack of a real recording, a synthesized thump, a metallic ring in the gun's pitch and a tail, three variants eachEvery enemy is a rig of boxes on joints (hips, shoulders, wing roots) posed from its AI state each frame: legs swing with the walk cycle, wings flap, and arms come up to aim or slam during a wind-up, so you can read an attack before it lands. Each type teaches a different answer:
| Enemy | Looks like | What it does | Answer |
|---|---|---|---|
| Husk | Humanoid rifleman, glowing visor | Holds mid range, strafes, fires slow orbs | Parry the orbs back (F) |
| Ripper | Low four-legged hound with blades | Zig-zags in, crouches, lunges | Dash out of the lunge |
| Sentinel | Tall cyclops sniper | Paints you with a laser, then fires a fast 3-round burst | Break line of sight while the laser is up |
| Raptor | Bird with a 4 m flapping wingspan | Circles overhead shooting, then dives at you | Watch the sky |
| Brute | 2.9 m heavy with a glowing chest core | Walks you down and slams the ground; lobs at range | Jump the shockwave |
| Mite | Small spider bomb | Rushes in and detonates | Shoot it early: its blast hurts its friends |
| Juggernaut | 3.3 m armored heavy with a cannon arm | Armor halves bullet damage; fires slow siege shells, smashes up close | Parry the shell (400 back), or punch the smash to break it |
| Shieldbearer | Soldier behind a tower shield with a glowing sigil | The shield stops bullets from the front; advances slowly, fires spreads of orbs, bashes up close | Flank it (it turns slowly), shoot the head over the rim, use explosives, or parry the bash to knock the shield aside |
| Conductor | A hovering emitter with a glowing core and turning fins | Doesn't attack: tethers up to three nearby enemies (cyan beams, a ring over each head) and they take no damage at all while it lives (shots spark off them); hangs back above and behind them | Shoot it down first (90 HP); the tethers drop with it |
| Conduit | A pylon with a pulsing core and turning rings | Doesn't fight: the wave spawns out of it | Destroy it (300 HP); the core beats faster as it breaks |
| Seraph (Act II) | Winged figure with a ring for a head | Hangs high and far; charges, then sweeps a beam toward you that turns slower than you run | Keep moving, break line of sight, or hit it hard (or in the head) while it charges |
The regular enemies (not the bosses, which have their own answers) take a hit with their bodies (src/enemies/HitReact.h, src/game/Gameplay_Feel.h):
tools/gen_sfx.py): jumps, landings, slams, reloads, bolts, hits, kills, telegraphs, explosions, stingers, spawns, pickups, level-upstools/import_sfx.py: the four gunshots (real pistol, shotgun and rifle recordings), the dash whoosh, the grapple, footsteps (four variations), the punch and the parry clank. Sources in assets/sfx/CREDITS.mdaudio/SfxMixer.h, in the same audio callback as the music): enemies, doors, blasts and boss attacks sound from where they happen (quieter and darker with distance, panned to their side, muffled behind you); every play varies a little in pitch and level, and a sound with numbered variants (name_1.wav …) never repeats one twice running; voice caps per group (player, enemies, world, UI) keep a crowd from turning to noise without ever cutting your gun or the boss, and past six enemy and world sounds at once they share the level of about six, so a big fight never buries a wind-up; the music and the world duck when you're hit, a wave starts or a boss winds up; each arena has its own reverb (the open Yard, metal halls, the long dark Sanctum, Nave and Orrery)/api/overdrive-board, checked server-side for plausible scores and times); elsewhere it's this machine's. Only full runs count: not one started at a later arena, in god mode or from the dev level selector F2 on the main menu,--dev, ?dev` on the web) — jump to any arena (and wave), any FAST room, or any STORY checkpoint in all three chapters, with god mode on or off. These are practice runs (no records, no leaderboard); in one, F5 clears the current wave and F6 refills health. STORY checkpoints skip the wake-up and include earlier equipment; scroll the STORY column to reach all three chaptersVoiceSynth.h, no audio files): its own spawn, idle murmur, steps or wingbeats, a wind-up tell for every attack (the Sovereign sings a different syllable for each stroke, the Penitent groans before a low sweep and rings a bell before a high one), a release, a hurt and a death. The voice director (EnemyVoice.h) decides who speaks: every wind-up within 40 m is heard, even out of sight; only the nearest few chatter; hurts and spawns are rationed. Hollowed enemies sound changed (Enraged higher and driven, Twinned doubled, Haloed shimmering, and their halo shatters)UIBatch.h); it used to be one draw call per pixel of text| Library | Purpose | Platform |
|---|---|---|
| SDL2 | Window, input, OpenGL context | all |
| SDL2_mixer | Sound effects | all |
| OpenGL 3.3 | Rendering | all |
| GLM | Math — vectors, matrices, transforms | all |
| GLEW | OpenGL function loader | Windows |
| LLVM/clang++ | Compiler (Homebrew) — replaces system clang | macOS |
| Anchor (Act II) | Hunched heavy with a ring core on its back | Projects a 10 m field where you can't dash or grapple; lobs slow orbs | Fight it from outside its field, or go in and kill it first; parry the orbs back for 120 |
| Warden | 4.6 m crowned boss, cabled to the Core's pillars | A reactor-fed guardian in three phases. Charging: four conduits on the pillars feed it (it takes half), it volleys, slams and calls Mites, and every third attack it vents, its core open. Overload (60%): it walks to the reactor and feeds; the reactor fires rings, it sweeps a lance that a pillar stops, each lance ending in a vent; hide or keep away and a seeker finds you. Meltdown (25%): it tears free, its core open, everything faster, lunging at you, with 40 s on the clock before it goes off (it hurts you and heals back to a quarter) | Shoot the conduit nodes (all four cut: it reels 4 s); hit the core when it vents (x3, x2 in meltdown); parry its orbs back into the core (150); jump the rings; punch a lunge as it lands (it reels) |
| Sovereign | 3.5 m horned knight with a greatsword, a halo and a cape | Act I's final boss. Dashes at you and dashes again; chains two sweeps into an overhead cleave with a shockwave; leaps onto any platform you climb; throws crescent slashes at range. Enrages at half health: faster, longer chains, double crescents, and the eclipse turns to blood while the orbiting platforms speed up | Dash through the dashes, jump the cleave's shockwave, and parry (F) a sweep or the cleave as it lands to break his guard for 3 s (he takes double while broken). Every tell lasts at least 0.35 s (0.28 s inside a combo), every recovery 0.8 s, and he pauses a moment at each phase |
| Revenant (Act II) | A gaunt machine-knight, a white-blue soul glowing in its cracked chest | Rakes twice up close, throws a slow soul bolt at range. When its body dies its soul flees to another relic and re-forms it at half health (twice at most) | Shoot or punch the fleeing soul; parry its bolt |
| Weaver (Act II) | A long-legged spider machine with a violet spinner | Keeps its distance and strings a glowing wire at chest height across your way every few seconds (up to four); touching one snares you, hurts and wakes the wave | Slide under or jump over; shoot a node to cut it; kill it and its wires drop |
| Leviathan (Act II's final boss) | A colossal plated serpent rising out of the Maw's pool: an iron skull, a jaw of turbine teeth, and an eclipse for an eye (a black disc in a burning corona) | Rears and crashes its head down along a strip burned into the floor (red, a deep roar), then lies beached; a torrent of orbs (the throat glows cyan, a gurgle); a tide rolled over the ring by its tail (a hiss); a spit for anyone keeping away, hiding or perching. Under 65 % it dives and breaches out of the well nearest you (the well boils first); under 30 % the ring floods, its eye stays open, and it swallows: an inhale that drags you toward its mouth | Dash out of the strip and shoot the eye while it's beached (x3), or punch it as it lands (staggered 4 s); parry an orb back into its eye; jump the tide; get clear of a boiling well; run from a swallow, or shoot down its throat until it chokes |
| The Absolute (the encore, in the Zenith) | A 2.6 m figure of white light, the most detailed model in the game: plated muscle, jointed fingers, wraps trailing light, two red eyes, gauntlets with rune bands and a broken halo; a boxer's guard | Fights in strings: TOO SLOW (a teleport behind you, jabs, a hook), KNEEL (an uppercut that launches you into a drop-kick), FLURRY (jab-jab-jab-cross), BREAKER (jab, cross, a gold haymaker), CROSS-UP (a backfist and a roundhouse from behind), BULLRUSH (a shoulder charge, an elbow, a red clap), HEEL OF HEAVEN (an axe kick, a stomp ring), a ray; serpents of light at range. He flies: up 9-14 m, circling you, then SKYFALL (a gold circle under you, then he crashes onto it - and onto a shard you camp on, breaking it) or METEOR (a white dive at you: parry it and he's knocked out of the sky). Up there his guard is down: 6 % of his health in one flight shoots him down. Every strike flashes white (parryable), gold (not) or shows red on the floor. Under 60 % he ascends: a 360 sweep that shatters four pillars, longer, quicker strings, mirages, burning seams, a ray from the Halo for anyone on the shards. Under 25 % he draws a blade of light; at 8 % the last judgment | Parry the white (he reels; twice in one string and he breaks for 3 s), dodge or block the gold, leave the red; slide under the sweep, stand in the dark cells, stay beside him at the judgment. A sword parry of his blade shatters it. Past 14 m his guard turns your shots aside: get close |
| MORNINGSTAR (the Absolute's second form) | The Absolute at 1.65x, blood orange with white eyes | His lethal blow doesn't kill him: he kneels, the eclipse swallows the Zenith, and he comes back with a fresh health bar inside it. Three phases: MORNINGSTAR (jabs, a backhand, GRASP, his ECLIPSE drop, SUNDER rings, FISSUREs, the CORONA's flares from the ring of fire), RISING under 55 % (every slam leaves an aftershock, everything quicker) and TOTALITY under 12 % (the ring of fire collapses on you, then the light goes out of him). A death here retries from his second form | Only white shows on his body (parry it; the backhand can be parried too); his gold and red moves are read from the floor. Jump the collapsing ring |
| Penitent (Act II) | A kneeling machine-monk, 8 m tall, hooded, a bone mask, two censers on chains, chained to six anchors on the pit wall | The Descent's boss. Chained, it can't move and takes a quarter of the damage: censer sweeps low (amber, a grinding whine) and high (white, a bell), a slam ring, burning incense pools, and a chain lash for anyone who keeps away or perches. With every chain broken it rises to 11.5 m and stalks you, sweeping in pairs and calling up Hollowed Husks; under a quarter health it scourges itself (rings of embers, everything 30% faster) | Shoot or rip the anchors; jump the low sweep, slide under the high; parry a censer as it reaches you (it reels, double damage); under a quarter, get behind it and hit the glowing wound (x3) |
runs.jsonl beside the binary (desktop only): what hurt you and what killed you, each gun's shots, hits and kills, time to kill per enemy, stuns, finishers and OVERDRIVE. python3 tools/runlog.py sums it upMixTable.h, held by a test; ./shooter --mixreport prints them all); OVERDRIVE_SOLO=tell|chatter|action|ui hears one role alonetools/gen_sfx.pyMusicSynth.h, no audio files): darksynth/industrial, a track per area (and one for the Sovereign), layers that build from the tubes into the fight and up again for the boss, muffled while paused./shooter --bench N [--cap HZ] [--res H] prints frame timessettings.cfg, records.v2.cfg, leaderboard.cfg, ghost.v2.cfg next to the binary; localStorage in the browser)