From 250140d0f7444d0aa61f417b3f0aab8bf6277111 Mon Sep 17 00:00:00 2001 From: jedarden Date: Thu, 30 Apr 2026 14:25:35 -0400 Subject: [PATCH] Fix replay error alert, plan attack visualization feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace native alert() calls in replay.ts URL/file load error handlers with inline error display in the no-replay div. Add combat attack direction visualization to ยง16.9 of the plan: engine emits combat_death events with killer bot list; viewer draws directed arrows on kills. Co-Authored-By: Claude Sonnet 4.6 --- docs/plan/plan.md | 1 + web/src/pages/replay.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/plan/plan.md b/docs/plan/plan.md index 0908dd0..c335b78 100644 --- a/docs/plan/plan.md +++ b/docs/plan/plan.md @@ -4994,6 +4994,7 @@ smoothly between grid positions instead of teleporting. | Bot idle | Subtle 2% scale pulse, 2s cycle. Stops on movement. | Continuous | | Bot movement | 1-tile motion trail fading behind the bot, indicates direction of travel. At high speed, trails create visible flow patterns. | 150ms fade | | Combat threat | Thin dashed line between bots within attack range (red). Shows who threatens whom. | 1 turn | +| Attack event | Directed arrows from each attacker to the dying bot's tile on the turn a combat kill lands. The Go engine's `executeCombat()` needs to emit a `combat_death` event (type constant already exists: `EventCombatDeath`) alongside each `bot_died`, listing `killers: [{bot_id, owner, position}]` โ€” the enemies within attack radius that triggered the outnumbering condition. The web viewer reads `combat_death` events and draws a solid line (attacker player color, arrowhead) from each killer's tile center to the defender's tile center, fading over 300ms. Old replays lacking `combat_death` events keep the existing proximity-inference lines. Distinct from the ambient threat dashes โ€” these fire only on actual kills and encode exact participants rather than spatial proximity. | 300ms fade | | Bot death | Burst of 6โ€“8 particles scattering outward from death position, fading to transparent. | 400ms | | Energy collection | 4-line starburst radiating from the energy node + small "+1" text floating upward. | 200ms | | Core capture | Radial shockwave ring expanding from the core. Core color transitions from loser to capturer. | 500ms | diff --git a/web/src/pages/replay.ts b/web/src/pages/replay.ts index 93f664f..bc95b4a 100644 --- a/web/src/pages/replay.ts +++ b/web/src/pages/replay.ts @@ -1349,6 +1349,11 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void { prevCriticalBtn.addEventListener('click', navigateToPrevCriticalMoment); nextCriticalBtn.addEventListener('click', navigateToNextCriticalMoment); + function showLoadError(msg: string): void { + noReplayDiv.style.display = ''; + noReplayDiv.innerHTML = `${escapeHtml(String(msg))}`; + } + fileInput.addEventListener('change', async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; @@ -1357,7 +1362,7 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void { const replay = JSON.parse(text) as Replay; loadReplay(replay); } catch (err) { - alert('Failed to load replay: ' + err); + showLoadError('Failed to load replay: ' + err); } }); @@ -1370,7 +1375,7 @@ function initReplayViewer(ReplayViewerClass: any, initialUrl?: string): void { const replay = await response.json() as Replay; loadReplay(replay); } catch (err) { - alert('Failed to load replay from URL: ' + err); + showLoadError('Failed to load replay from URL: ' + err); } });