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