Fix/terrain smoothing and init time - #15
Conversation
…sim startup - Implement smoothTerrain() to fix ReferenceError and enable terrain rendering - Add robust mesh readiness checks before aircraft setup - Prevent all undefined mesh/aircraft errors at startup and in animation loop - Ensure project loads with no white screen or runtime errors
WalkthroughThe changes introduce a new external stylesheet for minimap UI, overhaul terrain and minimap generation with procedural mountains and smoothing, add a dynamic minimap display, implement a finish line flag, introduce wind and turbulence effects, add fire and crash visuals, and provide a flight timer. Control lockout, altitude-based effects, and increased aircraft thrust and speed are also included. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Aircraft
participant Controls
participant Environment
participant Physics
participant Minimap
participant Timer
User->Controls: Keyboard input
Controls->Aircraft: Update controls (unless controlsLocked)
loop Animation Frame
Physics->Aircraft: Apply physics, wind, turbulence
Aircraft->Environment: Check altitude (fire effect if too high)
Aircraft->Minimap: Update minimap position/heading
Aircraft->Timer: Start/stop/update timer (airborne/finish)
alt Crash or fire
Physics->Aircraft: emitCrashStream (fire/smoke effect)
Controls->Aircraft: Lock controls
end
end
Environment->Minimap: Redraw minimap if needed
Poem
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (4)
src/main.js (1)
129-129: Consider using optional chaining for cleaner code.The static analysis correctly identifies opportunities to use optional chaining.
Apply optional chaining for more concise code:
- if (aircraft && aircraft.airborne && !window.timerStarted) { + if (aircraft?.airborne && !window.timerStarted) {- if (aircraft && aircraft.position && aircraft.velocity) { + if (aircraft?.position && aircraft?.velocity) {Also applies to: 146-146
src/environment.js (3)
74-86: Extract magic numbers as named constants for better maintainability.The terrain generation uses several magic numbers that would benefit from being named constants. This improves code readability and makes future adjustments easier.
+// Terrain generation constants +const BASE_FREQUENCY_SCALE = 0.5; +const INITIAL_AMPLITUDE = 1.5; +const FREQUENCY_MULTIPLIER = 1.7; +const AMPLITUDE_DECAY = 0.55; +const BASE_OCTAVES_ADDITION = 3; + // Generate base elevation with layered noise const baseElevationFn = (x, y) => { let elevation = 0; - let frequency = scale * 0.5; - let amplitude = 1.5; - for (let o = 0; o < octaves + 3; o++) { + let frequency = scale * BASE_FREQUENCY_SCALE; + let amplitude = INITIAL_AMPLITUDE; + for (let o = 0; o < octaves + BASE_OCTAVES_ADDITION; o++) { elevation += noise.noise(x * frequency, y * frequency, 0) * amplitude; - frequency *= 1.7; - amplitude *= 0.55; + frequency *= FREQUENCY_MULTIPLIER; + amplitude *= AMPLITUDE_DECAY; } return elevation; };
89-118: Consider extracting mountain generation parameters as constants.The mountain peak generation logic uses many hardcoded values that would be clearer as named constants.
+// Mountain generation parameters +const MIN_PEAK_DISTANCE = 6000; +const MAX_PEAK_ATTEMPTS = 200; +const TARGET_PEAK_COUNT = 18; +const PEAK_RADIUS_MIN = 8000; +const PEAK_RADIUS_MAX = 20000; +const PEAK_OFFSET_RANGE = 4000; +const PEAK_HEIGHT_BASE = 2.0; +const PEAK_HEIGHT_VARIATION = 1.2; +const PEAK_RADIUS_BASE = 3500; +const PEAK_RADIUS_VARIATION = 1200; + // Generate dispersed mountain peaks with minimum distance const mountainPeaks = []; -const minPeakDist = 6000; let attempts = 0; -while (mountainPeaks.length < 18 && attempts < 200) { +while (mountainPeaks.length < TARGET_PEAK_COUNT && attempts < MAX_PEAK_ATTEMPTS) { const angle = Math.random() * Math.PI * 2; - const radius = 8000 + Math.random() * 12000; - const px = Math.cos(angle) * radius + Math.random() * 4000; - const py = Math.sin(angle) * radius + Math.random() * 4000; + const radius = PEAK_RADIUS_MIN + Math.random() * (PEAK_RADIUS_MAX - PEAK_RADIUS_MIN); + const px = Math.cos(angle) * radius + Math.random() * PEAK_OFFSET_RANGE; + const py = Math.sin(angle) * radius + Math.random() * PEAK_OFFSET_RANGE; let tooClose = false; for (const p of mountainPeaks) { const d = Math.sqrt((px - p.x) ** 2 + (py - p.y) ** 2); - if (d < minPeakDist) { + if (d < MIN_PEAK_DISTANCE) { tooClose = true; break; } @@ -106,8 +106,8 @@ mountainPeaks.push({ x: px, y: py, - height: 2.0 + Math.random() * 1.2, // Lowered peak height - radius: 3500 + Math.random() * 1200 + height: PEAK_HEIGHT_BASE + Math.random() * PEAK_HEIGHT_VARIATION, + radius: PEAK_RADIUS_BASE + Math.random() * PEAK_RADIUS_VARIATION }); }
410-426: Define the downward velocity as a constant.The hardcoded velocity value of -100 should be extracted as a named constant for better maintainability and clarity.
const ALTITUDE_LIMIT = 3200; // meters above sea level +const FIRE_FALL_VELOCITY = -100; // m/s downward velocity when on fire + export function checkAltitudeLimit(aircraft) { if (!aircraft || !aircraft.position) return; if (aircraft.position.y > ALTITUDE_LIMIT && !aircraft.isOnFire) { // Trigger fire effect aircraft.isOnFire = true; // Show fire sprite/overlay (implement in your render loop) // Disable controls (implement in controls.js) // Start falling - aircraft.velocity.y = -100; + aircraft.velocity.y = FIRE_FALL_VELOCITY; // Optionally play sound, etc. }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
index.html(1 hunks)src/controls.js(4 hunks)src/environment.js(3 hunks)src/main.js(3 hunks)src/physics.js(2 hunks)style.css(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/environment.js (2)
src/physics.js (2)
i(592-592)i(612-612)src/main.js (3)
scene(13-13)aircraft(46-46)dx(135-135)
🪛 Biome (1.9.4)
src/main.js
[error] 129-129: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 146-146: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (8)
index.html (1)
8-8: LGTM!The external stylesheet link is correctly placed and follows standard conventions for separating styles from HTML.
src/physics.js (1)
67-71: Performance parameters doubled.The thrust and speed increases significantly enhance aircraft performance. Ensure these values are balanced with the game's difficulty and physics constraints.
style.css (1)
1-13: Well-structured minimap styles.The CSS provides clean, functional styling for the minimap with appropriate positioning, sizing, and visual presentation.
src/controls.js (1)
3-6: Control lockout mechanism implemented correctly.The lockout checks are properly placed to prevent all control inputs when the aircraft is in a locked state (fire/crash). The JSDoc addition improves code documentation.
Also applies to: 25-26, 55-56, 72-73
src/main.js (3)
34-44: Good implementation of mesh readiness polling.The polling mechanism ensures both terrain and ocean meshes are ready before proceeding with aircraft initialization, preventing potential undefined reference errors.
52-60: Wind and turbulence effects add realism.The wind system with steady direction and random turbulence provides good environmental dynamics. The implementation correctly scales the effects by delta time.
Also applies to: 186-201
104-126: Robust safety checks and fire effect integration.The comprehensive property checks prevent runtime errors, and the fire effect trigger with control lockout provides good gameplay state management.
src/environment.js (1)
375-406: Well-implemented terrain smoothing function.The
smoothTerrainfunction is properly implemented with correct boundary handling and clear documentation. The averaging algorithm correctly processes the grid structure and preserves X/Y coordinates while only smoothing Z values.
| /** | ||
| * NEW: Emits a realistic fire + smoke effect at the given world position. | ||
| * @param {THREE.Vector3} position - World impact location. | ||
| */ | ||
| export function emitCrashStream(position) { | ||
| const group = new THREE.Group(); | ||
| const fireCount = 120; | ||
| const smokeCount = 180; | ||
| let smokeSprites = []; | ||
|
|
||
| // Use the exact impact point as the center for all particles | ||
| const center = position.clone(); | ||
| // Use a fixed bounding box around the impact point for coverage | ||
| const bbox = new THREE.Box3( | ||
| center.clone().addScalar(-6), | ||
| center.clone().addScalar(6) | ||
| ); | ||
|
|
||
| // Helper to create a single particle | ||
| function createParticle(tex, size, color, opacity, pos, vel, fade, expand, animate) { | ||
| const mat = new THREE.SpriteMaterial({ | ||
| map: tex, | ||
| color: color, | ||
| opacity: opacity, | ||
| transparent: true, | ||
| depthWrite: false, | ||
| blending: tex === FIRE_TEX ? THREE.AdditiveBlending : THREE.NormalBlending | ||
| }); | ||
| const sprite = new THREE.Sprite(mat); | ||
| sprite.position.copy(pos); | ||
| sprite.scale.set(size, size, size); | ||
| sprite.userData = { vel, fade, expand, animate, baseSize: size, opacity }; | ||
| group.add(sprite); | ||
| return sprite; | ||
| } | ||
|
|
||
| // Fire core (bright, small, fast, climbs up) | ||
| for (let i = 0; i < fireCount; i++) { | ||
| // Distribute within bounding box centered at impact | ||
| const pos = new THREE.Vector3( | ||
| THREE.MathUtils.lerp(bbox.min.x, bbox.max.x, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.y, bbox.max.y, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.z, bbox.max.z, Math.random()) | ||
| ); | ||
| const vel = new THREE.Vector3( | ||
| (Math.random() - 0.5) * 2, | ||
| Math.random() * 8 + 8, | ||
| (Math.random() - 0.5) * 2 | ||
| ); | ||
| const color = new THREE.Color().setHSL(0.08 + Math.random() * 0.06, 1, 0.5 + Math.random() * 0.2); | ||
| createParticle( | ||
| FIRE_TEX, 8 + Math.random() * 6, color, 1, | ||
| pos, vel, 2.2 + Math.random() * 0.7, 1.5 + Math.random(), true | ||
| ); | ||
| } | ||
|
|
||
| // Fire glow (larger, orange, slower, climbs up) | ||
| for (let i = 0; i < fireCount / 2; i++) { | ||
| const pos = new THREE.Vector3( | ||
| THREE.MathUtils.lerp(bbox.min.x, bbox.max.x, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.y, bbox.max.y, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.z, bbox.max.z, Math.random()) | ||
| ); | ||
| const vel = new THREE.Vector3( | ||
| (Math.random() - 0.5) * 1.5, | ||
| Math.random() * 5 + 4, | ||
| (Math.random() - 0.5) * 1.5 | ||
| ); | ||
| const color = new THREE.Color().setHSL(0.07, 1, 0.35 + Math.random() * 0.1); | ||
| createParticle( | ||
| FIRE_TEX, 16 + Math.random() * 8, color, 0.8, | ||
| pos, vel, 2.8 + Math.random(), 2.5 + Math.random(), true | ||
| ); | ||
| } | ||
|
|
||
| terrain.parent.add(group); | ||
|
|
||
| // Animate all particles | ||
| let time = 0; | ||
| function animateFire() { | ||
| time += 0.016; | ||
| for (let i = group.children.length - 1; i >= 0; i--) { | ||
| const sprite = group.children[i]; | ||
| const ud = sprite.userData; | ||
| sprite.position.addScaledVector(ud.vel, 0.016); | ||
| ud.vel.y += 0.12 * 0.016; | ||
| ud.vel.x += (Math.random() - 0.5) * 0.02; | ||
| ud.vel.z += (Math.random() - 0.5) * 0.02; | ||
| sprite.scale.setScalar(ud.baseSize + ud.expand * time); | ||
| // Fade less aggressively | ||
| sprite.material.opacity = Math.max(0, ud.opacity * (1 - time / (ud.fade + 1.5))); | ||
| // Animate fire flicker | ||
| if (ud.animate && Math.random() < 0.2) { | ||
| sprite.material.color.offsetHSL(0, 0, (Math.random() - 0.5) * 0.1); | ||
| } | ||
| // Remove if faded | ||
| if (time > ud.fade + 1.5) { | ||
| group.remove(sprite); | ||
| } | ||
| } | ||
| if (group.children.length > 0) { | ||
| requestAnimationFrame(animateFire); | ||
| } | ||
| } | ||
| animateFire(); | ||
|
|
||
| // After a short delay, add smoke | ||
| setTimeout(() => { | ||
| // Thick smoke (dark, slow, large, rises and drifts) | ||
| for (let i = 0; i < smokeCount; i++) { | ||
| const pos = new THREE.Vector3( | ||
| THREE.MathUtils.lerp(bbox.min.x, bbox.max.x, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.y, bbox.max.y, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.z, bbox.max.z, Math.random()) | ||
| ); | ||
| const vel = new THREE.Vector3( | ||
| (Math.random() - 0.5) * 1.2, | ||
| Math.random() * 4 + 2, | ||
| (Math.random() - 0.5) * 1.2 | ||
| ); | ||
| const color = new THREE.Color().setHSL(0, 0, 0.08 + Math.random() * 0.12); | ||
| smokeSprites.push(createParticle( | ||
| SMOKE_TEX, 18 + Math.random() * 12, color, 0.7 + Math.random() * 0.2, | ||
| pos, vel, 4.5 + Math.random() * 2, 3 + Math.random() * 2, false | ||
| )); | ||
| } | ||
| // Light smoke (gray, very large, slow, fades out, rises and drifts) | ||
| for (let i = 0; i < smokeCount / 2; i++) { | ||
| const pos = new THREE.Vector3( | ||
| THREE.MathUtils.lerp(bbox.min.x, bbox.max.x, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.y, bbox.max.y, Math.random()), | ||
| THREE.MathUtils.lerp(bbox.min.z, bbox.max.z, Math.random()) | ||
| ); | ||
| const vel = new THREE.Vector3( | ||
| (Math.random() - 0.5) * 0.8, | ||
| Math.random() * 2 + 1, | ||
| (Math.random() - 0.5) * 0.8 | ||
| ); | ||
| const color = new THREE.Color().setHSL(0, 0, 0.25 + Math.random() * 0.15); | ||
| smokeSprites.push(createParticle( | ||
| SMOKE_TEX, 32 + Math.random() * 16, color, 0.4 + Math.random() * 0.2, | ||
| pos, vel, 6 + Math.random() * 2, 4 + Math.random() * 2, false | ||
| )); | ||
| } | ||
| // Animate smoke | ||
| let smokeTime = 0; | ||
| function animateSmoke() { | ||
| smokeTime += 0.016; | ||
| for (let i = smokeSprites.length - 1; i >= 0; i--) { | ||
| const sprite = smokeSprites[i]; | ||
| const ud = sprite.userData; | ||
| sprite.position.addScaledVector(ud.vel, 0.016); | ||
| ud.vel.y += 0.12 * 0.016; | ||
| ud.vel.x += (Math.random() - 0.5) * 0.02; | ||
| ud.vel.z += (Math.random() - 0.5) * 0.02; | ||
| sprite.scale.setScalar(ud.baseSize + ud.expand * smokeTime); | ||
| // Fade less aggressively | ||
| sprite.material.opacity = Math.max(0, ud.opacity * (1 - smokeTime / (ud.fade + 2))); | ||
| if (smokeTime > ud.fade + 2) { | ||
| group.remove(sprite); | ||
| smokeSprites.splice(i, 1); | ||
| } | ||
| } | ||
| if (smokeSprites.length > 0) { | ||
| requestAnimationFrame(animateSmoke); | ||
| } else { | ||
| terrain.parent.remove(group); | ||
| } | ||
| } | ||
| animateSmoke(); | ||
| }, 400); // 400ms delay before smoke appears | ||
| } |
There was a problem hiding this comment.
Critical: Remove duplicate function definition and fix undefined variable.
The emitCrashStream function is already defined within setupPhysics (lines 84-251) with identical implementation. Additionally, this exported version references an undefined terrain variable at line 630, which will cause a runtime error.
Remove this duplicate function and instead, either:
- Move the function outside
setupPhysicsand passterrainas a parameter, or - Keep it inside
setupPhysicsand export a wrapper function
Here's the recommended fix:
-/**
- * NEW: Emits a realistic fire + smoke effect at the given world position.
- * @param {THREE.Vector3} position - World impact location.
- */
-export function emitCrashStream(position) {
- const group = new THREE.Group();
- // ... (entire function body)
-}Instead, modify the function inside setupPhysics to make it accessible:
export function setupPhysics(aircraft, onTakeoff, terrain, ocean, camera) {
// ... existing code ...
- function emitCrashStream(position) {
+ aircraft.emitCrashStream = function(position) {
// ... existing implementation ...
}Then in main.js, use aircraft.emitCrashStream(position) instead of the imported function.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/physics.js between lines 555 and 726, there is a duplicate definition of
the emitCrashStream function which also references an undefined terrain variable
causing runtime errors. Remove this entire duplicate exported function. Instead,
keep the original emitCrashStream function inside setupPhysics (lines 84-251)
and modify it to accept terrain as a parameter or make it accessible via the
aircraft object. Then export a wrapper or access it through aircraft in main.js,
avoiding direct import of this duplicate function.
| // --- NEW: Timer triggers --- | ||
| if (aircraft && aircraft.airborne && !window.timerStarted) { | ||
| startFlightTimer(); | ||
| window.timerStarted = true; | ||
| } | ||
| // Stop timer if at finish line (simple proximity check) | ||
| if (aircraft && typeof mazeGoal !== 'undefined') { | ||
| const dx = aircraft.position.x - mazeGoal.x; | ||
| const dz = aircraft.position.z - mazeGoal.y; | ||
| if (Math.sqrt(dx*dx + dz*dz) < 200 && window.timerStarted) { | ||
| stopFlightTimer(); | ||
| window.timerStarted = false; | ||
| } | ||
| } | ||
| updateFlightTimerDisplay(); // NEW: update timer display |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Missing imports and initialization for timer and finish line.
The code references mazeGoal (line 134) which is not defined or imported. Also, window.timerStarted should be properly initialized.
Add the missing import and initialization:
-import { setupEnvironment, terrainMesh, oceanMesh, checkAltitudeLimit, startFlightTimer, stopFlightTimer, updateFlightTimerDisplay } from './environment.js';
+import { setupEnvironment, terrainMesh, oceanMesh, checkAltitudeLimit, startFlightTimer, stopFlightTimer, updateFlightTimerDisplay, mazeGoal } from './environment.js';And initialize the timer flag:
let missionManager = null;
+window.timerStarted = false;🏁 Script executed:
#!/bin/bash
# Check if mazeGoal is exported from environment.js
ast-grep --pattern 'export { $$$ mazeGoal $$$ }' src/environment.js || \
ast-grep --pattern 'export const mazeGoal' src/environment.js || \
ast_grep --pattern 'export let mazeGoal' src/environment.jsLength of output: 69
🏁 Script executed:
#!/bin/bash
# Search for mazeGoal references in src directory
rg -n 'mazeGoal' src/
# Search for timerStarted initialization or usage in src directory
rg -n 'timerStarted' src/Length of output: 999
Define and Export mazeGoal & Initialize timerStarted
The code references mazeGoal but there’s no definition or export anywhere in the JS modules, and window.timerStarted is never initialized. You’ll need to:
• In src/environment.js – add a definition and export for your goal coordinates.
// …existing code around line 299…
// Place at mazeGoal (assume mazeGoal is defined globally)
if (typeof mazeGoal !== 'undefined') {
goalFlag.position.set(mazeGoal.x, 0, mazeGoal.y);
}
+
+// Define and export the maze goal coordinates
+export const mazeGoal = {
+ x: /* target X */,
+ y: /* target Y */
+};• In src/main.js – import mazeGoal at the top and explicitly initialize the timer flag before it’s used:
-import { setupEnvironment, terrainMesh, oceanMesh, checkAltitudeLimit,
- startFlightTimer, stopFlightTimer, updateFlightTimerDisplay }
- from './environment.js';
+import {
+ setupEnvironment,
+ terrainMesh,
+ oceanMesh,
+ checkAltitudeLimit,
+ startFlightTimer,
+ stopFlightTimer,
+ updateFlightTimerDisplay,
+ mazeGoal
+} from './environment.js';
let missionManager = null;
+// Initialize the flight timer flag
+window.timerStarted = false;This ensures mazeGoal is available for proximity checks and that window.timerStarted starts in a defined state.
🧰 Tools
🪛 Biome (1.9.4)
[error] 129-129: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🤖 Prompt for AI Agents
In src/environment.js, define and export a constant for the goal coordinates
named mazeGoal with appropriate x and y values. In src/main.js, import mazeGoal
from src/environment.js at the top of the file. Also, before using
window.timerStarted in the timer logic (around lines 128-142), explicitly
initialize window.timerStarted to false to ensure it has a defined starting
state. This will make mazeGoal available for proximity checks and properly
initialize the timer flag.
| if (!document.getElementById('flight-timer')) { | ||
| timerDisplay = document.createElement('div'); | ||
| timerDisplay.id = 'flight-timer'; | ||
| timerDisplay.style.position = 'fixed'; | ||
| timerDisplay.style.top = '24px'; | ||
| timerDisplay.style.left = '50%'; | ||
| timerDisplay.style.transform = 'translateX(-50%)'; | ||
| timerDisplay.style.fontSize = '2em'; | ||
| timerDisplay.style.fontWeight = 'bold'; | ||
| timerDisplay.style.color = '#fff'; | ||
| timerDisplay.style.textShadow = '0 0 8px #222'; | ||
| timerDisplay.style.zIndex = '1000'; | ||
| document.body.appendChild(timerDisplay); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Move DOM element creation into initialization function.
Creating DOM elements at module load time can cause issues in server-side rendering or testing environments. Consider moving this into an initialization function that's called when needed.
-// Add timer display to DOM
-if (!document.getElementById('flight-timer')) {
- timerDisplay = document.createElement('div');
- timerDisplay.id = 'flight-timer';
- timerDisplay.style.position = 'fixed';
- timerDisplay.style.top = '24px';
- timerDisplay.style.left = '50%';
- timerDisplay.style.transform = 'translateX(-50%)';
- timerDisplay.style.fontSize = '2em';
- timerDisplay.style.fontWeight = 'bold';
- timerDisplay.style.color = '#fff';
- timerDisplay.style.textShadow = '0 0 8px #222';
- timerDisplay.style.zIndex = '1000';
- document.body.appendChild(timerDisplay);
-}
+// Initialize timer display
+function initializeTimerDisplay() {
+ if (!timerDisplay && !document.getElementById('flight-timer')) {
+ timerDisplay = document.createElement('div');
+ timerDisplay.id = 'flight-timer';
+ timerDisplay.className = 'flight-timer'; // Use CSS class
+ document.body.appendChild(timerDisplay);
+ }
+}
+
+// Call this when starting the timer
+export function startFlightTimer() {
+ initializeTimerDisplay();
+ timerStartTime = performance.now();
+ timerRunning = true;
+}Also add the corresponding CSS styles to your style.css file:
.flight-timer {
position: fixed;
top: 24px;
left: 50%;
transform: translateX(-50%);
font-size: 2em;
font-weight: bold;
color: #fff;
text-shadow: 0 0 8px #222;
z-index: 1000;
}🤖 Prompt for AI Agents
In src/environment.js around lines 446 to 459, the creation and styling of the
flight-timer DOM element is done at module load time, which can cause issues in
server-side rendering or testing. Refactor this code by moving the DOM element
creation and appending logic into a dedicated initialization function that is
called only when needed. Additionally, remove the inline style assignments and
instead add the provided CSS styles for the flight-timer class to your style.css
file, then apply this class to the created element.
| minimapCtx.font = 'bold 24px sans-serif'; | ||
| minimapCtx.fillStyle = '#ff2222'; | ||
| minimapCtx.strokeStyle = '#fff'; | ||
| minimapCtx.lineWidth = 2; | ||
| minimapCtx.fillText('🏁', flagPx, flagPy - 10); | ||
| minimapCtx.strokeText('🏁', flagPx, flagPy - 10); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
Replace emoji with canvas-drawn flag for consistent rendering.
Using emoji (🏁) for the flag may render inconsistently across different browsers and operating systems. Consider drawing the flag using canvas primitives for consistent appearance.
-minimapCtx.font = 'bold 24px sans-serif';
-minimapCtx.fillStyle = '#ff2222';
-minimapCtx.strokeStyle = '#fff';
-minimapCtx.lineWidth = 2;
-minimapCtx.fillText('🏁', flagPx, flagPy - 10);
-minimapCtx.strokeText('🏁', flagPx, flagPy - 10);
+// Draw a flag icon using canvas primitives
+minimapCtx.save();
+// Flag pole
+minimapCtx.strokeStyle = '#333';
+minimapCtx.lineWidth = 2;
+minimapCtx.beginPath();
+minimapCtx.moveTo(flagPx, flagPy);
+minimapCtx.lineTo(flagPx, flagPy - 15);
+minimapCtx.stroke();
+// Flag
+minimapCtx.fillStyle = '#ff2222';
+minimapCtx.beginPath();
+minimapCtx.moveTo(flagPx, flagPy - 15);
+minimapCtx.lineTo(flagPx + 10, flagPy - 10);
+minimapCtx.lineTo(flagPx + 10, flagPy - 5);
+minimapCtx.lineTo(flagPx, flagPy - 10);
+minimapCtx.closePath();
+minimapCtx.fill();
+minimapCtx.restore();📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| minimapCtx.font = 'bold 24px sans-serif'; | |
| minimapCtx.fillStyle = '#ff2222'; | |
| minimapCtx.strokeStyle = '#fff'; | |
| minimapCtx.lineWidth = 2; | |
| minimapCtx.fillText('🏁', flagPx, flagPy - 10); | |
| minimapCtx.strokeText('🏁', flagPx, flagPy - 10); | |
| } | |
| if (flagVisible) { | |
| // Draw a flag icon using canvas primitives | |
| minimapCtx.save(); | |
| // Flag pole | |
| minimapCtx.strokeStyle = '#333'; | |
| minimapCtx.lineWidth = 2; | |
| minimapCtx.beginPath(); | |
| minimapCtx.moveTo(flagPx, flagPy); | |
| minimapCtx.lineTo(flagPx, flagPy - 15); | |
| minimapCtx.stroke(); | |
| // Flag | |
| minimapCtx.fillStyle = '#ff2222'; | |
| minimapCtx.beginPath(); | |
| minimapCtx.moveTo(flagPx, flagPy - 15); | |
| minimapCtx.lineTo(flagPx + 10, flagPy - 10); | |
| minimapCtx.lineTo(flagPx + 10, flagPy - 5); | |
| minimapCtx.lineTo(flagPx, flagPy - 10); | |
| minimapCtx.closePath(); | |
| minimapCtx.fill(); | |
| minimapCtx.restore(); | |
| } |
🤖 Prompt for AI Agents
In src/environment.js around lines 314 to 320, replace the emoji flag drawn with
fillText and strokeText with a flag drawn using canvas drawing primitives like
fillRect, strokeRect, and path methods. This will ensure consistent rendering
across browsers by manually drawing the flag shape and colors instead of relying
on emoji rendering.
| minimap.id = 'minimap-canvas'; | ||
| minimap.width = minimapWidth; | ||
| minimap.height = minimapHeight; | ||
| minimap.className = 'minimap-canvas'; // Use CSS class only |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify minimap CSS class is defined in style.css.
The code references a CSS class minimap-canvas that should be defined in the external stylesheet mentioned in the PR summary.
🏁 Script executed:
#!/bin/bash
# Description: Verify that the minimap-canvas CSS class is defined in style.css
# Search for the minimap-canvas class definition
rg -A 5 "\.minimap-canvas" "style.css" || rg -A 5 "\.minimap-canvas"Length of output: 67
🏁 Script executed:
#!/bin/bash
# Locate any style.css files in the repo
fd -e css style.css
# Search all CSS files for the .minimap-canvas class definition
rg -n '\.minimap-canvas' -g '*.css'Length of output: 69
Add missing .minimap-canvas CSS rule
It looks like the class minimap-canvas isn’t defined in your stylesheets, so the element will have no styling. Please add a corresponding rule in style.css (or the appropriate CSS file), for example:
/* style.css */
.minimap-canvas {
/* e.g. */
width: 100px;
height: 50px;
background: #eee;
/* …other styles… */
}• File to update: style.css (or your main CSS file)
• Location: alongside other minimap-related rules
🤖 Prompt for AI Agents
In src/environment.js at line 366, the class 'minimap-canvas' is assigned but
not defined in any CSS file. To fix this, add a '.minimap-canvas' CSS rule in
style.css (or the main CSS file) near other minimap-related styles. Include
properties like width, height, background color, and any other necessary styling
to ensure the minimap element is properly styled.
| if (typeof mazeGoal !== 'undefined') { | ||
| goalFlag.position.set(mazeGoal.x, 0, mazeGoal.y); | ||
| scene.add(goalFlag); |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
Verify mazeGoal is properly accessible in this scope.
The code checks if mazeGoal is defined but doesn't show where it comes from. Ensure it's properly imported or declared as a global variable.
🏁 Script executed:
#!/bin/bash
# Description: Verify where mazeGoal is defined and how it's made available to this module
# Search for mazeGoal definition or export
rg -B 2 -A 2 "mazeGoal\s*=" --type js
# Search for global declaration
rg "global.*mazeGoal|window.*mazeGoal" --type jsLength of output: 90
🏁 Script executed:
#!/bin/bash
# Search for all occurrences of mazeGoal to locate its declaration or import
rg -n "mazeGoal" .
# Check for ES module imports of mazeGoal
rg -n "import.*mazeGoal" -t js -t ts
# Check for ES module exports of mazeGoal
rg -n "export.*mazeGoal" -t js -t tsLength of output: 805
Ensure mazeGoal is defined or imported before use
I didn’t find any declaration, import, or window.mazeGoal assignment in the codebase, yet environment.js (lines 299–301) and main.js reference it as a free/global variable. This will lead to a runtime “undefined” error if mazeGoal isn’t injected externally.
Locations to address:
- src/environment.js (lines 299–301):
// Place at mazeGoal (assume mazeGoal is defined globally) if (typeof mazeGoal !== 'undefined') { goalFlag.position.set(mazeGoal.x, 0, mazeGoal.y); scene.add(goalFlag); }
- src/main.js (lines 134–136) & 311–312: similar checks/use
Suggested fixes:
- Explicitly import or pass
mazeGoalinto these modules. - Or, if it must be global, add at the very start of each file:
and ensure
const { mazeGoal } = window;
window.mazeGoalis set before these scripts run. - Update documentation or initialization script to guarantee
mazeGoalis defined onwindow.
🤖 Prompt for AI Agents
In src/environment.js around lines 299 to 301, mazeGoal is used as a global
variable without any declaration or import, which can cause runtime errors if it
is undefined. To fix this, explicitly import mazeGoal into the module if
possible, or at the start of the file add a line to assign mazeGoal from window
(e.g., const { mazeGoal } = window;) and ensure window.mazeGoal is set before
this script runs. Also apply similar fixes in src/main.js where mazeGoal is
used. Update any initialization or documentation to guarantee mazeGoal is
defined globally before usage.
Summary by CodeRabbit
New Features
Enhancements
Style