Skip to content

feat: hardening IV — decomposition, tests, resilience, mobile#21

Open
xConde wants to merge 40 commits into
mainfrom
feat/hardening-iv
Open

feat: hardening IV — decomposition, tests, resilience, mobile#21
xConde wants to merge 40 commits into
mainfrom
feat/hardening-iv

Conversation

@xConde

@xConde xConde commented Mar 7, 2026

Copy link
Copy Markdown
Owner

Summary

Production-hardening sprint: decomposed game-board god component into 6 child components, added 393 tests (1635→2028), hardened localStorage/WebGL/state resilience, redesigned mobile layout, added PWA manifest + a11y foundations.

Technical Implementation

  • Component decomposition: Extracted HUD, Setup, Controls, Results, TowerInfo, TowerSelectionBar from game-board.component
  • Mobile layout: Vertical tower strip (left), minimap top-left via CSS, speed controls bottom-right, free bottom edge
  • WebGL resilience: Context loss/restore handlers, renderer guard in animate loop
  • State hardening: LeakedEnemyInfo interface, status effect eager cleanup, fixed timestep edge cases
  • Storage resilience: localStorage quota guards, corrupted JSON fallbacks, auto-save try-catch
  • Test coverage: 393 new tests — restart integration, game tiles, three-utils, combat edge cases, map validation
  • PWA: manifest.webmanifest, theme-color, apple-mobile-web-app meta
  • A11y: Skip link, ARIA live regions, focus-visible, prefers-reduced-motion
  • Constants: Extracted magic numbers into typed config objects (UI, effects, rendering, lighting, camera, mobile)
  • Dead code cleanup: Removed unused minimap mobile constants, dead imports
  • Editor: DI scoping (services to EditorModule), auto-save failure handling, loading spinner

Test Results

✓ 2028 tests passing, 0 failures

Risk Assessment

  • Breaking changes: None — all changes are additive or internal refactors
  • Rollback plan: git revert the merge commit
  • Monitoring: Check mobile layout on iPhone/Android, verify WebGL context recovery on tab switch

🤖 Generated with Claude Code

xConde and others added 15 commits March 7, 2026 16:02
- Add grid size bounds (5-30), tile dimension checks, terrain type validation,
  spawn/exit coordinate and count validation to validateMapJson
- Add validateMapPlayability method for pre-game checks (spawn/exit presence,
  no overlap, dimensions)
- Add pre-game validation in map-select before navigating to /play
- Add defensive checks in map-bridge convertToGameBoard (null guard, gridSize
  clamping, tiles validation)
- 33 new tests covering all validation edge cases (1689 total, all passing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…-board

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… game-board

Move setup panel (difficulty, modifiers, endless mode) and tower info panel
(stats, upgrade/sell, specialization, targeting) into standalone child
components. Parent retains all business logic; children only emit events.

1730 tests passing (was 1656 — +20 new component tests, +54 from prior step).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mirror the game-board error handling pattern: try/catch around
ngAfterViewInit init chain, WebGL detection before renderer creation,
animate() guard against null renderer, ngOnDestroy guards for partial
init, and error overlay UI with fallback navigation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…t; cache tower costs

Move tower selection bar (buttons, tooltips, affordability) and game
controls (wave start, pause, speed, range/path toggles) into standalone
child components with their own templates, SCSS, and specs. Pre-compute
effective tower costs into a Map to eliminate template function calls.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 3 hand-crafted playable demo maps with distinct layouts
- Built-in Maps section on map-select with distinct styling
- Demo maps not editable/deletable (read-only built-ins)
- Row-major visual grid → column-major tiles conversion utility
- 149 new tests (demo map validation + map-select integration)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ange rings

- Extract handleInteraction() — eliminates 56 lines of duplicated click/tap logic
- Extract recordGameEndIfNeeded() — consolidates duplicated game-end recording
- Cache tileMeshArray and towerChildrenArray for raycasting performance
- Refresh range rings after tower upgrade when showAllRanges is active
- Add spawn retry safety net (SPAWN_MAX_RETRIES=300) in wave.service

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Map storage: explicit QuotaExceededError detection with warning logs
- Settings service: log warnings on save failures instead of silent swallow
- Player profile: pre-computed unlockedSet avoids Set allocation per getProfile()
- Remove 18 unused CSS custom properties (verified grep-clean)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- EditHistoryService, CameraControlService, EditorStateService: @Injectable()
- Added all three to EditorModule providers array
- Updated spec files to provide services explicitly in TestBed
- Services now destroyed on route away from /edit (no stale state)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tors, skip link

- prefers-reduced-motion: disables animations/transitions system-wide
- ARIA live regions on HUD (lives, gold, wave status)
- :focus-visible indicators using theme colors
- Skip-to-content link for keyboard navigation
- 3 new ARIA tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Game and editor show spinner overlay while renderer initializes
- Spinner hidden on success or error (error fallback takes over)
- Respects prefers-reduced-motion (animation disabled)
- 2 new tests for isLoading state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Web app manifest with fullscreen display mode
- SVG placeholder icon (192x192 and 512x512 referenced)
- Theme color, apple-mobile-web-app meta tags
- Viewport locked for gameplay (no pinch-zoom conflicts)
- Manifest added to Angular build assets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… bloom

- Cap devicePixelRatio to 1.5 on mobile (prevents 9x overdraw on 3x screens)
- Cap shadow map to 1024px on mobile (from 2048)
- Halve particle count on mobile
- Skip bloom pass on phones (≤480px) — expensive GPU effect
- Centralized MOBILE_CONFIG constants
- Same optimizations applied to editor renderer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…spawn retry

- Wave service: endless generation scaling, getMaxWaves, spawn retry skip, reward formula (11 tests)
- Tower combat: targeting strategies (nearest/first/strongest), cycle wrap, chain dedup (8 tests)
- Enemy service: shield absorption edge cases, MIN_ENEMY_SPEED floor, SPEED_DEMONS selective, flying SLOW immunity (13 tests → trimmed to fit)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Mar 7, 2026

Copy link
Copy Markdown

Deploying novarise with  Cloudflare Pages  Cloudflare Pages

Latest commit: 5138207
Status: ✅  Deploy successful!
Preview URL: https://d3196c50.novarise.pages.dev
Branch Preview URL: https://feat-hardening-iv.novarise.pages.dev

View logs

xConde and others added 14 commits March 7, 2026 23:04
…convention

- Replace magic numbers in init-error overlay SCSS with CSS custom properties (both game + editor)
- Fix PWA manifest to reference existing icon.svg instead of missing PNG files
- Rename catch(e) to catch(error) in player-profile and settings services

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Merge origin/main into feat/hardening-iv. Resolved conflicts:
- game-board.component.ts: adopt 3-point lighting from visual-overhaul, retain mobile shadow cap
- enemy.service.spec.ts: keep both hardening IV edge case tests + visual-overhaul animation tests
- tower-combat.service.spec.ts: keep both targeting/sell tests + zigzag/impact flash tests

1893/1893 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… comment

- Add console.warn when enemy is silently skipped after SPAWN_MAX_RETRIES
- Move recordGameEndIfNeeded() JSDoc to correct method
- Fix misleading wave preview template comment (INTERMISSION only, not SETUP)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add SKYBOX_CONFIG.timeScale for ms→seconds conversion (replaces `* 0.001`)
- Add sinNormalized() utility for [-1,1]→[0,1] mapping (replaces `* 0.5 + 0.5`)
- Document GLSL shader magic numbers inline per CLAUDE.md convention

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- saveMap() returns null on storage failure; callers show appropriate alerts
- File import rejects files > 10 MB (prevents browser tab crash)
- Achievements array capped to defined count on localStorage load
- testCanvas WebGL context released after feature detection (both components)
- migrateOldFormat() preserves source data when save fails
- importMapFromJson() simplified with null-propagation from saveMap()
- +4 tests: save failure/success alerts, file size limit, achievements cap

Red team pass 2 + 3: 5 findings, all fixed. STRATEGIC_AUDIT.md updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add camera.updateMatrixWorld() before all raycaster.setFromCamera() calls
  in both game and editor — OrbitControls.update() changes camera position
  without refreshing matrixWorld, causing stale rays after zoom
- Disable left-click orbit (reserved for tower placement), move orbit to
  right-click; WASD still pans, scroll still zooms
- Shrink minimap from 150px to 80px on phones (≤480px) and reposition from
  bottom-left to top-left to avoid overlapping the tower selection bar
- Derive minimap mobileBreakpoint from MOBILE_CONFIG.phoneBreakpoint
  (single source of truth)
- Add MOUSE_ACTION_DISABLED constant to camera.constants.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
window.innerWidth doesn't reflect the actual layout width when using
DevTools device simulation or when the game is embedded in a narrower
container. Use container.clientWidth instead for reliable mobile
detection at minimap init time.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
window.innerWidth and container.clientWidth both fail to reflect the
CSS viewport in DevTools device simulation. matchMedia queries the same
viewport that CSS media queries use, so it works reliably everywhere.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…etection

JS-based viewport detection (window.innerWidth, clientWidth, matchMedia)
failed to reliably detect mobile in DevTools responsive mode and on
deployed previews. Moved all minimap positioning to CSS class
(.minimap-canvas in styles.css) with @media (max-width: 480px) query.
Desktop: bottom-left 150px. Mobile: top-left 80px.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…y tower bar

- Minimap: top: 5.5rem (below HUD+wave status) instead of top: 8px
- Controls: bottom offset 11rem (was 8.5rem) to clear the ~10rem tower grid
- Speed buttons stay horizontal on mobile to reduce vertical footprint

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
xConde and others added 10 commits March 8, 2026 12:24
FPS counter and keyboard shortcut help (?) are not useful on phones.
Hiding them at ≤480px so the audio toggle has room and doesn't crowd
into the HUD bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…screen

Tower selection on mobile (≤480px) now renders as a narrow column on the
left side below the minimap (icon + cost only, no name/hotkey/tooltip).
This frees the entire bottom of the screen that the 3×2 grid was eating.
Speed controls move to bottom-right with no offset needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… ref

Sprint 1: Add GameBoardTile spec (41 tests) — factory methods, edge cases
Sprint 2: Add three-utils spec (11 tests) — disposeMaterial with all input types
Sprint 3: Fix stale enemy leak reference — updateEnemies() now returns
LeakedEnemyInfo { id, leakDamage } instead of bare IDs, eliminating
fragile post-lookup pattern

1898 → 1950 tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…onstants

Sprint 4: Explicit statusEffectService.removeAllEffects() on enemy kill/leak
  before removeEnemy(), preventing stale effect entries (+4 tests)
Sprint 5: Reset minimap visible flag in cleanup() to prevent state leak
  across restart cycles (+3 tests)
Sprint 6: Extract 4 magic numbers from game-board.component.ts into
  GAME_TIMING_CONFIG, TOWER_VISUAL_CONFIG, ENEMY_VISUAL_CONFIG

1950 → 1957 tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ouch

Sprint 7: WebGL context loss recovery — shows overlay on GPU context
  loss, auto-resumes on restoration, guards animate loop (+2 tests)
Sprint 8: Restart state consistency integration tests — 20 tests
  verifying all services reset to initial state after restartGame()
Sprint 9: Keyboard shortcut test coverage — M/m (minimap), H/h (help),
  V/v (path), Space, plus VICTORY/DEFEAT blocking for all keys (+18 tests)
Sprint 10: Mobile touch hardening — multi-touch guard prevents tower
  placement during pinch-to-zoom, phase guard in handleInteraction
  blocks raycasting during VICTORY/DEFEAT (+7 tests)

1957 → 2005 tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sprint 11: Guard WebGL context restore — check renderer.getContext()
  before resuming animate loop after context restoration
Sprint 12: Verified tower cache rebuild — no fix needed, cache already
  rebuilt on add/remove, upgrades only modify existing mesh properties
Sprint 13: Fix tower cost desync — tryPlaceTower() now uses
  getEffectiveTowerCost() instead of inline calculation
Sprint 14: Wave preview overflow-x: hidden, recordGameEnd idempotence
  test, contextLost flag test (+2 tests)

2005 → 2007 tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…cs, edge cases

Sprint 15: Dedicated context-lost-overlay CSS class with shared SCSS
  placeholder — separates recoverable vs init error overlay semantics
Sprint 16: Minimap init() guard — auto-cleanup if canvas exists,
  prevents orphaned canvases on double-init (+1 test)
Sprint 17: Editor mobile audit — no changes needed, already has proper
  touch targets (44px), bottom sheet, and 768px breakpoints
Sprint 18: Skybox shader magic numbers fully documented inline,
  5 enemy service edge case tests (remove nonexistent, empty map,
  no spawn points, damage dead enemy, no exit points)

2007 → 2013 tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…e cases

Sprint 19: Map storage resilience — guard loadMap against missing data
  field, guard getAllMaps against non-array parsed result (+2 tests)
Sprint 20: GameStateService edge case tests — 13 new tests covering
  setPhase idempotency, startWave DEFEAT guard, loseLife(0), exhaustive
  reset verification, endless highestWave tracking, rapid startWave
  double-call guard, reward accumulation, defensive copies (+13 tests)

2013 → 2028 tests, all passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…, auto-save guard

- Remove unused MINIMAP_CONFIG mobile constants (CSS handles sizing)
- Remove dead MINI_SWARM_MESH_SEGMENTS import from enemy.service
- Normalize minimap show/toggle display values to use CSS default
- Add try-catch to editor ngOnDestroy auto-save to prevent silent failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@xConde xConde changed the title feat: hardening IV — resilience, modularization, product foundations feat: hardening IV — decomposition, tests, resilience, mobile Mar 9, 2026
Minimap moves from top-left (crowded between HUD and tower bar) to
bottom-left corner. Tower bar now starts at 5.5rem (right below HUD)
instead of 11rem, using the freed vertical space.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant