A modern, WebGL-enhanced reconstruction of the classic Lemmings game using the experimental HTML-in-Canvas API (layoutsubtree, drawElementImage, onpaint).
In this implementation, each Lemming is a real <div> child of a <canvas> element. This creates a DOM-based Entity-Component-System (ECS) where entities are rendered directly into the canvas every frame while retaining native mouse hit-testing and accessibility support on the drawn pixels.
โถ Play now โ requires Chrome 148 to 154 with chrome://flags/#canvas-draw-element enabled (see Runtime Requirement below).
๐ Special thank you to
- ccexplore & rt for sharing their knowledge on Lemmings Archive
- Mindless
- Lemmings Forum for keeping the game alive
- crisp for the OG dhtml Lemmings
- trufae for preserving DHTML Lemmings on Github
- the Lemmings Level Database where I found the 2-player levels
- oklemenz for LemmingsJS
- amiga protracker module player for web audio api (c) 2012-2013 firehawk/tda
Motivation
I wanted to level up my agent coding skills and how to better learn than to practise? After recently reading about HTML-in-canvas I thought this might be something interesting to target and since I love playing Lemmings, DHTML Lemmings came to mind.
I really didn't expect to release something since Lemmings in HTML & canvas isn't exactly ground breaking but I soon discovered that there are still treasures buried in this old game.
There was no reconstruction of the EGA, CGA or Tandy version and no 2-player mode (which didn't exist in the DOS version)
โ ๏ธ Runtime Requirement
The experimental HTML-in-Canvas API is in origin trial and needs Chrome 148 to 154. It will not run on standard browsers, Safari, or Firefox (which will display an unsupported banner).
To run this project:
- Use Chrome 148 to 154 (or a Chromium build supporting the flag).
- Navigate to
chrome://flags/#canvas-draw-element. - Set the flag to Enabled and restart the browser.
๐ Running the Project
This project is fully client-side, bundled/served via Vite:
Open the printed http://localhost:5173 URL in Chrome with the flag enabled.
For a production build (a dist/ folder ready to serve statically, e.g. on GitHub Pages):
npm run build
npm run preview # serve dist/ locally to sanity-check the build๐ฎ Features
1. Single Player Mode
- 346 Playable Levels: Every rating from the original DOS Lemmings, Oh No! More Lemmings, and the Holiday/Xmas Lemmings packs (
Fun/Tricky/Taxing/Mayhem,Crazy/Wild/Wicked/Havoc/Tame,Flurry/Blizzard'93 & '94,Frost,Hail,Xmas'91 & '92) plus a 20-level Amiga2-playerpack. - Live
.DATDecoding: Terrain, placed objects, steel areas, and level metadata for every rating, including2-player, are decoded directly from the original DOS/Amiga binary files (gamedata/<pack>/LEVEL<n>.DAT,gamedata/Amiga-Two-Player/Level NN.lvl, etc.) in the browser at play time โ no offline conversion step. Seedocs/gamedata.md. - 8 Original Skills: Full state machine implementations for Climber, Floater, Bomber (Exploder), Blocker, Builder, Basher, Miner, and Digger.
- Classic UI & Controls: Interactive skill toolbar with skill counters, release-rate controllers (+/-), fast-forward (4x speed), pause, and a "nuke" button.
- EGA / CGA / Tandy Video Modes: A pause-menu "Video Mode" setting recolors terrain, objects, and the lemming sprites themselves to match real EGA (per-level palette), CGA (native 2bpp,
lemmingspack), or Tandy (fixed hardware palette,lemmings/oh-no-more-lemmings) hardware output โ VGA stays the default fixed palette. CGA "special" (VGASPEC-backed) levels are decoded via their own nativeCGASPEC<n>.DATart rather than re-quantizing VGA art. - 4 Music Engines + Real DOS Sound Effects: A "Music Engine" setting switches between the Amiga ProTracker rotation, real per-pack AdLib (OPL2) music decoded from
ADLIB.DAT, pre-rendered MP3 tracks, and real per-pack Tandy 3-voice PSG music decoded fromTANDYSND.DAT.
2. 2-Player Lockstep Multiplayer
- P2P Connection: Direct peer-to-peer multiplayer using WebRTC data channels via PeerJS.
- Lockstep Synchronization: A fully synchronized local simulation. Instead of sharing state variables for hundreds of entities, peers exchange only tick-stamped player commands.
- Input Delay Buffer: Commands are queued and executed with a fixed buffer (
INPUT_DELAY_TICKS = 6ticks, ~480ms) to hide network latency and keep the simulations perfectly aligned.
3. Level Editor
- Live Terrain/Object Palette: Stamp and Objects tools are decoded straight from the original
GROUND<n>O.DAT/VGAGR<n>.DATgraphic-style files โ no baked tile catalog. Placed pieces are real interactive HTML-in-Canvas entities (native click/drag hit-testing viadrawElementImage), the same ECS pattern used for lemmings and decorations in real gameplay. - Grid Painting: Interactive canvas interface to paint terrain semantic values directly (Air, Soft, Soft Right, Soft Left, Steel, Water, Fire, Exit, Slicer).
- Smart Brushes: An Exit tool that paints tall columns to ensure walking Lemmings collide with the exit trigger (sampled at mid-body, y+16).
- Level Configurations: Configure starting parameters such as skill availability counts, release rates, saved percentage targets, time limits, and music.
- Local Storage: Saves, loads, and manages draft playtests and custom levels in
localStorageunderlemmings.customLevels.v1. - Export/Import Real DOS Levels: "Export Level Packโฆ"/"Import Levelโฆ" convert custom levels to/from real, LZ-compressed DOS
LEVEL*.DATfiles, playable in an actual DOSBox install. Seedocs/level-export-import.md.
4. Custom Graphics Pack Editor
graphicspack.html: A standalone tool to author your own DOS-compatible ground/object graphics sets โ import/quantize your own art or clone an existing pack, hand-edit on a palette-constrained pixel canvas (pencil/eraser/bucket/eyedropper), and manage object animation frames/trigger boxes.- Real Export: Saves for use with this project's own editor/game, or exports as real
GROUND<n>O.DAT/VGAGR<n>.DATfiles for a genuine DOSBox pack folder. Seedocs/graphics-pack-editor.md.
5. Deterministic Replay, Rewind & Cheats
- Input Logging: Every simulation-affecting input (skill selection, release rate adjustments, nuking) is recorded along with the simulation tick it occurred on.
- URL-based Sharing: Replay records are compressed, encoded, and appended to the URL (
?replay=...). The result page offers a "Copy replay link" button to share playthroughs. - Rewind: A scrub bar (next to Pause/Fast-Forward) jumps the simulation to an earlier tick by restoring a snapshot and headlessly fast-forwarding the command log back to it โ non-destructive while watching a replay, a true rewind/retry while playing live. See
docs/rewind.md. - Debugging Cheats: Appending
?cheat=1to the URL loads the level with 99 of every skill available for sandbox testing.
6. WebGL Shaders & Visual FX
- CRT Simulation: CRT filter overlay including scanlines and vignette shading.
- GL Shimmer & Lighting: WebGL fragment shaders (
src/webgl-effects.js,src/lighting.js) that sample the rendered canvas to create shimmery liquid surfaces and lighting calculations. - Screen FX: Skill-triggered WebGL screen distortions โ bomber shockwave/shake, basher chromatic aberration, miner jitter, digger squish, blocker shield shimmer, floater turbulence, builder holographic mesh โ plus a secret Shift+D tuning popover for live-adjusting effect parameters. See
docs/screen-fx.md. - Hover Highlight: The Lemming currently hovered by the mouse pointer while a skill is armed is recolored with the same red tint 2-player mode uses for team 1.
- Particle System: Exploding sparks and liquid particles rendered directly onto the canvas view. See
docs/particle-system.md.
7. Headless Level Solver
solver.html: Pick any level and run a headless, best-effort Monte Carlo Tree Search (src/solver.js) for a skill-assignment sequence that solves it โ no Canary/flag needed to search, only to watch the resulting replay. Seedocs/solver.md.
8. Amiga ProTracker Audio Engine
- Chiptune Synthesis: Custom Amiga audio tracker player synthesized in JavaScript (
src/AmigaAudioEngine.js) to play retro music formats. - Original SFX: Reuses digital audio files (
upgrade/lemmings/sounds/*.ogg) for classic sound cues ("Let's Go!", "Oh No!", splats, explosions, and tools).
๐ Directory Structure
โโโ src/ # Modern ES Module codebase (JS + game.css) โ no build-time deps beyond Vite
โโโ assets/ # First-party static assets the live game fetches directly: UI chrome
โ # (skill toolbar/control-bar icon strips, background.png), the menu's
โ # handwriting-style font, and the editor's custom cursor SVGs
โโโ gamedata/ # Original DOS/Amiga game packs (LEVEL*.DAT, GROUND*O.DAT, VGAGR*.DAT,
โ # MAIN.DAT, VGASPEC*.DAT, ADLIB.DAT, TANDYSND.DAT, ...), fully committed
โ # to this repo โ live-decoded at play time/edit time for every rating,
โ # including gamedata/Amiga-Two-Player/ (2-player's raw *.lvl source).
โ # See docs/gamedata.md and docs/2-player-dos-assets.md.
โโโ levels/ # Shared object/decoration art (levels/{gfx,ani,doors,traps}/*.gif) +
โ # levels/music/*.ogg (unused leftover, see CLAUDE.md)
โโโ logos/ # Result-screen rating badge art (complete/failed/holiday93/holiday94/...)
โโโ upgrade/<pack>/ # Optional, non-vanilla polish grouped by gamedata pack (see CLAUDE.md):
โ # backgrounds/*.webp, lemmings/sounds/*.ogg (shared SFX), <pack>/music/
โโโ graphics_tools/ # Node scripts used to reverse-engineer/verify the binary formats above
โ # (render_*.js/.mjs previews, parity_check_phase*.mjs, lem_decompress.js)
โโโ vite-plugins/ # Custom Vite plugins (gamedataDirListing.js โ see vite.config.js)
โโโ scripts/ # One-off maintenance shell scripts (e.g. convert-backgrounds-to-webp.sh)
โโโ docs/ # Binary format specs + architecture notes โ see "Documentation Reference"
โโโ index.html # Main menu, level selector, and Level Editor mount
โโโ game.html # Main gameplay viewport and multiplayer lobby mount
โโโ *.html # Standalone dev tools in the root โ see "Developer Tools" below
๐ ๏ธ Codebase Architecture
The modern ported game is organized as a collection of ES modules located under the src/ folder โ no vendored third-party engine code, everything below is native:
Core Application
index.html/src/menuMain.js: Initial entry page. Sets up the main menu, difficulty level selector, custom level manager, and initializes the Level Editor.game.html/src/gameMain.js: Handles the main gameplay view, runs the fixed 80ms simulation step loop (TICK_SECONDS = 0.08), and coordinates WebRTC lobby connections.src/entities.js: Contains the Entity-Component-System (ECS) engine. Implements the 8 skill state machines, coordinate systems, step-up (โค12px) / step-down (โค8px) walking geometry, and coordinate checks. The state machine itself (stepLemming/applyJob) is DOM-free โ DOM writes and sound playback are separated out (syncEntityVisuals,stepLemming's returnedsoundsarray) so it's callable headlessly, e.g. for the rewind feature's fast-forward. Seedocs/rewind.md.src/terrain.js: Manages the level collision matrix (mapped onto a grid of 0-9 values). Manages drawing the offscreen canvas where Digger, Basher, and Miner actions physically carve paths or Builders place stairs.snapshot()/restore()bundle both for the rewind feature.src/leveldata.js:loadLevelDef()is the single entry pointgameMain.jscalls to load a level for every rating; it dispatches togamedataLevelLoader.js, which live-decodes all of them (including2-player). Also owns Run-Length Encoding (RLE) grid compression/decompression, load-bearing for the level editor's custom-level storage format.src/gamedataLevelLoader.js: Live-decodes a real level directly fromgamedata/<pack>/LEVEL<n>.DAT(orDLVEL<n>.DATfor Oh No! More Lemmings) for the 6 DOS packs, and fromgamedata/Amiga-Two-Player/Level NN.lvlfor2-playerโ terrain-piece/object/steel-area placement and metadata โ using nativesrc/levelReader.js/src/groundReader.js/src/vgaspecReader.js/src/oddTableReader.js, and bakes the result into one sharedlevelDefshape the rest of the engine needs no awareness of which pipeline produced. Seedocs/gamedata.md/docs/2-player-dos-assets.md.src/gamedataPacks.js: Shared per-pack table (filename casing, level-file prefix, which graphic styles/ODDTABLE/VGASPEC a pack ships, and therating โ {pack, config.json group}mapping) used by every live-decode module.src/groundCatalog.js/src/groundReader.js: Live-decode a graphic style's terrain-piece bitmaps and object animation strips fromGROUND<n>O.DAT/VGAGR<n>.DATโ shared by the level editor's palette, the graphics pack editor, andgamedataLevelLoader.js.src/vgaspecReader.js/src/cgaspecReader.js: DecodeVGASPEC<n>.DAT/CGASPEC<n>.DAT-backed "special" levels โ a fully custom background image instead of tiled ground pieces โ for VGA/EGA and CGA video modes respectively.src/lemmingSpriteCatalog.js: Recolors lemming sprites for the EGA/CGA/Tandy Video Mode settings from real per-level/hardware palettes (VGA stays the fixed default palette).src/decorations.js: Manages non-terrain interactive and animated game decorations (e.g., animated entrance hatches, exits, and traps) as real HTML-in-Canvas ECS entities. Decompresses animated GIF frames via WebCodecs (ImageDecoder) into custom offscreen sprite strips to guarantee synchronization with the simulation ticks.
Multiplayer & Controls
src/network.js: Interfaces PeerJS/WebRTC. Manages connection heartbeats, packet routing, and connection watchdogs.src/editor.js/src/editorHistory.js: The painting loop, palette options, mouse brushes, undo/redo history, and validation rules for the custom Level Editor. Placed pieces/objects/steel areas are real HTML-in-Canvas ECS entities, not flat canvas drawing.src/editorBake.js/src/levelBakeGeometry.js: Turn the editor's placed-piece lists (or a live-decoded gamedata level) into the flat terrain bitmap + collision grid +levelImages/trapsreal gameplay consumes.src/datLevelEncoder.js/src/exportDat.js/src/importDat.js: Convert custom levels to/from real, LZ-compressed DOS LemmingsLEVEL*.DATfiles โ "Export Level Packโฆ"/"Import Levelโฆ" in the My Levels screen. Seedocs/level-export-import.md.graphicspack.html/src/graphicsPackMain.js/src/groundSetFormat.js/src/customGraphicsPacks.js: Standalone custom graphics pack (ground/object set) authoring tool โ paint/import/clone art, then either save it for use in this project's own editor/game (IndexedDB) or export real DOS-compatibleGROUND<n>O.DAT/VGAGR<n>.DATfiles. Seedocs/graphics-pack-editor.md.src/replay.js: Handles compressing/decompressing user inputs into URL parameters.src/solver.js: Headless, best-effort level solver (solver.html/src/solverMain.js) โ re-simulates a level off-DOM using an event-driven Monte Carlo Tree Search (MCTS) that branches at genuine decision points (spawn, wall, gap, trap) and hands back a replay command log for the best attempt found. Seedocs/solver.md.src/rewind.js/src/worldSnapshot.js: Reverse playback / rewind โ jump the live simulation or a loaded replay to an earlier tick by restoring a one-time snapshot and headlessly fast-forwarding the recorded command log back to the target tick. A "Rewind" scrub bar in the skill toolbar; non-destructive while watching a replay, a true rewind/retry (discards what was going to happen next) during live play. Seedocs/rewind.md.src/ui.js/src/debugPanel.js/src/effectsTuning.js: Toolbar renderers, keyboard shortcuts (1-8 for skills, Escape to pause, Arrow keys for rate/volume), the pause menu's full settings surface (CRT/Lighting/Particles/Screen FX/Background, Music Engine, Video Mode), and a secret Shift+D popover for live-tuning Screen FX shader parameters.src/settings.js: WrapslocalStorageto persist volume levels, sound/music toggles, particle settings, and shader states.src/progress.js/src/progressMp.js: Level unlock trackers inlocalStorage.
Audio & Visuals
src/sound.js/src/musicManager.js: Controls HTML5 audio playback and connects the tracker/hardware audio engines.musicManager.jsswitches live between 4 Music Engines: the shared Amiga ProTracker.modrotation, real per-pack AdLib (OPL2) music decoded fromADLIB.DAT, pre-rendered per-pack.mp3tracks, and real per-pack Tandy 3-voice PSG music decoded fromTANDYSND.DAT. Seedocs/music-engine-naming.md.src/AmigaAudioEngine.js/src/gamedataMusicPlayer.js/src/oplAudioEngine.js/src/tandyAudioEngine.js: Low-level audio synthesis โ the Amiga ProTracker player, the AdLib/OPL2 driver (also the source ofsfx-explorer.html's sound effects,src/dosSfxMapping.js), and a from-scratch Tandy 1000 PSG driver port (src/tandySfxMapping.js; seedocs/SOUND_FORMAT.md/docs/SOUND_FORMAT_ADLIB.md).src/webgl-effects.js: WebGL-based visual shader processors (shimmers, reflections, CRT scanlines, Screen FX distortions), tuned by a family ofsrc/*Tuning.jsfiles (crtTuning.js,waterTuning.js,lavaTuning.js,foamTuning.js,sandTuning.js,moltenTuning.js,iceFloeTuning.js,throwerTuning.js,lightingTuning.js,particlesTuning.js) backing the Shift+D debug popover above.src/lighting.js: WebGL effects for active entities and shadows.src/particles.js/src/fireworks.js: Simulation of explosion fragments and fireworks. Seedocs/particle-system.md.src/colorPalette.js/src/imageQuantizer.js/src/planarImage.js/src/binaryReader.js/src/datContainer.js/src/datContainerCompressor.js/src/mainDatCatalog.js: Shared low-level building blocks (palette handling, image quantization for custom-art import, planar bitplane decode, binary stream reading, DOS.DATcontainer packing/unpacking,MAIN.DATasset catalog) used across the live-decode and dev-tool code paths.
๐ ๏ธ Developer Tools & Utilities
The project includes several standalone pages (accessible in the root directory, all wired into vite.config.js's build) designed to examine, author, and process original Lemmings/Oh No! More Lemmings game assets:
explorer.html(DOS Lemmings Asset Explorer): A web-based inspector to load and visualize graphics, palettes, levels, and animations decoded live from the original MS-DOS/Amiga.DAT/.lvlfiles. Seedocs/explorer.md.converter.html(GFX & Level Converter): No longer a required build step โ real gameplay and the level editor both live-decode terrain/objects directly now for every rating. Kept as a standalone tool to export a portable snapshot of a level. Seedocs/converter.md.graphicspack.html(Graphics Pack Editor): Author custom DOS-compatible ground/object graphics sets โ paint on a palette-constrained pixel canvas, manage object animation frames/trigger boxes, save for use in this project or export realGROUND<n>O.DAT/VGAGR<n>.DATfiles. Seedocs/graphics-pack-editor.md.sprite-editor.html(Sprite Editor): A specialized paint program to modify pixel sheets, set anchor offsets, and test individual frame sequences. Seedocs/sprite-editor.md.gif2sprite.html(GIF to Spritesheet Converter): Converts standard multi-frame animated GIFs into linear horizontal sprite strips suitable for standard CSS step-animations. Seedocs/gif2sprite.md.solver.html(Level Solver): Pick a level and it runs a headless, best-effort Monte Carlo Tree Search (src/solver.js) for a skill-assignment sequence that solves it, then offers a "Watch replay" link intogame.html. No Canary/flag needed to search โ only to watch the result play out. Seedocs/solver.md.assembly.html(Exploded Assembly Viewer): Loads a real DOS level straight fromgamedata/and flies every terrain/object piece into place โ a dev viewer sharing its live-decode core withgame.html's between-levels piece-flying screen. Seedocs/assembly.md.sfx-explorer.html(DOS Sound Effect Explorer): Plays each pack's real, live-decoded AdLib/Tandy sound effects side-by-side with the currentupgrade/lemmings/sounds/*.oggset, for building/confirming a nameโindex mapping by ear.missing.html(Missing Upgrades): An asset-coverage audit โ checks every optionalupgrade/asset (backgrounds/sounds/music) against what's actually on disk and reports what's still missing, with a directgame.htmllink for every catalog level (including no-title ones, dimmed).graphics_tools/: Node scripts (not browser pages) used to reverse-engineer and cross-verify the binary formats above against real DOS asset dumps โ per-formatrender_*.js/.mjspreviews andparity_check_phase*.mjsregression checks;graphics_tools/sound_dispatch_explorer.htmlis one further, standaloneCGALEMMI.EXEsound-dispatch research artifact.
๐ Documentation Reference
The docs/ directory has both architecture notes and binary format specs:
docs/gamedata.md: Whichgamedata/packs back which ratings, filename-casing gotchas, and how live decoding works end to end.docs/2-player-dos-assets.md: How2-playercame to fully live-decode from its raw Amiga.lvlsource, reusing thelemmingspack's own (byte-identical) ground/object art.docs/gif-to-gamedata-migration.md: The history of retiring pre-rendered.gif/converter-script level art in favor of livegamedata/decoding for every rating.docs/editor.md: How the level editor's live-decode palette and ECS placed pieces work.docs/graphics-pack-editor.md: The custom graphics-pack authoring tool's format/verification writeup, including two real decoder bugs found and fixed along the way.docs/level-export-import.md: Exporting custom levels to real, LZ-compressed DOSLEVEL*.DATfiles and importing them back โ byte-level field mapping, unit-conversion gotchas, and bugs found/fixed along the way.docs/rewind.md: How the lemming state machine became headless-callable, the terrain/world snapshot primitives, and the reverse-playback/rewind feature built on top โ live vs. replay semantics, the benchmark behind skipping a keyframe cache, and how it was verified.docs/music-engine-naming.md: Theupgrade/<pack>/music/track-NN.{mod,mp3}naming convention the Amiga/MP3 music engines expect, and each pack's real track count.docs/solver.md: How the headless level solver's MCTS search/rollout policy works, its known limitations, and how it was verified.docs/screen-fx.md: The skill-triggered WebGL Screen FX distortions โ design choices and the debug-popover tuning surface.docs/particle-system.md: Current state of the environmental particle system and its related shader effects.docs/assembly.md: The exploded-assembly dev viewer and the game's shared level-prepare/piece-flying screen.docs/explorer.md: What the DOS Lemmings Asset Explorer inspects and how.docs/sprite-editor.md: How the Sprite Editor's crop/slice/export pipeline works.docs/converter.md: Whatconverter.htmlstill does now that it's optional.docs/webmcp-tools.md: The WebMCP tool surfaces registered by the graphics pack editor, level editor, and game, letting an agent author/play content with no more capability than a human clicking through the same page.docs/messages.md: Post-level result-message logic and text mappings ported from the original engine.docs/GRAPHICS_FORMAT.md: Reverse-engineered DOS Lemmings graphics format reference (ground/object/VGASPEC/CGASPEC bitmaps, palettes).docs/SOUND_FORMAT.md: Reverse-engineered Tandy 1000TANDYSND.DATsound/music driver format.docs/SOUND_FORMAT_ADLIB.md: Reverse-engineered AdLib/OPL2ADLIB.DATsound/music driver format.