Skip to content

Releases: avcode-exe/Tower-Defense

v1.7.3 - Emergency Updater Fix

Choose a tag to compare

@avcode-exe avcode-exe released this 30 Jul 15:45

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.yml references: 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:

  1. Check if _downloadedFilePath is set (from the manual download)
  2. Launch the NSIS installer directly via spawn() with { detached: true, shell: true, stdio: 'ignore' } and no /S silent flag — so the user can see the installer UI and interact with it
  3. Wait 500ms for the installer to start, then call app.quit() to allow the installer to close the app process for file replacement
  4. Fall back to autoUpdater.quitAndInstall() if _downloadedFilePath is 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-log IPC channel. Logs appear as [update-main] in the renderer console.
  • Installer visibility: The NSIS installer now runs with a visible UI (no /S silent flag), so users can see installation progress and interact with the standard install dialogs.

📦 Download

🧪 Testing

  • 1,938 tests passed across 50 test files
  • ESLint: 0 errors, 0 warnings
  • Prettier: All files pass format check

🔧 How to Update

  1. 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.
  2. 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 _downloadedFilePath module-level variable to track manually downloaded installer path
  • Rewrote downloadUpdateSafely() as async with manual fetch() + Web Streams Reader download (replacing autoUpdater.downloadUpdate())
  • Added fixAssetNamesInInfo() function to query GitHub API and resolve the actual asset name with dots
  • Rewrote restart-to-update IPC handler to launch the installer directly via spawn() with visible UI and 500ms delay before app.quit()
  • Added comprehensive logUpdate() calls in all IPC handlers, checkForUpdatesDirect(), sendStatus(), shouldAnnounceToUser(), and all autoUpdater.on() event listeners
  • Added UPDATE_DEBUG flag and logUpdate() helper function

src/main.js:

  • Added onUpdateDebugLog relay 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 onUpdateDebugLog function that exposes the 'update-debug-log' IPC channel to the renderer via contextBridge

src/updateManager.js:

  • Added console.log('[update] renderer: ...') calls in check(), _onStatus(), and download() for all phases: checking, available, downloading, downloaded, error

src/game.js:

  • Version string updated to v1.7.3

src/gamePersistence.js:

  • CURRENT_VERSION updated 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: Added child_process mock, updated to async for downloadUpdateSafely, added onUpdateDebugLog to mock electron, updated all version references to 1.7.3
  • tests/helpers.js: Updated game.appVersion and getVersion mock to 1.7.3
  • tests/main.test.js: Updated getAnnouncedVersion, getVersion mock, text content assertions, and describe labels to 1.7.3
  • tests/persistence.test.js: Updated save metadata version, cmp assertions to 1.7.3
  • tests/preload.test.js: Updated mockInvoke mock and expectations to 1.7.3
  • tests/electronMain.test.js: Updated resolveDownloadTag and parseUpdateInfo mocks 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

Choose a tag to compare

@avcode-exe avcode-exe released this 30 Jul 10:28

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 receives aria-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-hidden CSS 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-content to resize to fit content rather than using fixed min-width/max-width constraints. Min-widths adjusted to smaller values (180–300px range).
  • Single-line content: Added white-space: nowrap to .game-panel so all popup content renders on single lines. Added overflow-x: auto to .bar-popup for 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 calls startNewGame().
  • 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 via app.commandLine.appendSwitch() before app.whenReady(), preventing cc/raster/one_copy_raster_buffer_provider.cc:282: Creation of StagingBuffer's SharedImage failed errors 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 _lastSaveWave in restart() so auto-save debounce doesn't skip waves after game reset. Clear _pendingAttack on ATTACKING→MOVING transitions to prevent stale attack queue entries. Guard against undefined m.leak in _stepMonsters. Restore _onProjectileImpact callback in restore(). Replace sparse Array(N) with Array(N).fill(null) for _monsterTileIndex.
  • Version utilities: isPrerelease regex updated to match all common pre-release formats (-beta, -alpha, -rc, -pre). parseVersion handles 4-segment versions.
  • Healer scan throttling: Healer _tryHealAllies now 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-updater updated to ^6.8.9 — fixes builder-util-runtime credential leak (GHSA-p2f4-r6v6-j797)
  • brace-expansion override 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 >= 20 requirement

🧪 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.appendSwitch mock, disableHardwareAcceleration mock
  • 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-hidden
    • src/game.js — title screen rendering, particle throttling, click-anywhere, bar show/hide
    • src/particles.js — quality tiers, auto-throttle, hardware-aware pool sizing
    • src/gameRuntime.js — pause/resume/defeat state transitions

📦 Technical

  • Electron 42 with context isolation, --disable-gpu fallback 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 entry
  • README.md — updated features, controls, test counts, documentation
  • css/style.css — bar-hidden class, settings sidebar-row, settings-actions bottom-right, single-line, dynamic popup width
  • electron-main.js — GPU fallback with /dev/dri check, additional GPU switches, improved crash handler
  • index.html — bar-hidden default, settings-actions moved outside tab content, simplified audio settings, title screen prompt text
  • package.json — version 1.7.2
  • package-lock.json — version 1.7.2
  • src/game.js — title screen optimization, click-anywhere, bar show/hide, version string, inline docs
  • src/gamePersistence.js — CURRENT_VERSION → 1.7.2
  • src/gameRuntime.js — inline documentation for pause/resume/defeat
  • src/main.js — settings-actions placement, title screen, bar hide/show, inline docs
  • src/particles.js — inline documentation for quality tiers
  • tests/electronMain.test.js — commandLine mock
  • tests/helpers.js — version update
  • tests/main.test.js — version update
  • tests/persistence.test.js — version update
  • tests/preload.test.js — version update

v1.7.1 — emergency update fix

Choose a tag to compare

@avcode-exe avcode-exe released this 24 Jul 07:58

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() and getHps() 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() and killTroop(); the index is rebuilt only in _cleanupDead().
  • Optimized monster tile index — Rewrote from full clear-and-rebuild to incremental updates tracking _prevTileIdx on 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 the TYPE_ICONS[type] || '' fallback and icon ? falsy ternary branches.
  • preload.cjs excluded from v8 coverage — v8 cannot instrument CommonJS files, so preload.cjs always reported 0%. Removed from the coverage include list. The file still has 36 active tests via tests/preload.test.js.
  • All test references to removed skip features cleaned up — Removed skipUpdate, skip-update, and skippedVersions test coverage from tests/preload.test.js, tests/electronMain.test.js, tests/updateManager.test.js, and tests/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 SaveMigrator to src/gamePersistence.js with versioned migration system and sensible defaults for legacy saves. Integrated into GameSnapshotRestorer.apply().

📝 Documentation

  • All references to skipUpdate, skip-update, and skippedVersions removed 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

Choose a tag to compare

@avcode-exe avcode-exe released this 23 Jul 12:17

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 _autoTier when 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

  • SaveRotationManager with 3 auto-save slots (autosave.0autosave.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() in gamePersistence.js with 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 settingsDraft object; Save writes to
    Electron main process, Cancel reloads from disk
  • SETTINGS_FIELD_TYPES configuration in settingsDefaults.js drives the form
    rendering (sliders, toggles, selects, keybinds)
  • All collapsed-panel state persisted and restored

🐛 Bug Fixes

  • Sim tick crash recovery_runSimTick() now sets game._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
    call openFn twice (once from transitionend, once from the fallback
    setTimeout()). Added an idempotent opened guard flag.
  • Popup fallback timer leak — Moved the fallback setTimeout to an instance
    property (this._popupFallbackTimer) so it can be cleared on subsequent calls,
    preventing multiple openFn invocations when rapidly switching popups.
  • PopupManager fallback timer — Module-level _popupFallbackTimer prevents
    duplicate openTarget calls when popups are rapidly opened/closed from the
    bar buttons.
  • Save slot validationGame.loadFromSlot() now validates save data via
    SaveSerializer.isValid() before calling restore(), preventing corrupt or
    malformed saves (NaN, negative values, missing fields) from crashing the game.
  • Monster _hitTroops memory leak — Clear _hitTroops Set when a monster
    dies (.alive = false), freeing stale troop references and preventing
    unbounded Set growth.
  • Input bounding rect stale cacheInput._cachedRect is recalculated on
    every mousedown event instead of being cached indefinitely, ensuring
    accurate coordinates after window resize.
  • Drag-to-place _dragState cleanup_dragState is reset to null in
    restart() and restore() so stale drag state doesn't persist after reset.
  • Shield shop placement index — Fixed a duplicate SHIELD_SHOP_WIDTH in
    config that caused incorrect shield placement bounds.
  • Missing about key in electron-main settingsDEFAULT_SETTINGS.collapsed
    in electron-main.js was missing about: false; synced with settingsDefaults.js.
  • Dead code removal — Removed unused _compactMonsters() method from game.js
    and an unreachable early return in resolveDownloadTag.
  • Redundant if (m.alive) guard — Removed redundant alive check inside the
    if (m.hp <= 0) block in _stepMonsters().

⚡ Performance

  • Input bounding rect cachingInput class now caches
    canvas.getBoundingClientRect() and recalculates only on mousedown instead
    of every mousemove/wheel event, 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 _clearedTileScratch array reuses memory across compact cycles
    • _updateMonsterTileIndex rewritten from full clear-and-rebuild to
      incremental updates (tracks _prevTileIdx per monster)
    • Entity compaction in _cleanupDead() now tracks size changes to know when
      to rebuild the troop tile index
    • Cached DPS/HPS in troop.js via getDps()/getHps() reading from
      _cachedStats, computed once in _recomputeStats()
    • Cached stat lines in shop.js_buildStatLines() caches on the troop
      object, invalidated on upgrade
    • popupManager.js uses module-level _popupFallbackTimer to avoid
      per-call timer allocation
  • 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
  • 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, _meta field, 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, _meta block 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, icons
    • SETTINGS_FIELD_TYPES: all field type configs for audio/graphics/controls/
      accessibility (sliders, toggles, selects, keybinds)
    • PARTICLE_QUALITY_TIERS: pool/spawn/lifetime values, monotonic increase
    • DEFAULT_SETTINGS: structure and default values (Medium quality, 0.5 volume,
      Space/Enter/R/S/F keybinds)
    • COLLAPSED_KEYS and makeCollapsedDefaults()
  • 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...
Read more

v1.6.2 — Known Limitation Sprint

Choose a tag to compare

@avcode-exe avcode-exe released this 22 Jul 02:48

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) to MONSTER_SPECS.S.shieldRegenDelay with 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) for drawWaveTransition progress, fade, and early-return paths
  • tests/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

Choose a tag to compare

@avcode-exe avcode-exe released this 21 Jul 03:54

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_into variants (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_DEFS table 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 _monsterTileIndex and _troopTileIndex
  • Code deduplication — extracted shared _buildStatLines helper 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(), and vi.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

  1. Download Tower Defense Setup 1.6.0.exe from the assets below
  2. Run the installer — it will guide you through the setup
  3. 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

Choose a tag to compare

@avcode-exe avcode-exe released this 19 Jun 08:59

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 Troop to Flamer in 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 least 1

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 DPS and burn BRN values.
  • Burning monsters render a pulsing orange ring.
  • Burn application and burn tick effects spawn dedicated particle feedback.

Automated test coverage:

  • tests/troop.test.js covers Flamer melee burn application, non-application on lethal hits, stack cap, and upgrade-scaled burn damage.
  • tests/monster.test.js covers burn state, ticking, stack scaling, applied tick interval, expiration, refresh behavior, and clear behavior.
  • tests/monsterIntegration.test.js covers burn kill rewards, reward double-count prevention, and burn interaction with Shielded monsters.
  • tests/placementPreview.test.js covers burn DPS calculation.
  • tests/config.test.js covers 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, and tests/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-builder is 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

Choose a tag to compare

@avcode-exe avcode-exe released this 14 Jun 07:24

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

Pre-release

Choose a tag to compare

@avcode-exe avcode-exe released this 14 Jun 06:23

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

Choose a tag to compare

@avcode-exe avcode-exe released this 14 Jun 05:39

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.