All notable changes to Exploids. Dates are ISO 8601 (YYYY-MM-DD).
First signed & notarized release since v0.13.0 — this DMG bundles everything from v0.14.0 through v0.14.4.
For players:
- Fix: watching the title-screen demo no longer leaves a movement key "stuck" when you then start a game (the ship could keep rotating on its own).
- Asteroid rendering is a little smoother, especially on lower-end hardware.
Everything else in this range is internal — a large refactor, a new automated test workflow (CI), and build-portability fixes — with no effect on gameplay. Details below and in the entries for v0.14.0–v0.14.3.
Under the hood (v0.14.4):
- Build/CI: fixed Swift 6 concurrency errors that only surfaced on the stable toolchain
(Swift 6.1 on the CI runner) and were hidden by newer local toolchains (6.3+, which infer the
isolation by default).
ReplayPlayer.advanceStepcalls the MainActor-isolatedGameScene.injectReplayInput, and the entireExploidsMacCLI layer (Main,ReplayRenderer) drives a MainActorGameScene/SpriteKit — all three are now explicitly@MainActor, which builds on both toolchains. Caught by the freshly added CI on its very first run.
- Cleanup: removed the dead "Wave Cannon / charge shot" feature — the charge level was never
raised, so the ship charge indicator, the
playChargeShotSFX + charge-hum synthesis, and the unusedchargeshot_0.m4asample were all inert. Docs (README / AGENTS.md) corrected accordingly. - Perf: the asteroid wireframe path is now rebuilt once per rendered frame instead of once per 120 Hz simulation step (roughly halves the SKShapeNode path rebuilds on a 60 Hz display). Purely visual — the golden replay still reproduces bit-exact and GIF/video rendering is unchanged.
- Fix: a failed replay encode when saving a high score is now logged instead of silently swallowed
(
try?→do/catch); the high score is still stored, just without the replay.
- Internal: split the 4.8k-line
GameScene.swiftinto thematicextension GameScenefiles (HUD, attract/autopilot, glossary, Mad-Meteoroids rotation, test hooks) plus standaloneOptionDrone.swift— pure code move, verified bit-exact against a golden replay. - Internal: deduplicated the entities'
distance/moveToward/wrapAroundhelpers into a sharedVectorMath.swift, and gathered scattered gameplay magic numbers into aGameplayTuningenum. - Tests: split the 2.3k-line single-file test suite by domain (physics, power-ups/weapons, bosses,
modes, replay determinism, autopilot, scene state) over a shared
GameCoreTestCasebase, and addedAudioSmokeTestscovering the previously untestedSoundManager/MusicPlayersurface (muted, no real audio engine). 101 tests, all green.
- Build: the macOS app-bundle version now comes from the central
VERSIONfile instead of being hard-coded inbuild-app.sh; the bundle build number is derived from the git commit count (monotonic, no more manual bumping). - iOS: new
ios/generate.shsyncsMARKETING_VERSIONfromVERSIONbefore generating the Xcode project — the iOS version had silently drifted to 0.9.0 while macOS was at 0.14.0. - CI: added a GitHub Actions workflow that runs the full test suite (
swift test) on every push and pull request (tests are headless and deterministic, so no extra setup is needed). - Internal: extracted high-score persistence (
HighScoreStore) and the on-disk replay archive (ReplayArchive) out ofGameSceneinto their own types — no behavior change, all 97 tests pass.
- iOS: the demo / attract mode now runs on the mobile build too — after 30 s idle (or via a new DEMO button on the title screen) an autopilot plays a full game; a touch, or the on-screen ESC, hands control back. A "> DEMO — " marker shows while it plays.
- iOS: redesigned in-game HUD to free up the playfield — smaller score, high score hidden during play, and level / time / demo shown as one centered line just below ESC (same size, three colors).
- iOS: the on-screen touch controls are dimmer and thinner in-game (visible for orientation, but out of the way), and are hidden entirely during a demo — only ESC stays so a viewer can stop it.
- iOS: the glossary now opens already scrolled in so its content is visible immediately, and the settings screen no longer shows the redundant control hint.
- Fix: after watching a demo, starting a game no longer leaves an autopilot movement key "stuck" (the ship kept rotating on its own) — held keys are now cleared on every fresh game and demo abort.
- Demo / attract mode on the title screen: after 30 s of no input (or on pressing D), an autopilot plays a full game on its own. When it dies it does not enter the high-score list, but the high-score screen is shown for 10 s, then the title screen for 15 s, then the next demo — looping. Any key hands control back to a human.
- Four autopilot personas that play with distinct styles (cautious ↔ reckless, skilled ↔ sloppy) and each start at a fitting level: Ace (L4, the expert — reaches level 10 and can survive the full ~10 minutes), Cowboy (L6, offensive but clean), Rookie (L5, cautious but sloppy) and Kamikaze (L7, reckless — dies youngest but spectacularly). The autopilot uses a potential-field navigator (threats repel, shooters/power-ups weakly attract, look-ahead dodging) that keeps the ship weaving through the gaps and firing along its path; it also collects shields/extra-lives.
- Replay fix: a recording now stores the scene size it was played at. The simulation depends on the
scene size (spawn positions, wrap bounds, enemy entry), so replaying or rendering at a different
size made the run drift completely. The headless renderer and
--replay-verifynow use the recorded size by default; older recordings without the field assume the macOS window default of 1024×768 (so existing replays render correctly without manual flags). - The GIF renderer can now decouple simulation size from output size (
--sim-scalefor the sim,--scalefor the GIF), so a faithful 1024×768 run can be rendered to a compact GIF. - New CLI
--render-video <file> --out <mp4>: render a whole replay to a real-time h264 video (via AVAssetWriter). For long runs that would be absurdly large as a GIF — scrub it to pick a GIF segment.
- Fixed-timestep simulation: the game loop now advances in fixed steps (1/120 s) driven by a time accumulator, decoupled from the display refresh rate, instead of integrating one variable step per frame. On 120 Hz this is effectively one step per frame as before; on other refresh rates the simulation stays consistent.
- Because every step is the same length, a replay no longer needs the recorded per-frame
dtsequence — it depends only on (seed + inputs). Replay format bumped to v3; older replays (v2, variable timestep) are rejected as incompatible. - The headless GIF renderer drives the simulation one fixed step at a time and picks a capture
stride automatically so the GIF plays in real time (
--stridestill overrides). - Replays are now auto-saved to disk on every game over (not only high-score runs), under
~/Library/Application Support/Exploids/replays, so a good run can be turned into a GIF even if it didn't make the board. New CLI--render-last-replay --out <gif>renders the most recent one. - New CLI
--reset-highscoresclears the saved high-score list (when the board fills with unbeatable scores). - No gameplay-balance changes intended; this is an engine/feel change to be confirmed by playtest.
- Replay fix: the auto-fire setting is now recorded in a replay and restored on playback. Before this, a run played with auto-fire on would not reproduce (the replayed ship barely fired and died early). Replay format bumped to v2; pre-fix replays are rejected as incompatible.
- Replay GIF renderer gained
--from <frame>,--max-frames <n>and--auto-fireoptions, plus a--replay-verifydiagnostic. - Note: faithful replay requires the exact binary that recorded the run — a rebuilt binary can drift (floating-point reproducibility is binary-specific). In-app replays and GIFs from the same installed build are reliable.
- Deterministic replay system: every run is recorded (seed + inputs) and the simulation is now bit-exact reproducible. High-score runs can be watched again in-app — press 1–5 on the title screen to replay an entry; ESC exits.
- Headless GIF rendering: turn a replay into a clean, cursor-free animated GIF from the command
line (
exploids --render-replay <file> --out <gif>, plus--export-replayand--render-demo). - Under the hood: seeded PRNG for all gameplay randomness and a single accumulated game-time clock (no more wall-clock reads in the gameplay path), which also makes the test suite deterministic.
- Pixel font (Press Start 2P) for the EXPLOIDS / GAME OVER headings.
- High-score name entry fix (first responder).
- Reworked object glossary: every power-up listed individually, with a title strip.
- Fixes: extra life with gravity wells, entzerrtes start-screen layout, "#" extra-life cheat (for testing).
- Two selectable game modes: Ancient Asteroids (classic) and Mad Meteoroids (rotating field).
- Four new power-ups: Rear, Compress, Extra Life and Laser beam.
- App icon (ship + flame) and chiptune background music with an M toggle.
- Flatter difficulty curve; asteroids now reliably fly in from the screen edge instead of spawning mid-screen.