Releases: avcode-exe/Tower-Defense
Release list
v1.7.3 - Emergency Updater Fix
v1.7.3 — Emergency Updater Fix
Release Date: 2026-07-30
🚨 Critical Bug Fixes — Updater Broken in v1.7.1/v1.7.2
This is an emergency release that fixes three critical bugs in the auto-update system that prevented users from updating the application. Without this fix, clicking "Restart & Install" after downloading an update would fail.
Bug 1: "Please check update first" (Download Failure)
autoUpdater.downloadUpdate() requires autoUpdater.checkForUpdates() to have been called first to populate internal state. Since the app uses a custom checkForUpdatesDirect() that queries the GitHub Atom feed directly, autoUpdater.checkForUpdates() was never called, and autoUpdater.updateInfoAndProvider was null. Calling autoUpdater.downloadUpdate() would throw:
Error: Please check update first
Fix: Replaced autoUpdater.downloadUpdate() with a manual download implementation using fetch() + Web Streams Reader API. Download progress is tracked via reader.read() loop and reported to the renderer via sendStatus('downloading', { percent }).
Bug 2: 404 Not Found on Download URL
The latest.yml from GitHub releases referenced the asset as Tower-Defense-Setup-1.7.2.exe (hyphens), but the actual GitHub release asset name was Tower.Defense.Setup-1.7.2.exe (dots). This mismatch caused a 404 when attempting to download the installer.
Example from logs:
- GitHub API asset names:
["latest.yml","Tower.Defense.Setup.1.7.2.exe","Tower.Defense.Setup.1.7.2.exe.blockmap"] latest.ymlreferences:Tower-Defense-Setup-1.7.2.exe(hyphens — ❌ 404)
Fix: Added fixAssetNamesInInfo() function that queries the GitHub API (https://api.github.com/repos/avcode-exe/Tower-Defense/releases/tags/vX.Y.Z) at download time to discover the actual asset name. The function replaces the hyphenated name in the update info with the correct dotted name and constructs the full download URL.
Bug 3: "No update filepath provided, can't quit and install" (Install Failure)
autoUpdater.quitAndInstall() failed because the download was performed manually (not via autoUpdater.downloadUpdate()), so autoUpdater had no knowledge of the downloaded file path. The error was:
Error: No update filepath provided, can't quit and install
Fix: Rewrote the restart-to-update IPC handler to:
- Check if
_downloadedFilePathis set (from the manual download) - Launch the NSIS installer directly via
spawn()with{ detached: true, shell: true, stdio: 'ignore' }and no/Ssilent flag — so the user can see the installer UI and interact with it - Wait 500ms for the installer to start, then call
app.quit()to allow the installer to close the app process for file replacement - Fall back to
autoUpdater.quitAndInstall()if_downloadedFilePathis not set (old electron-updater path)
🛠️ Other Improvements
- Comprehensive logging: Every step of the update flow (check → download → install) is now logged to both the main process console and relayed to the renderer's DevTools (F12) console via a dedicated
update-debug-logIPC channel. Logs appear as[update-main]in the renderer console. - Installer visibility: The NSIS installer now runs with a visible UI (no
/Ssilent flag), so users can see installation progress and interact with the standard install dialogs.
📦 Download
- Windows x64: Tower Defense Setup 1.7.3.exe
- Blockmap: Tower Defense Setup 1.7.3.exe.blockmap
🧪 Testing
- 1,938 tests passed across 50 test files
- ESLint: 0 errors, 0 warnings
- Prettier: All files pass format check
🔧 How to Update
- If you are on v1.7.1 or v1.7.2: The update will be automatically detected on startup (if "Check on startup" is enabled in Settings → Update). Click Download, then click Restart & Install. The NSIS installer will appear — follow the prompts to complete the installation.
- Alternatively, download the installer manually from the links above and run it.
⚠️ Manual Installation Required for v1.7.1/v1.7.2 Users
If you are on v1.7.1 or v1.7.2 and the auto-update does not work (due to the bugs fixed in this release), please download the installer manually and run it to update to v1.7.3. After this update, the auto-update system will work correctly for future releases.
Full Changelog
What's Changed
electron-main.js (major changes):
- Added
import { spawn } from 'child_process'at module top level - Added
_downloadedFilePathmodule-level variable to track manually downloaded installer path - Rewrote
downloadUpdateSafely()as async with manualfetch()+ Web Streams Reader download (replacingautoUpdater.downloadUpdate()) - Added
fixAssetNamesInInfo()function to query GitHub API and resolve the actual asset name with dots - Rewrote
restart-to-updateIPC handler to launch the installer directly viaspawn()with visible UI and 500ms delay beforeapp.quit() - Added comprehensive
logUpdate()calls in all IPC handlers,checkForUpdatesDirect(),sendStatus(),shouldAnnounceToUser(), and allautoUpdater.on()event listeners - Added
UPDATE_DEBUGflag andlogUpdate()helper function
src/main.js:
- Added
onUpdateDebugLogrelay listener that receives'update-debug-log'IPC events from electron-main.js and logs them as[update-main]in the renderer DevTools console
preload.cjs:
- Added
onUpdateDebugLogfunction that exposes the'update-debug-log'IPC channel to the renderer via contextBridge
src/updateManager.js:
- Added
console.log('[update] renderer: ...')calls incheck(),_onStatus(), anddownload()for all phases: checking, available, downloading, downloaded, error
src/game.js:
- Version string updated to
v1.7.3
src/gamePersistence.js:
CURRENT_VERSIONupdated to'1.7.3'
CHANGELOG.md:
- Added
[1.7.3]entry documenting all three bug fixes and the version bump
PLAN.md:
- Updated "Current State" version to v1.7.3
- Updated version table with v1.7.3 emergency entry
- Shifted future plans: v1.7.3 Audio & Visual → v1.7.4, v1.7.4 Performance → v1.7.5
- Updated dependency ranges and release counts
Tests (6 files updated):
tests/electronMain.test.js: Addedchild_processmock, updated to async fordownloadUpdateSafely, addedonUpdateDebugLogto mock electron, updated all version references to 1.7.3tests/helpers.js: Updatedgame.appVersionandgetVersionmock to 1.7.3tests/main.test.js: UpdatedgetAnnouncedVersion,getVersionmock, text content assertions, and describe labels to 1.7.3tests/persistence.test.js: Updated save metadata version,cmpassertions to 1.7.3tests/preload.test.js: UpdatedmockInvokemock and expectations to 1.7.3tests/electronMain.test.js: UpdatedresolveDownloadTagandparseUpdateInfomocks to 1.7.3
package.json / package-lock.json:
- Version bumped to 1.7.3
v1.7.2 — Pause Menu, Title Screen, Accessibility, GPU Fix, Settings UX
v1.7.2 — Pause Menu, Title Screen, Accessibility, GPU Fix, Settings UX
🎉 New Features
Pause Menu — DOM-based modal overlay
Replaced the canvas-dim pause overlay with a full DOM modal with improved UX:
- Three buttons: Resume, Restart (with confirmation dialog), and Save Game
- Focus trap: Tab/Shift+Tab cycles within pause menu buttons only (prevents tabging to background elements like the quick access bar)
- Backdrop click does NOT resume: Player must click Resume or press Space/ESC
- Canvas
aria-hidden: When the pause menu is open, the game canvas receivesaria-hidden="true"so screen readers focus on the modal dialog; removed when closed - Prefers-reduced-motion: Respects OS-level reduced motion setting; disables all fade-in/fade-out animations when enabled
Title Screen — Canvas background with click-anywhere start
A polished first-impression title screen:
- Animated gradient background with twinkling particles
- "Tower Defense" title text with GPU-accelerated gradient caching (cached across frames)
- Click anywhere on the canvas or press SPACE to start — no more hunting for a specific button
- DOM overlay with New Game, Load Game, Settings, and About buttons
- Load Game is automatically disabled when no save slots exist
- Particle quality auto-throttles on the title screen (same
_checkFrameBudget()mechanism as in-game) - Canvas
onMouseDown()now handles TITLE state: any click starts the game
Quick Access Bar — Hidden during title screen
- The bottom bar (Monsters, Controls, DEV, Settings, Notifications, About) is hidden during the title screen via a
.bar-hiddenCSS class (display: none !important) - Shown when the player starts the game (click anywhere, press SPACE, or load a save)
- Hidden again when returning to the title screen
Settings Popup Improvements
- Save/Cancel buttons fixed at bottom-right: Moved outside the tab content container so they remain in the same position regardless of which tab is active. The sidebar and tab content are now in a flex row, with actions below.
- Simplified Audio Settings: Reduced to a single Master volume slider with live percentage display. Removed SFX, Ambient, and UI sliders and all mute checkboxes for a cleaner, less cluttered settings panel.
- Dynamic popup width: Popups use
width: max-contentto resize to fit content rather than using fixedmin-width/max-widthconstraints. Min-widths adjusted to smaller values (180–300px range). - Single-line content: Added
white-space: nowrapto.game-panelso all popup content renders on single lines. Addedoverflow-x: autoto.bar-popupfor horizontal scrolling when content exceeds viewport width.
🐛 Bug Fixes
- Space key in TITLE state: Previously called
togglePause()which silently did nothing (state was TITLE, not WAVE_ACTIVE). Now correctly callsstartNewGame(). - onMouseDown in TITLE state: Previously returned early for both DEFEAT and TITLE states, ignoring all clicks. Now starts the game on any click.
- GPU StagingBuffer error: On Linux without a GPU device (
/dev/dri) or display server (DISPLAY), GPU rasterization is now proactively disabled viaapp.commandLine.appendSwitch()beforeapp.whenReady(), preventingcc/raster/one_copy_raster_buffer_provider.cc:282: Creation of StagingBuffer's SharedImage failederrors in headless/VM/container environments. Added switches:--disable-gpu,--disable-gpu-sandbox,--disable-software-rasterizer,--disable-gpu-rasterization,--disable-accelerated-video-decode. - Game loop stability: Reset
_lastSaveWaveinrestart()so auto-save debounce doesn't skip waves after game reset. Clear_pendingAttackon ATTACKING→MOVING transitions to prevent stale attack queue entries. Guard againstundefined m.leakin_stepMonsters. Restore_onProjectileImpactcallback inrestore(). Replace sparseArray(N)withArray(N).fill(null)for_monsterTileIndex. - Version utilities:
isPrereleaseregex updated to match all common pre-release formats (-beta,-alpha,-rc,-pre).parseVersionhandles 4-segment versions. - Healer scan throttling: Healer
_tryHealAlliesnow throttles O(n) target scans to every 0.2s (CONFIG.HEAL_SCAN_INTERVAL) instead of every frame.
🔒 Security
- 0 vulnerabilities (down from 15 at v1.7.1; was 15 high/critical)
electron-updaterupdated to^6.8.9— fixes builder-util-runtime credential leak (GHSA-p2f4-r6v6-j797)brace-expansionoverride to^5.0.8— fixes DoS via unbounded expansion (GHSA-mh99-v99m-4gvg)- Declared transitive dependencies (
semver,builder-util-runtime,js-yaml) explicitly in package.json - Added
engines.node >= 20requirement
🧪 Testing
- 1,938 tests across 50 files (up from 1,885 at v1.7.1; +53 new tests)
- New file:
tests/pauseMenu.test.js(13 tests) — pause menu buttons, title screen, settings sub-modal, quit-to-title - New tests in
tests/main.test.js: 10 tests covering showPauseMenu aria-hidden, hidePauseMenu aria-hidden removal, pause menu button focusability, backdrop click non-resume, focus trap Tab/Shift+Tab cycling, title screen button aria-labels, title screen button clickability, title screen role=dialog and aria-modal, pause menu role=dialog and aria-modal - New tests in
tests/game.test.js: _checkFrameBudget called in TITLE state, title screen gradient caching, particle pre-computation - Pause menu integration: _showPauseMenu/_hidePauseMenu window calls, ESC-when-PAUSED, gameRuntime pause/resume/defeat integration
- Electron main tests: Added
commandLine.appendSwitchmock,disableHardwareAccelerationmock - IPC channel parity test: All 16 preload.cjs channels handled in electron-main.js and vice versa
- Known limitations consolidated to exactly 5 remaining (L1–L5)
- Coverage: 50 test files, all passing, lint clean, format clean
📝 Documentation
- CHANGELOG.md: Comprehensive entry with full history from v1.4.1 through v1.7.2 (252 lines)
- README.md: Updated features list, controls table (click-anywhere added), UI panels section, settings panel description, audio settings subsection, test counts (1,938)
- PLAN.md: Version updated to
1.7.2(stable), definition of done items checked off - Inline code documentation added to:
src/main.js— pause menu architecture, focus trap, title screen, canvas aria-hiddensrc/game.js— title screen rendering, particle throttling, click-anywhere, bar show/hidesrc/particles.js— quality tiers, auto-throttle, hardware-aware pool sizingsrc/gameRuntime.js— pause/resume/defeat state transitions
📦 Technical
- Electron 42 with context isolation,
--disable-gpufallback for headless Linux - ES modules throughout
src/with clean module boundaries - Fixed-timestep simulation decoupled from frame rate via accumulator
- Entity pooling for projectiles, popups, and tile-index arrays
- Offscreen canvas caching for static grid/path layers
- Dynamic particle system with 4 quality tiers (Low/Medium/High/Ultra) and auto-throttle
- Background heartbeat keeps sim running when window is backgrounded
- CI: GitHub Actions on ubuntu-latest, Node 20/22/24
📂 Files Changed
17 files modified (+340/-261 lines):
CHANGELOG.md— comprehensive v1.7.2 entryREADME.md— updated features, controls, test counts, documentationcss/style.css— bar-hidden class, settings sidebar-row, settings-actions bottom-right, single-line, dynamic popup widthelectron-main.js— GPU fallback with /dev/dri check, additional GPU switches, improved crash handlerindex.html— bar-hidden default, settings-actions moved outside tab content, simplified audio settings, title screen prompt textpackage.json— version 1.7.2package-lock.json— version 1.7.2src/game.js— title screen optimization, click-anywhere, bar show/hide, version string, inline docssrc/gamePersistence.js— CURRENT_VERSION → 1.7.2src/gameRuntime.js— inline documentation for pause/resume/defeatsrc/main.js— settings-actions placement, title screen, bar hide/show, inline docssrc/particles.js— inline documentation for quality tierstests/electronMain.test.js— commandLine mocktests/helpers.js— version updatetests/main.test.js— version updatetests/persistence.test.js— version updatetests/preload.test.js— version update
v1.7.1 — emergency update fix
Release v1.7.1 — Emergency Update Fix
This emergency patch release fixes critical auto-update issues that could silently prevent users from receiving updates, and removes the "skip this version" feature to ensure reliable update notifications on every startup.
1,885 tests · 49 files · 92.93% branch coverage · 98.38% statement coverage
🐛 Critical Bug Fixes
Stale skipped version blocking updates — When a version was previously skipped (e.g. clicking "Skip" on an update notification), the skipped version was persisted to settings.json. If the user later downgraded to an older version (e.g. for testing or reinstalling), the stale skipped version would prevent the app from ever notifying them about the same update again — because shouldAnnounceToUser() immediately returned false for any version in the skippedVersions list. This could cause users to silently miss updates indefinitely.
Fix: Removed the "Skip this version" feature entirely. The "Skip" button has been replaced with "Cancel" — clicking Cancel simply dismisses the notification without persisting any state. The update prompt will re-appear on every startup until the user updates.
Double startup update check — Both the main process (did-finish-load handler in electron-main.js) and the renderer (updateManager.init() with a 3-second setTimeout) were independently triggering update checks on every app launch. This caused two identical "Update available: v1.7.0" notifications to appear simultaneously.
Fix: Removed the main process startup check. The renderer's delayed check is now the sole trigger, eliminating duplicate notifications.
Update notification "not-available" despite newer version existing — The shouldAnnounceToUser() function was filtering out valid updates because of the stale skippedVersions list combined with channel and release-type checks. Now that the skip feature is removed, shouldAnnounceToUser is simplified and always reports available updates that match the user's channel preference.
Files affected: electron-main.js, preload.cjs, src/updateManager.js, src/main.js, src/config/settingsDefaults.js
🎉 Features & Improvements
-
Sidebars expanded on startup — The left (HUD) and right (shop) sidebars now always expand on app launch regardless of the previously persisted collapsed state. This ensures full UI visibility immediately after starting the game.
-
Update banner smooth fade-out — The persistent update banner now uses a smooth CSS animation that slides it upward and fades it out of the canvas when dismissed, instead of abruptly disappearing.
-
Dev console support (F12) — A developer console has been added to help debug issues in production builds.
⚙️ Performance
- Auto-save debounce —
_stepWaveCompletion()now calls_autoSave()every 5 waves instead of every wave, reducing disk I/O during long sessions. - Cached DPS/HPS — Troop
getDps()andgetHps()return cached values computed in_recomputeStats(), avoiding recomputation on every render frame. - Cached stat lines — Shop
_buildStatLines()caches its result on the troop object, invalidated on upgrade. - Optimized troop tile index — Removed redundant full rebuilds from
sellTroop()andkillTroop(); the index is rebuilt only in_cleanupDead(). - Optimized monster tile index — Rewrote from full clear-and-rebuild to incremental updates tracking
_prevTileIdxon each monster, only moving entries when monsters cross tile boundaries. - Dynamic particle cap — Particle pool scales with
navigator.hardwareConcurrency(100–300 particles based on core count) instead of a hardcoded 300.
🧪 Testing & Quality
- Toast.js branch coverage: 83.33% → 95.83% — Added test for
showToast()without a type parameter, covering theTYPE_ICONS[type] || ''fallback andicon ?falsy ternary branches. - preload.cjs excluded from v8 coverage — v8 cannot instrument CommonJS files, so preload.cjs always reported 0%. Removed from the coverage
includelist. The file still has 36 active tests viatests/preload.test.js. - All test references to removed skip features cleaned up — Removed
skipUpdate,skip-update, andskippedVersionstest coverage fromtests/preload.test.js,tests/electronMain.test.js,tests/updateManager.test.js, andtests/smoke.test.js. - 1,885 tests across 49 files (up from 1,710 tests across 47 files).
- Full coverage compliance: Every source file now meets ≥90% on all coverage metrics.
- Lint: 0 errors, 0 warnings.
- Format: All source files use consistent Prettier formatting.
Coverage Summary (vs v1.7.0)
| Metric | v1.7.0 | v1.7.1 | Change |
|---|---|---|---|
| Statements | 98.47% | 98.38% | -0.09pp |
| Branches | 92.15% | 92.93% | +0.78pp |
| Functions | 93.75% | 98.30% | +4.55pp |
| Lines | 99.64% | 99.30% | -0.34pp |
Branch coverage improved significantly (+0.78pp). The slight changes in statements/lines are due to the exclusion of preload.cjs from coverage measurement.
⚙️ Configuration
- Magic numbers extracted — Added named constants to
config.js:MAX_SPAWNS_PER_FRAME,HIT_TROOPS_CAP,PARTICLE_POOL_SIZE,DEV_MODE_CLICK_THRESHOLD,DEV_MODE_CLICK_WINDOW_MS,WAVE_TRANSITION_DURATION,AUTO_SAVE_DEBOUNCE_MS,AUTO_SAVE_DEBOUNCE_WAVES,REVIVE_REWARD_HP_RATIO,POPUP_ANIM_MS. - Version bump — 1.7.0 → 1.7.1 (patch release).
💾 Persistence
- Save migration pipeline — Added
SaveMigratortosrc/gamePersistence.jswith versioned migration system and sensible defaults for legacy saves. Integrated intoGameSnapshotRestorer.apply().
📝 Documentation
- All references to
skipUpdate,skip-update, andskippedVersionsremoved from API docs, notification system descriptions, and contributing guidelines. - Test stats and coverage metrics updated across README and CONTRIBUTING to reflect current project state.
- Full CHANGELOG entry for v1.7.1.
v1.7.0 — Particles & Progress
v1.7.0 — Stable Release
This release upgrades Tower Defense from the 1.6.x beta track to a stable 1.7.0
release. It brings major quality-of-life improvements: a dynamic particle system
with auto-throttle, a multi-slot save rotation system, a fully reworked settings
panel, and numerous performance optimizations and bug fixes.
🎉 Features
Dynamic Particle System with Quality Tiers
- 4 quality tiers: Low (pool: 100, spawn: 0.3×, lifetime: 0.5×), Medium
(300, 0.6×, 0.75×), High (1000, 1.0×, 1.0×), Ultra (2000, 1.5×, 1.5×) - Configurable via
PARTICLES.setQuality()and adjustable from the Settings
panel (Graphics → Particle Quality) - Spawn multiplier scales particle count per effect; lifetime multiplier scales
how long particles remain visible - Pool trimming: switching to a lower tier immediately shrinks the particle pool
(_pool.length = newMaxPool) and caps active count to prevent memory spikes - Hardware-aware default pool size:
Math.min(CONFIG.PARTICLE_POOL_SIZE, Math.max(100, navigator.hardwareConcurrency × 50))
Auto-throttle (_checkFrameBudget)
- Runs every frame from the game simulation tick
- Downgrade: 3 consecutive frames >33ms (≤30 FPS) drops one quality tier
toward Low, cascading if performance remains poor - Upgrade: 60 consecutive frames <16ms (>60 FPS) moves one tier back toward
the user's chosen tier, then clears_autoTierwhen reached - Operates independently of
_userTier— never overwrites the user's preference - Normal frames (16–33ms) reset both counters to prevent false triggers
- Useful on low-end hardware or when the browser tab is backgrounded
Multi-Slot Save Rotation System
SaveRotationManagerwith 3 auto-save slots (autosave.0–autosave.2)
using LRU (Least Recently Used) eviction- Manual named save slots — enter any name, saved to disk
- Save/Load popup: accessible from the bar button, shows all save entries
with thumbnail preview, wave/gold/lives summary, timestamps - Overwrite confirmation dialog: warns when saving to an existing named slot;
auto-save slots show an Overwrite button instead - Auto-save debounced to every 5 waves (
AUTO_SAVE_DEBOUNCE_WAVES) to reduce
disk I/O - Save preview thumbnails: 200×150 JPEG captured from the game canvas
captureSavePreview()ingamePersistence.jswith graceful fallback when
the canvas is not available (e.g. Node.js environment)
Settings Panel Rework
- Tab-based layout: Audio | Graphics | Controls | Accessibility
| Update — each tab shows relevant controls - Audio: Master/SFX/Ambient/UI volume sliders with mute toggles
- Graphics: Particle Quality selector (Low/Medium/High/Ultra), Resolution
Scale slider (0.5×–2×), Screen Shake intensity (0%–100%) - Controls: Scroll/Zoom toggle, keybind capture with modifier key support
(e.g.Ctrl+R,Shift+P), visual feedback on capture - Accessibility: Colorblind Mode (high contrast) toggle, Reduced Motion toggle
- Update: Release channel radio buttons (Release / Pre-release), auto-download
checkbox, check interval (minutes), manual "Check Now" button - Draft-based editing: changes populate a
settingsDraftobject; Save writes to
Electron main process, Cancel reloads from disk SETTINGS_FIELD_TYPESconfiguration insettingsDefaults.jsdrives the form
rendering (sliders, toggles, selects, keybinds)- All collapsed-panel state persisted and restored
🐛 Bug Fixes
- Sim tick crash recovery —
_runSimTick()now setsgame._errorState = true,
stops the game loop, and shows "Game Error — Press R to restart" on the canvas
instead of white-screening. Pressing R re-enables the game. - Popup shortcut race condition —
_handlePopupShortcut()in game.js could
callopenFntwice (once fromtransitionend, once from the fallback
setTimeout()). Added an idempotentopenedguard flag. - Popup fallback timer leak — Moved the fallback
setTimeoutto an instance
property (this._popupFallbackTimer) so it can be cleared on subsequent calls,
preventing multipleopenFninvocations when rapidly switching popups. - PopupManager fallback timer — Module-level
_popupFallbackTimerprevents
duplicateopenTargetcalls when popups are rapidly opened/closed from the
bar buttons. - Save slot validation —
Game.loadFromSlot()now validates save data via
SaveSerializer.isValid()before callingrestore(), preventing corrupt or
malformed saves (NaN, negative values, missing fields) from crashing the game. - Monster
_hitTroopsmemory leak — Clear_hitTroopsSet when a monster
dies (.alive = false), freeing stale troop references and preventing
unbounded Set growth. - Input bounding rect stale cache —
Input._cachedRectis recalculated on
everymousedownevent instead of being cached indefinitely, ensuring
accurate coordinates after window resize. - Drag-to-place
_dragStatecleanup —_dragStateis reset tonullin
restart()andrestore()so stale drag state doesn't persist after reset. - Shield shop placement index — Fixed a duplicate
SHIELD_SHOP_WIDTHin
config that caused incorrect shield placement bounds. - Missing
aboutkey in electron-main settings —DEFAULT_SETTINGS.collapsed
inelectron-main.jswas missingabout: false; synced withsettingsDefaults.js. - Dead code removal — Removed unused
_compactMonsters()method from game.js
and an unreachable early return inresolveDownloadTag. - Redundant
if (m.alive)guard — Removed redundant alive check inside the
if (m.hp <= 0)block in_stepMonsters().
⚡ Performance
- Input bounding rect caching —
Inputclass now caches
canvas.getBoundingClientRect()and recalculates only onmousedowninstead
of everymousemove/wheelevent, reducing layout thrashing on high-DPI
displays. - 7 hot-path optimizations across the game loop, rendering pipeline, and
audio system:- Pre-allocated scratch arrays in
_buildTroopTileIndex(replaces per-call
new Set(), saves ~200 allocs/frame) - Pre-allocated
_clearedTileScratcharray reuses memory across compact cycles _updateMonsterTileIndexrewritten from full clear-and-rebuild to
incremental updates (tracks_prevTileIdxper monster)- Entity compaction in
_cleanupDead()now tracks size changes to know when
to rebuild the troop tile index - Cached DPS/HPS in
troop.jsviagetDps()/getHps()reading from
_cachedStats, computed once in_recomputeStats() - Cached stat lines in
shop.js—_buildStatLines()caches on the troop
object, invalidated on upgrade popupManager.jsuses module-level_popupFallbackTimerto avoid
per-call timer allocation
- Pre-allocated scratch arrays in
- Dead code cleanup: removed
_compactMonsters()(~20 lines) and unused
project file references. - Auto-save debounce:
_stepWaveCompletion()calls_autoSave()every 5
waves instead of every wave, reducing disk I/O during long sessions.
🧪 Testing & Quality
- 1,898 tests across 49 test files — all passing
- Dynamic Particle System tests (21 new tests):
- Quality tier system:
setQuality(),_applyTier()with pool/spawn/lifetime
values for all 4 tiers, invalid tier handling, pool trimming on downgrade - Auto-throttle:
_checkFrameBudget()— slow frame downgrade cascade,
fast frame upgrade cycle, boundary conditions (33ms/16ms),_autoTier
lifecycle, counter reset on normal frames _applyCfg()helper: config reuse, color override, gravity preservation_spawnEffect()helper: default color, overridden color, deterministic
spawn counts with tier multipliers
- Quality tier system:
- Save System tests (40+ tests in
persistence.test.js):SaveRotationManager:autoSaveSlots(),selectSlotForWrite()LRU
eviction (7 edge cases),makeMetaData()(wave/gold/lives extraction,
Infinity handling, missing wave defaults),extractMeta()(null input,
non-object input,_metafield, fallback to top-level),summarize()
(formatted output, partial data, timestamp inclusion)SaveMigrator:CURRENT_VERSION,_migrateV0()(defaults for missing
fields, null preservation, existing value preservation),
_migrateV1toV170()(troop field normalization,_metablock upgrade),
compareVersions()(major/minor/patch, pre-release vs release,
different-length segments, non-numeric segments)captureSavePreview(): null in Node.js, DOM canvas fallback
- Settings Panel Rework tests (17 tests, new file
settingsDefaults.test.js):SETTINGS_SECTIONS: 5 sections with correct IDs, labels, iconsSETTINGS_FIELD_TYPES: all field type configs for audio/graphics/controls/
accessibility (sliders, toggles, selects, keybinds)PARTICLE_QUALITY_TIERS: pool/spawn/lifetime values, monotonic increaseDEFAULT_SETTINGS: structure and default values (Medium quality, 0.5 volume,
Space/Enter/R/S/F keybinds)COLLAPSED_KEYSandmakeCollapsedDefaults()
- Code coverage: Statements 98.47%, Branches 92.15%, Lines 99.64%,
game.js lines at 100% - Lint: 0 errors, 0 warnings
- Format: All 80+ source and test files use consistent Prettier formatting
🔧 Configuration
- Version bump: 1.7.0-beta.2 → 1.7.0 (stable release)
- Magic numbers extracted to
config.js:MAX_SPAWNS_PER_FRAME,
HIT_TROOPS_CAP,PARTICLE_POOL_SIZE,DEV_MODE_CLICK_THRESHOLD,
DEV_MODE_CLICK_WINDOW_MS,WAVE_TRANSITION_DURATION,
AUTO_SAVE_DEBOUNCE_MS,AUTO_SAVE_DEBOUNCE_WAVES,
REVIVE_REWARD_HP_RATIO,POPUP_ANIM_MS
💾 Persistence
- Save migration pipeline (
SaveMigrator): versioned migration system
handling legacy saves (v0) with sensible defaults. Integrated into
GameSnapshotRestorer.apply()so all loaded saves run through migration. - Save error resilience: all save/load/d...
v1.6.2 — Known Limitation Sprint
Release v1.6.2 — Known Limitation Sprint
1,532 tests · 45 files · 92.25% branch coverage · ≥80% per-file thresholds
🐛 Bug Fixes
- Monster attack distance (L6):
_stepMonsterAttacks()now validates Chebyshev tile distance before applying damage — attacks from out-of-range targets are discarded instead of hitting through walls. - Shield regen delay (L7): Regeneration delay moved from a hardcoded global constant (
SHIELD_REGEN_DELAY) toMONSTER_SPECS.S.shieldRegenDelaywith backward-compatible??fallback, making it spec-configurable.
🧪 Testing & Quality
| Metric | v1.6.1 | v1.6.2 |
|---|---|---|
| Tests | 1,420 | 1,532 (+112) |
| Test files | 43 | 45 (+2) |
| Branch coverage | 91%+ | 92.25% |
New test files:
tests/uiOverlays.test.js— 14 tests, 100% coverage (statements/branches/functions/lines) fordrawWaveTransitionprogress, fade, and early-return pathstests/preload.test.js— 30 tests, 100% coverage across all 4 metrics for the Electron preload script (contextBridge.exposeInMainWorld)tests/electronMain.test.js— 50 tests, ~55% coverage (excluded from thresholds)tests/main.test.js— 14 tests, ~47% coverage (excluded from thresholds)
🛠 Fixed Limitations
| ID | Limitation | Fix |
|---|---|---|
| L6 | Attack distance not validated | Chebyshev tile-distance check in _stepMonsterAttacks() |
| L7 | Hardcoded shield regen delay | Configurable per-spec via MONSTER_SPECS.S.shieldRegenDelay |
| L12 | drawWaveTransition progress uncovered |
100% coverage with deterministic performance.now() mocks |
| L13 | No Electron tests | Phase 1: electron-main (50 tests). Phase 2: preload (100% coverage) |
| L14 | Main.js uncovered | DOM bootstrap tests with jsdom/canvas polyfill (14 tests, ~47%) |
📋 Remaining Limitations (7)
L1 (TypeScript), L2 (save migration), L3 (particle cap), L4 (drawShop coupling), L5 (hit-test parity), L13 (electron-main <80%), L14 (main.js <80%)
v1.6.0 — Test Suite Rewrite & CI Hardening
Tower Defense v1.6.0 — Stable Release
🎮 Gameplay
New Troops
- Healer Troop (support) — locks onto damaged allies, heals 8 HP/tick, deals 3 damage to monsters in heal range; upgradeable TGT stat for more simultaneous targets
- Flamer Troop (melee) — applies burn DoT (3 stacks, 3s duration, 0.5s ticks); burn damage scales with DMG upgrades
New Monsters
- Necromancer — revives up to 4 dead allied monsters within 2-tile range; revived monsters become revive-immune and take 50% reduced damage
- Boss — appears at waves 10/20/30 with 1668 HP (doubled to 3336 at spawn), 15 HP/s passive heal, 200g reward
- Shielded — 173 HP + regenerating 69 HP shield (overheals to 104)
- Spear — slows to half speed near troops, attacks closest in 2.5 tile radius
Combat System
- Troop HP — troops now have health pools and can be destroyed by monsters, adding strategic depth to positioning
- Three monster attack modes:
- stop (default): Monsters pause to attack troops in range, then resume pathing
- slow: Monsters slow to half speed near defense troops while attacking (Spear)
- pass: Monsters penetrate defenses at full speed, hitting each troop once (Runner)
- Monster splitting — Brute, Elite, and Champion split into 2 of level-1 on death (non-Boss, non-Shielded, non-pass-mode)
- Ice Wizard — splash + slow (50% speed, 2.5s) + shatter bonus (+50% damage on slowed targets); upgradeable SLW stat
- Lightning chain — upgradeable CHN stat adds +1 chain target per level (base 2), with 0.5s stun on each hit
- Melee troops take 70% reduced damage from monster attacks — they are your front line
Economy & Controls
- Sell confirmation — optional toggle; selling refunds 30% of total gold invested with 3-second global cooldown
- Dev mode (triple-click gold) — infinite gold and lives, custom wave composition via DEV popup, Alt+D toggle
- Adjustable game speed — 1x / 2x / 4x / 8x / 16x / 32x / 64x / 128x
- 12 troop types — Swordsman, Knight, Flamer, Archer, Machine Gun, Mage, Sniper, Valkyrie, Lightning, Mortar, Ice Wizard, Healer
- 9 monster types — Grunt, Runner, Brute, Elite, Champion, Necromancer, Shielded, Boss, Spear
🖥️ User Interface
- Drag-to-place — click-and-drag troop placement from shop cards onto the grid
- Placement preview — ghost preview with range circles, DPS/HPS text shown before placing
- Wave preview panel — next-wave monster composition with health/damage estimates, Necromancer revive estimates
- Notification system — bell icon with toast popups, notification panel with timestamps and action buttons (Update/Skip/Restart)
- Settings panel — persistent settings with Save/Cancel, update channel selection, check interval, auto-download toggle
- About page — game info, version with release type, GitHub repo link
- Animated tray windows — smooth roll-up/down transitions, single-tray constraint
- Smart cursor — standard arrow by default, hand pointer on clickable elements (shop, buttons, troops, grid)
- Hover tooltips — shop cards show troop stats on hover, stat upgrades highlight on hover
- UI reorganization — extracted game rendering from UI rendering, separated panel and cursor logic
🔧 Technical & Architecture
- ES modules — entire source base migrated to ES2020 modules with clean module boundaries
- GameRenderer extraction — game-specific draw calls separated from core Canvas renderer
- Fixed-timestep simulation — deterministic game logic decoupled from frame rate via accumulator
- Background heartbeat — keeps the main-thread simulation running when the window is backgrounded
- Zero-allocation coordinate helpers —
_intovariants (tileCenterInto,pixelToTile,shopCardRectInto) write into pre-allocated output objects - Offscreen canvas caching — static grid/path layers rendered once to offscreen canvases for performance
- Path2D caching — troop rounded-rectangle paths created once and reused across frames
- Data-driven particle effects — all 9 effect types defined in a single
EFFECT_DEFStable with generic dispatcher - Config-driven design — all game tuning, monster specs, troop specs, and wave definitions centralized in
config.js - Entity pooling — projectiles, popups, and tile-index arrays recycled to minimize GC pressure
- Tile-index spatial lookups — O(1) neighbor queries via
_monsterTileIndexand_troopTileIndex - Code deduplication — extracted shared
_buildStatLineshelper in shop.js eliminating ~40 lines of duplication
🧪 Testing & Quality
- Complete test suite rewrite — all 32 test files rewritten from scratch, 13 new test files added
- 1,369 tests across 41 files (up from ~800 tests across 32 files)
- >=80% per-file coverage thresholds enforced on all 4 metrics (statements, branches, functions, lines)
- Project-wide coverage: 98.37% statements, 91.35% branches, 98.12% functions, 99.61% lines
- Save schema pinning — 6 JSON fixtures in tests/fixtures/saves/ for migration testing
- Contract enforcement — module boundary contracts tested via contracts.test.js
- Canvas hit-test parity — cursor hit-testing verified against UI coordinates in uiHitTestParity.test.js
- Memory lifecycle tests — pool recycling, long-session stability, particle cap saturation
- Deterministic tests — all tests use fixed seeds,
vi.useFakeTimers(), andvi.mock()for full isolation - ESLint + Prettier — 0 errors, 0 formatting warnings
📦 Build & Distribution
- Electron 42.3.2 — latest Electron with context isolation
- electron-builder 26.8.1 — NSIS installer with configurable install directory, desktop shortcut
- Auto-update via GitHub Releases — channel selection (stable/pre-release), progress bar, one-click install
- electron-updater 6.6.1 — automatic update detection and installation
- CI pipeline — GitHub Actions on ubuntu + windows, Node 20 + 22, enforcing lint + format + coverage thresholds
- Windows 64-bit NSIS installer — attached to this release
📚 Documentation
- CONTRIBUTING.md — comprehensive contribution guidelines with setup, workflow, conventions, and testing practices
- CHANGELOG.md — full release history
- README.md — updated with latest test count, coverage stats, and version references
Installation
- Download
Tower Defense Setup 1.6.0.exefrom the assets below - Run the installer — it will guide you through the setup
- Launch Tower Defense from the Start Menu or desktop shortcut
Existing users will receive the update automatically through the built-in auto-updater.
v1.6.0-beta.1 — Expanded Coverage & Flamer Rename
Tower Defense 1.6.0 Beta 1
Git tag: v1.6.0-beta.1
Display tag: 1.6.0 beta 1
Package version: 1.6.0-beta.1
Git tag names cannot contain spaces; the human display is 1.6.0 beta 1, package version is 1.6.0-beta.1, and GitHub/electron-builder tag is v1.6.0-beta.1.
Release type: beta / pre-release.
This tag marks Tower Defense 1.6.0 Beta 1 on release branch release/1.6.0 at commit 319d945. Beta 1 focuses on the new Flamer melee damage-over-time troop, burn state/tick/reward behavior, burn UI feedback, and a major expansion of automated Vitest coverage around UI, input, audio, renderer, toast, cursor hit-testing, and release-feed parsing.
Important scope note: Healer Monster, Healer Monster wave estimates, and the v1.6.0 balance pass remain planned for later v1.6.0 work. This beta is intended to validate the Flamer milestone and the expanded test baseline before continuing with monster support systems.
Release summary:
- Added Flamer as a melee DoT troop with burn stacks, burn duration, burn tick interval, and upgrade-scaled burn damage.
- Implemented burn application on successful non-lethal Flamer melee hits.
- Routed burn tick damage through the normal monster damage path so burn kills grant normal rewards and trigger normal death effects.
- Added burn expiration, revive cleanup, and monster-leave cleanup behavior.
- Added burn particles, burn tick particles, and a pulsing burn ring around burning monsters.
- Added burn DPS display in placement preview and selected troop/shop UI.
- Renamed the flame melee unit from
Flame TrooptoFlamerin config, README, and tests. - Expanded automated coverage with focused tests for UI helpers, UI constants, input handling, audio effects, renderer transforms/cache behavior, toast notifications, cursor hit-testing, and release-feed parsing.
Flamer values:
- Troop id:
flame - Name:
Flamer - Type:
melee - Cost:
160g - HP:
70 - Damage:
14 - Range:
1 - Attack speed:
0.75s - Burn stacks:
3 - Burn duration:
3s - Burn tick interval:
0.5s - Burn damage per tick:
25%of Flamer current damage per stack, rounded to at least1
Burn behavior:
- Burn is applied only after a successful non-lethal Flamer melee hit.
- Burn stacks up to the configured maximum of 3.
- Each new application refreshes burn duration to 3s.
- Burn tick damage scales with Flamer damage upgrades at application time.
- Burn ticks every 0.5s while the monster is alive and burning.
- Burn damage is routed through
Game.damageMonster()so rewards, death effects, splitting rules, and shield interactions use the normal monster damage path. - Burn is cleared when a monster is revived and naturally expires when the monster leaves or dies.
UI and feedback:
- Shop card shows
BRN <dps>for Flamer and updates with damage level. - Placement preview shows both direct
DPSand burnBRNvalues. - Burning monsters render a pulsing orange ring.
- Burn application and burn tick effects spawn dedicated particle feedback.
Automated test coverage:
tests/troop.test.jscovers Flamer melee burn application, non-application on lethal hits, stack cap, and upgrade-scaled burn damage.tests/monster.test.jscovers burn state, ticking, stack scaling, applied tick interval, expiration, refresh behavior, and clear behavior.tests/monsterIntegration.test.jscovers burn kill rewards, reward double-count prevention, and burn interaction with Shielded monsters.tests/placementPreview.test.jscovers burn DPS calculation.tests/config.test.jscovers Flamer config metadata.- New focused coverage files were added for
tests/audio.test.js,tests/gameRendererCursor.test.js,tests/input.test.js,tests/renderer.test.js,tests/toast.test.js,tests/uiConstants.test.js, andtests/uiUtils.test.js. - Release-feed tests were expanded for stable release selection and invalid/malformed feed handling.
Validation performed before tagging:
npm test: 31 files / 1,460 tests passed.npm run lint: passed.git diff --check: passed.- Working tree was clean before tag creation.
Build and publish:
- Build/publish command:
npm run release. electron-builderis configured to build the Windows x64 NSIS installer and publish release assets to GitHub Releases through the project publish configuration.
v1.5.2 — Stable Release
Tower Defense v1.5.2
Git tag: v1.5.2
Commit: 10eeb13
Package version: 1.5.2
v1.5.2 is the stable release for the 1.5.2 line, carrying forward the UI clarity, wave planning, runtime stability, and expanded validation work from the beta releases.
What changed:
- Package, persistence, and update-manager version metadata now report 1.5.2.
- Placement preview now shows DPS for damaging troops, HPS for support troops, and specific invalid-placement reasons.
- Wave preview now shows start timing, estimated clear duration, total gold, and revive-aware estimates for Necromancer waves.
- Runtime cleanup, UI hit-testing, muted audio, update checks, and Electron updater behavior were stabilized.
- Healer balance is live with 3 monster damage in healing range.
- Test suite expanded to 1,360 tests across 24 Vitest test files.
- Coverage thresholds were added for src/game.js, src/monster.js, and src/troop.js.
Validation:
- npm test: 1,360/1,360 passed.
- npm run lint: passed.
- npm run format:check: passed.
- npm run test:coverage: 1,360/1,360 passed.
v1.5.2-beta.2 — Pre-release Fixes
Tower Defense v1.5.2 Beta 2
Git tag: v1.5.2-beta.2
Commit: 03911e5
Package version: 1.5.2-beta.2
v1.5.2 Beta 2 is the final pre-release polish pass for the 1.5.2 line, carrying forward the UI clarity, wave planning, runtime stability, and expanded validation work from Beta 1.
What changed:
- Package, persistence, and update-manager version metadata now report 1.5.2-beta.2.
- Placement preview now shows DPS for damaging troops, HPS for support troops, and specific invalid-placement reasons.
- Wave preview now shows start timing, estimated clear duration, total gold, and revive-aware estimates for Necromancer waves.
- Runtime cleanup, UI hit-testing, muted audio, update checks, and Electron updater behavior were stabilized.
- Healer balance is live with 3 monster damage in healing range.
- Test suite expanded to 1,360 tests across 24 Vitest test files.
- Coverage thresholds were added for src/game.js, src/monster.js, and src/troop.js.
Validation:
- npm test: 1,360/1,360 passed.
- npm run lint: passed.
- npm run format:check: passed.
- npm run test:coverage: 1,360/1,360 passed.
v1.5.2-beta.1 — Placement Preview Enhancement
Tower Defense v1.5.2 Beta 1
Git tag: v1.5.2-beta.1
Display tag: 1.5.2 beta 1
Package version: 1.5.2-beta.1
Release commit: d84def3
Release type: beta / pre-release.
v1.5.2 Beta 1 focuses on UI clarity, wave planning, runtime stability, and expanded validation coverage.
Release summary:
- Placement preview now shows DPS for damaging troops, HPS for support troops, and specific invalid-placement reasons.
- Wave preview now shows start timing, estimated clear duration, total gold, and revive-aware estimates for Necromancer waves.
- Runtime cleanup, UI hit-testing, muted audio, update checks, and Electron updater behavior were stabilized.
- Healer balance is live with 3 monster damage in healing range.
- Test suite expanded to 1,360 tests across 24 Vitest test files.
- Coverage thresholds were added for src/game.js, src/monster.js, and src/troop.js.
Validation performed for this beta tag:
- npm test passed with 1,360 tests across 24 files.
- npm run lint passed.
- npm run format:check passed.
- npm run test:coverage passed with 1,360 tests across 24 files.
Packaging and auto-update workflow:
- Build and publish command: npm run release
- GitHub release publishing uses the configured GitHub provider and requires authenticated gh/GitHub access.