You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
PR Review — claude-opus-4-6 (Claude Opus 4.6, 1M context)
Overall
This is a textbook runtime split. The 264-line index.ts monolith is decomposed into adapter interfaces + a shared controller, and a GPUWindow entrypoint drops in cleanly against those seams. The refactoring is disciplined — no feature creep, no over-abstraction.
Verdict: Approve with two observations worth checking.
Both entrypoints (index.ts for browser/Tauri, gpu.ts for native) provide their own implementations of these 4 adapters against the same shared controller. Mission orchestration, alert management, input latching, and tick loop are in one place.
Strengths
Scene generalization (scene.ts): createDefenseScene(canvas) → createDefenseSceneWithRenderer(viewport, renderer, theme, getDevicePixelRatio). The DefenseViewport and DefenseRenderer interfaces are minimal — exactly the methods used. The original convenience wrapper is preserved at the bottom of the file for the browser path.
ResizeObserver correctly removed: Replaced by syncRendererSize() called on every render() frame. This is required for the GPU path (no ResizeObserver in Bun runtime) and harmless for the browser path — comparing dimensions is cheap.
Config extraction (config.ts): Types + fallback constants shared between both entrypoints. Clean single-source.
Cleanup pattern: startBattlestationRuntime() returns () => void that clears the interval, disposes controls and scene. Both entrypoints wire this to their cleanup hooks (window.on("close") for GPU, beforeunload for browser).
satisfies ElectrobunConfig in electrobun.config.ts: Type-checks the config without widening the inferred type. Nice.
WASM module declaration (wasm.d.ts): "./wasm/battlestation_wasm" → "*battlestation_wasm" — correctly handles the different relative import paths from index.ts vs gpu.ts.
subscribeAlerts removed from browser path: The old event-subscription-based alert flow is replaced by direct hud.setAlert() calls from the runtime. Simpler, and publishAlert still fires events for any external listeners.
Backwards compat: bun/index.js re-exports ./browser, so the existing electrobun config's default entrypoint still works.
Observations
GPUWindow mouse coordinate mapping may be off by title bar height (gpu.ts:83-97): Screen.getCursorScreenPoint() returns global screen coordinates. These are mapped to the 640×640 game world using window.getFrame(), but getFrame() likely returns the outer frame including the title bar (titleBarStyle: "default" is set). If so, cursor.y - frame.y would include the title bar offset, causing the y-coordinate to be shifted upward in the game world. The inside check would also admit clicks on the title bar as valid game targets.
This may need frame.y + titleBarHeight as the effective top edge, or switching to a content-area frame API if Electrobun provides one. Worth a quick manual test with a click at the very top of the renderable area.
Magic key codes in gpu.ts:7-10: LEFT_KEYS = new Set([37, 65, 123]) — 37 is ArrowLeft, 65 is 'A', but 123 is platform-specific (F12 on Windows, or a macOS virtual key code for arrow keys in native context). Similarly 124 in RIGHT_KEYS. A brief comment mapping these to their meanings would help future readers. Same for 13/32 (Enter/Space) and 77 (M) — less ambiguous but still worth documenting since these are raw key codes, not key names.
Minor notes (non-blocking)
Inline type imports (index.ts:1,19): import("./services/backend").MissionView and import("./services/profile").OperatorProfile — these work but are unusual. They avoid top-level imports of modules no longer needed at runtime in the browser entrypoint. Acceptable but worth noting for future readers who may find the pattern unfamiliar.
scene-gpu.ts double as unknown as casts (lines 17-33): Necessary because the Electrobun canvas shim and WebGPURenderer don't conform to the DOM types. The cast targets match DefenseViewport and DefenseRenderer exactly. This is the correct cross-runtime bridging pattern.
No tests: Acceptable for an example app refactor. The adapter interfaces are implicitly exercised by both entrypoints, and the core webtau framework tests remain untouched.
No Issues Found
No security concerns.
No behavioral regressions in the browser/Tauri path — the runtime controller preserves the exact same tick/alert/fire/mute logic.
The inFlight guard prevents concurrent step execution in both paths.
electrobun bumped to ^1.15.1 with correct lock file entries.
LGTM.
Reviewed by claude-opus-4-6 (Claude Opus 4.6, 1M context)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GpuWindowpath with shared mission logic and native Three WebGPU renderingWhat changed
startBattlestationRuntime()controllerWebGPURendererexamples/battlestation/README.mdGPUWindow limitations
Validation
bunx tsc --noEmit -p examples/battlestation/tsconfig.jsonbun run build:webinexamples/battlestationbun run build:electrobun:browserinexamples/battlestationbun run build:electrobun:gpuinexamples/battlestationTracking