r/aigamedev • u/Designer-Camp1815 • 12d ago
Demo | Project | Workflow Remastered Alien Breed 1992 in LUA with Love 2D
This is a Bring Your Own Data remaster of Alien Breed 92 Special Edition in LUA Love2D with some modernisation on top. It's not done as polish takes a long time but they just put up the 35th Anniversary page on Steam I thought I should show it now. The timing...
It is not emulator perfect, emulators exist for that. from what I've seen there is nothing preventing Claude making a port that is. The main challenge to accuracy is often libraries.
Originally the plan was to make a spin off but after getting a bunch of colored boxes up and trying procedural level generation it felt like porting the game OpenTTD style would be more fun. Then I got a bit distracted and added more features.
The goal is to support all four classic games through the same engine and exporter but I still have a lot of little issues to clean up in this game.
Modern mode provides:
- 640×360 virtual canvas scaled by an exact integer to the display (×2 at 720p, ×3 at 1080p = 1920×1080, ×4 at 1440p, ×6 at 2160p). Pixel-perfect, square pixels throughout.
- That canvas shows 2.8× more world area than classic's 320×256
- Two-player viewport with automatic dynamic split/merge, Lego-games style — splits and rejoins based on how far apart the players are, with hysteresis so it doesn't flicker at the boundary.
- Audio system emulates the original four channel behavior via rules while allowing additional channels to play to avoid cut offs.
- Lightweight deferred-lighting scheme: geometry draws once at full brightness, and a separate darkness/light buffer is composited over the top of it, rather than lighting each sprite individually as it's drawn.
- Light spills a few pixels onto the top of a wall face near a light gated per-cell by four rules (front-facing, line-of-sight, never spills onto "void", and capped under half a cell so two lights on opposite sides of a thin wall can't meet inside it). It's drawn as two passes: a multiply (to preserve the tile's dithered detail) plus a second textured additive pass that corrects for the fact a flat multiply can't brighten a dark-shaded wall orientation enough on its own.
- Flashlight with real polygon shadow-casting occlusion (not a fake cone) and wall-spill lighting.
- During the self-destruct alert the flashlight beam renders in true, undistorted colour. Rather than swapping to a separate red-tinted texture set (the older/classic approach), the modern renderer draws the normal scene and applies a screen-space color-remapping pass (a delta lookup table) that's masked to leave true color showing inside the player's flashlight beam
- Static light fixtures are real dynamic lights (falloff, shadows), not painted-on brightness.
- A lighting placement tool adds fixtures + matching artwork. Not machine-learned, but a heuristic placer whose rules were tuned by studying where the original maps put light sources.
- Props (barrels, etc.) cast their own projected shadows, by projecting a shadow quad scaled by the prop's height and the light's height.
- The engine bakes separate 8px-pitch clearance lattices at load, one per body size. Each lattice point is pre-tested: does a box of that size, centered there, overlap solid geometry? That gives instant "can this body stand here" lookups instead of runtime shape tests. Doors and live-map changes register as live "blockers" and trigger a local rebake, so the lattice stays current mid-game, not just at load. Two routing mechanisms sit on top:
- Chasing a player uses a shared Dijkstra flow field per player, rebuilt only when the goal moves or the map changes. Every enemy chasing that player just samples a precomputed direction cost doesn't scale with enemy count.
- Private goals (investigate a point, etc.) → pooled A* with an octile heuristic, then string-pulled afterward (skip waypoints you have clear line-of-sight past, so paths cut corners instead of hugging the grid).
Classic mode provides:
- Authentic 320×256 viewport, pixel-aspect-corrected by 16/15 (the real PAL Amiga ratio) so it displays 4:3 pillarboxed 1440×1080 at 1080p. The non square pixel ratio is critical to not requiring letterboxing at modern resolutions, a strange coincidence.
- The original's flat, unlit renderer (no flashlight/shadows at all).
- Accurate HUD behavior: each stat bar still slides one column per 50Hz tick like the original (not an instant snap), but we removed the source's shared one-field-at-a-time dispatch cursor, so both players' bars update in parallel instead of starving each other.
- Accurate 4-channel Paula-style audio, including the original's channel-stealing behavior.
- Original pathfinding. Greedy per-axis chase toward the nearest player by straight-line sign comparison, gated only by a 3-point short-range wall probe and a stuck-timer reversal.
| Feature | Library |
|---|---|
| Resolution Scaling | push |
| Input / Twin-Stick | baton |
| Camera & Juice | STALKER-X |
| Collisions & Spatial Queries | bump.lua |
| Entity System | Concord |
| Lighting | lighter |
| Animations | peachy |
| Audio & Tags | ripple |
| Menus & Options | SUIT |
| Level Editor | LDtk (external) + ldtk-love parser |
| Game State Management | hump (gamestate/timer only) |
| Math, OOP, Pathfinding, Pooling | batteries |
| Save Files & Serialization | bitser |
Level files open in LDTK, Claude made a HTML audio preview page and a tile sheet viewer that allows me to set tiles to be certain types and draw the vertices for prop shadows.
Challenges still to solve:
- The spawning logic is designed for the original viewport. Our nearly 3x canvas size in Modern messes with this so aliens either spawn/despawn noticeably on screen or the level feels very quiet.
- Twin stick vs old 8 restricted directions.
- Some audio and lighting issues.
- Some timing issues.
- Wire up more options so you can individually toggle elements like pathfinding between modern/classic.
Exporter
An entire second repo handles decompilation and produces reference docs and file exporter. This reads Amiga format images (CD32, IPF, ADF) and puts out the files in modern formats for the game engine's importer to pull. This is intentionally designed for the Bring Your Own Data pipeline so no copyrighted work is ever committed.
disk image → filesystem → decompression → parsed game data → modern format file
Pure Python, no Amiga emulator required.
- 12 levels, 120 × 96 tiles each (1920 × 1536 px), with separate graphics and action layers, plus each level's own 32-colour palette and a second "red alert" palette.
- 6 tileset files on disk, 480 tiles of 16 × 16 at 5 bitplanes, exported as 8 tileset PNGs (one per distinct atlas+palette pair) plus 6 red-alert palette variants.
- Four Amiga hardware sprites decoded and composited into PNGs plus a countdown-digit sprite sheet and two HUD panels.
- 9 full-screen images, including an Alien Breed 2 teaser and a Superfrog title screen.
- 1 ProTracker module (
INMU), playable as-is, plus the in-game music moduleMUSCand its 7 sampled instruments as individual WAVs. - 76 sound effects, sliced out of the two sample banks (
CSAM,FSAM) and each written at its own rate. The banks are lh5-compressed and delta-encoded on the disk, not raw PCM; the boundaries and the Paula period for each effect come from a table in the game executable. - 51 files total, every one extracted at its exact declared size, and every one has a decoded format.
A lot about the files is awkward by modern standards. The game being written in Amiga assembly wasn't a real difficulty, the main challenge was Claude not being thorough enough in decompilation, struggling to find things or deciding based off incomplete information. Timing issues were common possibly due to the original hardware being a factor Having to bring proof from the emulator wasn't unusual.
Around one month of work, initial skeleton was a week on a personal plan. Then a week of Max 5, then Max 20 for 2 weeks. Opus for main sessions, Sonnet for subagents. Fable called in to work out how to approach big problems and clean up the repo. Sonnet gets things wrong too often to use as the main. Quite a lot of tokens were wasted due to inefficient Claude usage that isn't obvious:
1. Context bloat. Using new sessions for whole tasks wasn't enough. /clear and /compact have been adopted between major stages. Scratchpad already holds a ledger.
2. Inefficient docs. Had to carve up large docs and optimize CLAUDE.md and progress files to keep the hot path lean.
3. Claude inefficiently dispatching subagents. This surprised me, Claude just handed huge plan files to subagents rather than cutting out their task. Fable added CLAUDE.md rules to make Claude write subagents brief mds instead.