nova64 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +25 -8
  2. package/bin/nova64.js +165 -0
  3. package/dist/assets/console-CY_kygm3.js +14 -0
  4. package/dist/assets/console-CY_kygm3.js.map +1 -0
  5. package/dist/assets/main-l0sNRNKZ.js.map +1 -0
  6. package/dist/assets/sky/studio/nx.png +0 -0
  7. package/dist/assets/sky/studio/ny.png +0 -0
  8. package/dist/assets/sky/studio/nz.png +0 -0
  9. package/dist/assets/sky/studio/px.png +0 -0
  10. package/dist/assets/sky/studio/py.png +0 -0
  11. package/dist/assets/sky/studio/pz.png +0 -0
  12. package/dist/assets/vanilla-Dcuy32gi.js +2 -0
  13. package/dist/assets/vanilla-Dcuy32gi.js.map +1 -0
  14. package/dist/console.html +899 -0
  15. package/dist/docs/BENCHMARK.md +77 -0
  16. package/dist/docs/CHEATSHEET.md +255 -0
  17. package/dist/docs/EFFECTS_API_GUIDE.md +577 -0
  18. package/dist/docs/EFFECTS_QUICK_REFERENCE.md +331 -0
  19. package/dist/docs/FONT_CHARACTER_REFERENCE.md +219 -0
  20. package/dist/docs/FREE_GLB_ASSETS.md +330 -0
  21. package/dist/docs/FULLSCREEN_BUTTON_FEATURE.md +296 -0
  22. package/dist/docs/GAMEPAD_SUPPORT.md +348 -0
  23. package/dist/docs/GAME_IMPROVEMENTS.md +278 -0
  24. package/dist/docs/GAME_QUALITY_STATUS.md +300 -0
  25. package/dist/docs/MIGRATION_GUIDE.md +553 -0
  26. package/dist/docs/NOVA64_3D_API.md +356 -0
  27. package/dist/docs/NOVA64_API_REFERENCE.md +1406 -0
  28. package/dist/docs/NOVA64_UI_API.md +503 -0
  29. package/dist/docs/UI_SYSTEM_SUMMARY.md +445 -0
  30. package/dist/docs/VOXEL_ENGINE_GUIDE.md +662 -0
  31. package/dist/docs/VOXEL_QUICK_REFERENCE.md +386 -0
  32. package/dist/docs/api-3d.html +750 -0
  33. package/dist/docs/api-effects.html +385 -0
  34. package/dist/docs/api-improvements.md +121 -0
  35. package/dist/docs/api-skybox.html +407 -0
  36. package/dist/docs/api-sprites.html +321 -0
  37. package/dist/docs/api-voxel.html +337 -0
  38. package/dist/docs/api.html +543 -0
  39. package/dist/docs/assets.html +306 -0
  40. package/dist/docs/audio.html +340 -0
  41. package/dist/docs/blogs.html +286 -0
  42. package/dist/docs/collision.html +316 -0
  43. package/dist/docs/console.html +247 -0
  44. package/dist/docs/editor.html +297 -0
  45. package/dist/docs/font.html +247 -0
  46. package/dist/docs/framebuffer.html +247 -0
  47. package/dist/docs/fullscreen-button.html +297 -0
  48. package/dist/docs/gpu-systems.html +247 -0
  49. package/dist/docs/index.html +580 -0
  50. package/dist/docs/input.html +491 -0
  51. package/dist/docs/physics.html +311 -0
  52. package/dist/docs/screens.html +311 -0
  53. package/dist/docs/storage.html +311 -0
  54. package/dist/docs/textinput.html +332 -0
  55. package/dist/docs/ui.html +488 -0
  56. package/dist/examples/3d-advanced/code.js +695 -0
  57. package/dist/examples/adventure-comic-3d/code.js +342 -0
  58. package/dist/examples/audio-lab/code.js +150 -0
  59. package/dist/examples/boids-flocking/code.js +270 -0
  60. package/dist/examples/crystal-cathedral-3d/code.js +706 -0
  61. package/dist/examples/cyberpunk-city-3d/code.js +1383 -0
  62. package/dist/examples/demoscene/README.md +192 -0
  63. package/dist/examples/demoscene/code.js +1081 -0
  64. package/dist/examples/demoscene/meta.json +21 -0
  65. package/dist/examples/dungeon-crawler-3d/code.js +1117 -0
  66. package/dist/examples/f-zero-nova-3d/code.js +865 -0
  67. package/dist/examples/f-zero-nova-3d/code_old.js +1555 -0
  68. package/dist/examples/fps-demo-3d/code.js +744 -0
  69. package/dist/examples/game-of-life-3d/code.js +338 -0
  70. package/dist/examples/generative-art/code.js +632 -0
  71. package/dist/examples/hello-3d/code.js +325 -0
  72. package/dist/examples/hello-skybox/code.js +183 -0
  73. package/dist/examples/hello-world/code.js +19 -0
  74. package/dist/examples/input-showcase/code.js +109 -0
  75. package/dist/examples/instancing-demo/code.js +315 -0
  76. package/dist/examples/minecraft-demo/code.js +387 -0
  77. package/dist/examples/model-viewer-3d/code.js +114 -0
  78. package/dist/examples/mystical-realm-3d/code.js +1203 -0
  79. package/dist/examples/nature-explorer-3d/code.js +1318 -0
  80. package/dist/examples/particles-demo/code.js +522 -0
  81. package/dist/examples/pbr-showcase/code.js +140 -0
  82. package/dist/examples/physics-demo-3d/code.js +948 -0
  83. package/dist/examples/screen-demo/code.js +267 -0
  84. package/dist/examples/shooter-demo-3d/code.js +1286 -0
  85. package/dist/examples/space-combat-3d/IMPLEMENTATION_SUMMARY.md +109 -0
  86. package/dist/examples/space-combat-3d/README.md +135 -0
  87. package/dist/examples/space-combat-3d/code.js +1332 -0
  88. package/dist/examples/space-harrier-3d/code.js +923 -0
  89. package/dist/examples/star-fox-nova-3d/code.js +1116 -0
  90. package/dist/examples/star-fox-nova-3d/code_backup.js +410 -0
  91. package/dist/examples/star-fox-nova-3d/code_broken.js +1821 -0
  92. package/dist/examples/storage-quest/code.js +209 -0
  93. package/dist/examples/strider-demo-3d/IMPROVEMENT_OPTIONS.md +285 -0
  94. package/dist/examples/strider-demo-3d/cache-test.html +132 -0
  95. package/dist/examples/strider-demo-3d/code-fixed.js +582 -0
  96. package/dist/examples/strider-demo-3d/code-old.js +1537 -0
  97. package/dist/examples/strider-demo-3d/code.js +1462 -0
  98. package/dist/examples/strider-demo-3d/code.js.bak2 +1169 -0
  99. package/dist/examples/strider-demo-3d/fix-game.sh +53 -0
  100. package/dist/examples/super-plumber-64/README.md +128 -0
  101. package/dist/examples/super-plumber-64/code.js +1185 -0
  102. package/dist/examples/super-plumber-64/index.html +88 -0
  103. package/dist/examples/test-2d-overlay/code.js +32 -0
  104. package/dist/examples/test-font/code.js +51 -0
  105. package/dist/examples/test-minimal/code.js +21 -0
  106. package/dist/examples/ui-demo/code.js +306 -0
  107. package/dist/examples/wing-commander-space/README.md +180 -0
  108. package/dist/examples/wing-commander-space/code.js +1285 -0
  109. package/dist/examples/wizardry-3d/CHANGELOG.md +366 -0
  110. package/dist/examples/wizardry-3d/code.js +3928 -0
  111. package/dist/index.html +666 -0
  112. package/dist/os9-shell/assets/index-DIHfrTaW.css +1 -0
  113. package/dist/os9-shell/assets/index-KchE_ngx.js +483 -0
  114. package/dist/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  115. package/dist/os9-shell/index.html +23 -0
  116. package/dist/os9-shell/nova-icon.svg +12 -0
  117. package/index.html +6 -1
  118. package/package.json +37 -32
  119. package/public/assets/sky/studio/nx.png +0 -0
  120. package/public/assets/sky/studio/ny.png +0 -0
  121. package/public/assets/sky/studio/nz.png +0 -0
  122. package/public/assets/sky/studio/px.png +0 -0
  123. package/public/assets/sky/studio/py.png +0 -0
  124. package/public/assets/sky/studio/pz.png +0 -0
  125. package/public/os9-shell/assets/index-KchE_ngx.js +483 -0
  126. package/public/os9-shell/assets/index-KchE_ngx.js.map +1 -0
  127. package/public/os9-shell/index.html +10 -1
  128. package/runtime/api-2d.js +301 -21
  129. package/runtime/api-3d/pbr.js +45 -1
  130. package/runtime/api-3d.js +1 -0
  131. package/runtime/api-effects.js +90 -3
  132. package/runtime/api-gameutils.js +476 -0
  133. package/runtime/api-generative.js +610 -0
  134. package/runtime/api-skybox.js +54 -0
  135. package/runtime/api-voxel.js +139 -28
  136. package/runtime/gpu-threejs.js +13 -9
  137. package/runtime/ui.js +2 -2
  138. package/src/main.js +20 -0
  139. package/public/os9-shell/assets/index-B1Uvacma.js +0 -32825
  140. package/public/os9-shell/assets/index-B1Uvacma.js.map +0 -1
@@ -0,0 +1,1555 @@
1
+ // F-ZERO NOVA - Ultimate 3D Racing Experience
2
+ // Nintendo 64 F-Zero style racing with full GPU acceleration and retro effects
3
+
4
+ // Ensure API functions are available
5
+ if (typeof get3DStats === 'undefined') {
6
+ globalThis.get3DStats = () => ({ meshes: 0, renderer: 'Unknown' });
7
+ }
8
+
9
+ // Screen management
10
+ let gameState = 'start'; // 'start', 'racing'
11
+ let startScreenTime = 0;
12
+ let uiButtons = [];
13
+
14
+ let gameTime = 0;
15
+ let raceTrack = [];
16
+ let player = null;
17
+ let opponents = [];
18
+ let trackPieces = [];
19
+ let particles = [];
20
+ let powerUps = [];
21
+ let lapTime = 0;
22
+ let currentLap = 1;
23
+ let maxLaps = 3;
24
+ let racePosition = 1;
25
+ let speed = 0;
26
+ let maxSpeed = 120;
27
+ let boost = 0;
28
+ let health = 100;
29
+
30
+ // Track configuration
31
+ const TRACK_WIDTH = 20;
32
+ const TRACK_SEGMENTS = 200;
33
+ const TRACK_RADIUS = 150;
34
+ const OPPONENT_COUNT = 7;
35
+
36
+ // Colors (F-Zero inspired)
37
+ const COLORS = {
38
+ player: 0x0088ff,
39
+ opponents: [0xff4444, 0x44ff44, 0xffff44, 0xff44ff, 0x44ffff, 0xff8844, 0x8844ff],
40
+ track: [0x333366, 0x444477, 0x555588],
41
+ powerup: [0xff0080, 0x80ff00, 0x0080ff, 0xff8000],
42
+ particle: [0xff6600, 0x0066ff, 0x66ff00, 0xff0066],
43
+ };
44
+
45
+ export async function init() {
46
+ console.log('🏁 F-ZERO NOVA - Initializing Ultimate 3D Racing...');
47
+
48
+ try {
49
+ // Professional 3D setup
50
+ setCameraPosition(-20, 15, 20);
51
+ setCameraTarget(0, 2, 0);
52
+ setCameraFOV(90); // Wide FOV for racing
53
+
54
+ // Advanced multi-layered lighting system
55
+ setupAdvancedLighting();
56
+
57
+ // Dynamic atmospheric fog with color shifting
58
+ setupDynamicFog();
59
+
60
+ // Professional post-processing pipeline
61
+ setupPostProcessingEffects();
62
+
63
+ await buildRaceTrack();
64
+ createPlayer();
65
+ spawnOpponents();
66
+ spawnPowerUps();
67
+ initRaceParticles();
68
+
69
+ // Initialize start screen
70
+ initStartScreen();
71
+
72
+ console.log('✅ F-ZERO NOVA - Initialization complete!');
73
+ } catch (error) {
74
+ console.error('❌ F-ZERO NOVA - Initialization failed:', error);
75
+ // Fallback initialization
76
+ setCameraPosition(-20, 15, 20);
77
+ setCameraTarget(0, 2, 0);
78
+ setFog(0x220044, 50, 300);
79
+ }
80
+ }
81
+
82
+ function initStartScreen() {
83
+ uiButtons = [];
84
+
85
+ uiButtons.push(
86
+ createButton(
87
+ centerX(240),
88
+ 150,
89
+ 240,
90
+ 60,
91
+ '🏁 START RACE',
92
+ () => {
93
+ console.log('🎯 START RACE CLICKED! Changing gameState to racing...');
94
+ gameState = 'racing';
95
+ // Reset race state
96
+ currentLap = 1;
97
+ lapTime = 0;
98
+ speed = 0;
99
+ health = 100;
100
+ console.log('✅ gameState is now:', gameState);
101
+ },
102
+ {
103
+ normalColor: rgba8(255, 150, 0, 255),
104
+ hoverColor: rgba8(255, 180, 30, 255),
105
+ pressedColor: rgba8(220, 120, 0, 255),
106
+ }
107
+ )
108
+ );
109
+
110
+ uiButtons.push(
111
+ createButton(
112
+ centerX(200),
113
+ 355,
114
+ 200,
115
+ 45,
116
+ '🎮 CONTROLS',
117
+ () => {
118
+ console.log('Controls: ARROWS=Steer/Accelerate, SPACE=Boost, Z=Brake');
119
+ },
120
+ {
121
+ normalColor: rgba8(100, 200, 255, 255),
122
+ hoverColor: rgba8(130, 220, 255, 255),
123
+ pressedColor: rgba8(70, 170, 230, 255),
124
+ }
125
+ )
126
+ );
127
+ }
128
+
129
+ export function update(dt) {
130
+ gameTime += dt;
131
+
132
+ if (gameState === 'start') {
133
+ startScreenTime += dt;
134
+ updateAllButtons();
135
+
136
+ // Animate scene in background
137
+ updateOpponents(dt);
138
+ updateTrack(dt);
139
+ updateParticles(dt);
140
+ updateAdvancedLighting(dt);
141
+
142
+ // Cinematic camera orbit
143
+ const angle = gameTime * 0.3;
144
+ setCameraPosition(Math.cos(angle) * 30, 18, Math.sin(angle) * 30);
145
+ setCameraTarget(0, 2, 0);
146
+ return;
147
+ }
148
+
149
+ lapTime += dt;
150
+
151
+ handleInput(dt);
152
+ updatePlayer(dt);
153
+ updateOpponents(dt);
154
+ updateTrack(dt);
155
+ updateParticles(dt);
156
+ updatePowerUps(dt);
157
+ updateCamera(dt);
158
+ updateRaceLogic(dt);
159
+
160
+ // Advanced visual effects
161
+ updateAdvancedLighting(dt);
162
+ updatePostProcessing(dt);
163
+ updateTrackLighting(dt);
164
+ }
165
+
166
+ export function draw() {
167
+ if (gameState === 'start') {
168
+ drawStartScreen();
169
+ return;
170
+ }
171
+
172
+ // 3D scene automatically rendered
173
+ try {
174
+ drawRacingHUD();
175
+ } catch (error) {
176
+ console.error('Error in F-Zero draw function:', error);
177
+ // Fallback minimal HUD
178
+ cls();
179
+ print('F-ZERO NOVA', 10, 10, rgba8(255, 255, 255, 255));
180
+ print('Error: Reloading...', 10, 30, rgba8(255, 0, 0, 255));
181
+ }
182
+ }
183
+
184
+ function drawStartScreen() {
185
+ // Racing gradient background (orange to red)
186
+ drawGradientRect(0, 0, 640, 360, rgba8(80, 30, 10, 235), rgba8(40, 15, 5, 250), true);
187
+
188
+ // Animated title with speed effect
189
+ setFont('huge');
190
+ setTextAlign('center');
191
+ const speed = Math.sin(startScreenTime * 5) * 0.4 + 0.6;
192
+ const speedColor = rgba8(255, Math.floor(speed * 180), 0, 255);
193
+
194
+ const offset = Math.sin(startScreenTime * 8) * 5;
195
+ drawTextShadow('F-ZERO', 320 + offset, 50, speedColor, rgba8(0, 0, 0, 255), 8, 1);
196
+ drawTextShadow('NOVA', 320, 110, rgba8(100, 200, 255, 255), rgba8(0, 0, 0, 255), 8, 1);
197
+
198
+ // Subtitle
199
+ setFont('large');
200
+ const pulse = Math.sin(startScreenTime * 6) * 0.2 + 0.8;
201
+ drawTextOutline(
202
+ '🏁 Extreme 3D Racing 🏁',
203
+ 320,
204
+ 170,
205
+ rgba8(255, 255, 0, Math.floor(pulse * 255)),
206
+ rgba8(0, 0, 0, 255),
207
+ 1
208
+ );
209
+
210
+ // Info panel
211
+ const panel = createPanel(centerX(480), 215, 480, 185, {
212
+ bgColor: rgba8(30, 15, 10, 220),
213
+ borderColor: rgba8(255, 150, 0, 255),
214
+ borderWidth: 3,
215
+ shadow: true,
216
+ gradient: true,
217
+ gradientColor: rgba8(50, 25, 15, 220),
218
+ });
219
+ drawPanel(panel);
220
+
221
+ setFont('normal');
222
+ setTextAlign('center');
223
+ drawText('RACE SPECIFICATIONS', 320, 235, rgba8(255, 150, 0, 255), 1);
224
+
225
+ setFont('small');
226
+ drawText('🏁 High-speed futuristic racing', 320, 260, uiColors.light, 1);
227
+ drawText('🏁 Maximum speed: 120 km/h with boost', 320, 275, uiColors.light, 1);
228
+ drawText('🏁 3 laps against 7 AI opponents', 320, 290, uiColors.light, 1);
229
+ drawText('🏁 Nintendo 64 F-Zero inspired graphics', 320, 305, uiColors.light, 1);
230
+
231
+ setFont('tiny');
232
+ drawText('ARROWS: Steer/Accelerate | SPACE: Boost | Z: Brake', 320, 325, uiColors.secondary, 1);
233
+
234
+ // Draw buttons
235
+ drawAllButtons();
236
+
237
+ // Pulsing prompt
238
+ const alpha = Math.floor((Math.sin(startScreenTime * 7) * 0.5 + 0.5) * 255);
239
+ setFont('normal');
240
+ drawText('🏁 PREPARE FOR MAXIMUM VELOCITY 🏁', 320, 435, rgba8(255, 200, 0, alpha), 1);
241
+
242
+ // Info
243
+ setFont('tiny');
244
+ drawText('Advanced 3D Racing Engine', 320, 350, rgba8(200, 150, 100, 150), 1);
245
+ }
246
+
247
+ async function buildRaceTrack() {
248
+ // Create main track loop with elevation changes and banking
249
+ for (let i = 0; i < TRACK_SEGMENTS; i++) {
250
+ const angle = (i / TRACK_SEGMENTS) * Math.PI * 2;
251
+ const nextAngle = ((i + 1) / TRACK_SEGMENTS) * Math.PI * 2;
252
+
253
+ // Track position with curves and elevation
254
+ const x = Math.cos(angle) * TRACK_RADIUS;
255
+ const z = Math.sin(angle) * TRACK_RADIUS;
256
+ const y = Math.sin(angle * 3) * 8 + Math.cos(angle * 1.5) * 4; // Elevation changes
257
+
258
+ const nextX = Math.cos(nextAngle) * TRACK_RADIUS;
259
+ const nextZ = Math.sin(nextAngle) * TRACK_RADIUS;
260
+ const nextY = Math.sin(nextAngle * 3) * 8 + Math.cos(nextAngle * 1.5) * 4;
261
+
262
+ // Calculate banking angle
263
+ const bankingAngle = Math.sin(angle * 2) * 0.3;
264
+
265
+ // Create track segment
266
+ const segment = createTrackSegment(x, y, z, nextX, nextY, nextZ, bankingAngle, i);
267
+ trackPieces.push(segment);
268
+
269
+ // Add track decorations with lighting
270
+ if (i % 10 === 0) {
271
+ await createTrackDecorations(x, y, z, angle);
272
+ }
273
+
274
+ // Add lighting elements along the track
275
+ if (i % 5 === 0) {
276
+ createTrackLighting(x, y, z, angle, i);
277
+ }
278
+
279
+ // Start/finish line with special lighting
280
+ if (i === 0) {
281
+ createStartFinishLine(x, y, z, angle);
282
+ createStartLineLighting(x, y, z, angle);
283
+ }
284
+
285
+ // Boost pads
286
+ if (i % 25 === 0) {
287
+ createBoostPad(x, y + 0.5, z, angle);
288
+ }
289
+ }
290
+
291
+ // Create outer barriers
292
+ for (let i = 0; i < TRACK_SEGMENTS; i++) {
293
+ const angle = (i / TRACK_SEGMENTS) * Math.PI * 2;
294
+ const outerRadius = TRACK_RADIUS + TRACK_WIDTH;
295
+ const innerRadius = TRACK_RADIUS - TRACK_WIDTH;
296
+
297
+ const x = Math.cos(angle) * TRACK_RADIUS;
298
+ const z = Math.sin(angle) * TRACK_RADIUS;
299
+ const y = Math.sin(angle * 3) * 8 + Math.cos(angle * 1.5) * 4;
300
+
301
+ // Outer barrier
302
+ const outerX = Math.cos(angle) * outerRadius;
303
+ const outerZ = Math.sin(angle) * outerRadius;
304
+ const outerBarrier = createCube(2, 6, 2, 0x666688, [outerX, y + 3, outerZ]);
305
+
306
+ // Inner barrier
307
+ const innerX = Math.cos(angle) * innerRadius;
308
+ const innerZ = Math.sin(angle) * innerRadius;
309
+ const innerBarrier = createCube(2, 6, 2, 0x666688, [innerX, y + 3, innerZ]);
310
+
311
+ // Add neon lighting to barriers
312
+ if (i % 5 === 0) {
313
+ const neonColor = COLORS.powerup[i % COLORS.powerup.length];
314
+ const neonOuter = createCube(2.2, 0.5, 2.2, neonColor, [outerX, y + 5.5, outerZ]);
315
+ const neonInner = createCube(2.2, 0.5, 2.2, neonColor, [innerX, y + 5.5, innerZ]);
316
+ }
317
+ }
318
+ }
319
+
320
+ function createTrackSegment(x, y, z, nextX, nextY, nextZ, banking, index) {
321
+ // Main track surface
322
+ const centerX = (x + nextX) / 2;
323
+ const centerY = (y + nextY) / 2;
324
+ const centerZ = (z + nextZ) / 2;
325
+
326
+ const length = Math.sqrt((nextX - x) ** 2 + (nextY - y) ** 2 + (nextZ - z) ** 2);
327
+ const angle = Math.atan2(nextZ - z, nextX - x);
328
+
329
+ const trackColor = COLORS.track[index % COLORS.track.length];
330
+ const segment = createCube(length, 0.5, TRACK_WIDTH, trackColor, [centerX, centerY, centerZ]);
331
+
332
+ setRotation(segment, 0, angle, banking);
333
+
334
+ // Racing line markers
335
+ if (index % 8 === 0) {
336
+ const marker = createCube(length, 0.1, 1, 0xffffff, [centerX, centerY + 0.3, centerZ]);
337
+ setRotation(marker, 0, angle, banking);
338
+ }
339
+
340
+ return {
341
+ mesh: segment,
342
+ x: centerX,
343
+ y: centerY,
344
+ z: centerZ,
345
+ angle: angle,
346
+ banking: banking,
347
+ index: index,
348
+ };
349
+ }
350
+
351
+ async function createTrackDecorations(x, y, z, angle) {
352
+ // Grandstands
353
+ if (Math.random() < 0.3) {
354
+ const standX = x + Math.cos(angle + Math.PI / 2) * 40;
355
+ const standZ = z + Math.sin(angle + Math.PI / 2) * 40;
356
+ const grandstand = createCube(20, 12, 8, 0x555577, [standX, y + 6, standZ]);
357
+
358
+ // Stadium lights
359
+ for (let i = 0; i < 4; i++) {
360
+ const lightX = standX + (i - 1.5) * 6;
361
+ const light = createSphere(1, 0xffffaa, [lightX, y + 18, standZ]);
362
+ }
363
+ }
364
+
365
+ // Sponsor banners
366
+ if (Math.random() < 0.4) {
367
+ const bannerX = x + Math.cos(angle + Math.PI / 2) * 25;
368
+ const bannerZ = z + Math.sin(angle + Math.PI / 2) * 25;
369
+ const banner = createCube(
370
+ 12,
371
+ 4,
372
+ 0.5,
373
+ COLORS.powerup[Math.floor(Math.random() * COLORS.powerup.length)],
374
+ [bannerX, y + 8, bannerZ]
375
+ );
376
+ setRotation(banner, 0, angle, 0);
377
+ }
378
+ }
379
+
380
+ function createStartFinishLine(x, y, z, angle) {
381
+ // Checkered pattern start/finish
382
+ for (let i = 0; i < 8; i++) {
383
+ const stripX = x + Math.cos(angle + Math.PI / 2) * (i - 4) * 2.5;
384
+ const stripZ = z + Math.sin(angle + Math.PI / 2) * (i - 4) * 2.5;
385
+ const color = i % 2 === 0 ? 0xffffff : 0x000000;
386
+ const strip = createCube(2, 0.1, 2, color, [stripX, y + 0.3, stripZ]);
387
+ }
388
+
389
+ // Start gantry
390
+ const gantry = createCube(TRACK_WIDTH * 2, 8, 2, 0x888899, [x, y + 8, z]);
391
+ setRotation(gantry, 0, angle + Math.PI / 2, 0);
392
+
393
+ // Digital display
394
+ const display = createCube(16, 4, 0.5, 0x002200, [x, y + 10, z + 1]);
395
+ setRotation(display, 0, angle + Math.PI / 2, 0);
396
+ }
397
+
398
+ function createBoostPad(x, y, z, angle) {
399
+ // Glowing boost pad
400
+ const pad = createCube(8, 0.3, 4, 0x00ff88, [x, y, z]);
401
+ setRotation(pad, 0, angle + Math.PI / 2, 0);
402
+
403
+ // Energy field effect
404
+ const field = createCube(8.5, 1, 4.5, 0x0088ff, [x, y + 0.5, z]);
405
+ setRotation(field, 0, angle + Math.PI / 2, 0);
406
+
407
+ trackPieces.push({
408
+ type: 'boost',
409
+ mesh: pad,
410
+ field: field,
411
+ x: x,
412
+ y: y,
413
+ z: z,
414
+ angle: angle,
415
+ });
416
+ }
417
+
418
+ function createPlayer() {
419
+ // Sleek F-Zero style racer
420
+ const body = createCube(4, 0.8, 2, COLORS.player, [0, 2, 0]);
421
+ const cockpit = createSphere(1.2, 0x004488, [0, 2.5, 0.3]);
422
+ const wing = createCube(6, 0.2, 0.8, 0x0066aa, [0, 2.8, -1.5]);
423
+
424
+ // Hover engines
425
+ const engine1 = createCube(0.8, 0.6, 1.5, 0xff4400, [-1.5, 1.5, -0.8]);
426
+ const engine2 = createCube(0.8, 0.6, 1.5, 0xff4400, [1.5, 1.5, -0.8]);
427
+
428
+ // Underglow
429
+ const glow = createCube(4.5, 0.2, 2.5, 0x00aaff, [0, 1, 0]);
430
+
431
+ player = {
432
+ body: body,
433
+ cockpit: cockpit,
434
+ wing: wing,
435
+ engines: [engine1, engine2],
436
+ glow: glow,
437
+ x: 0,
438
+ y: 2,
439
+ z: -TRACK_RADIUS,
440
+ vx: 0,
441
+ vy: 0,
442
+ vz: 0,
443
+ rotation: 0,
444
+ tilt: 0,
445
+ trackPosition: 0,
446
+ lanePosition: 0,
447
+ speed: 0,
448
+ health: 100,
449
+ boost: 0,
450
+ };
451
+ }
452
+
453
+ function spawnOpponents() {
454
+ for (let i = 0; i < OPPONENT_COUNT; i++) {
455
+ const opponent = createOpponent(i);
456
+ opponents.push(opponent);
457
+ }
458
+ }
459
+
460
+ function createOpponent(index) {
461
+ const color = COLORS.opponents[index % COLORS.opponents.length];
462
+ const startPos = (index + 1) * -15; // Stagger starting positions
463
+
464
+ const body = createCube(4, 0.8, 2, color, [0, 2, startPos]);
465
+ const cockpit = createSphere(1, color - 0x222222, [0, 2.4, startPos + 0.2]);
466
+ const glow = createCube(4.2, 0.2, 2.2, color + 0x333333, [0, 1, startPos]);
467
+
468
+ return {
469
+ body: body,
470
+ cockpit: cockpit,
471
+ glow: glow,
472
+ x: 0,
473
+ y: 2,
474
+ z: startPos,
475
+ vx: 0,
476
+ vy: 0,
477
+ vz: 0,
478
+ rotation: 0,
479
+ trackPosition: startPos,
480
+ lanePosition: (Math.random() - 0.5) * 8,
481
+ speed: 40 + Math.random() * 30,
482
+ maxSpeed: 80 + Math.random() * 40,
483
+ aiType: Math.random() < 0.5 ? 'aggressive' : 'defensive',
484
+ nextDecision: Math.random() * 2,
485
+ };
486
+ }
487
+
488
+ function spawnPowerUps() {
489
+ for (let i = 0; i < 20; i++) {
490
+ const angle = Math.random() * Math.PI * 2;
491
+ const x = Math.cos(angle) * TRACK_RADIUS;
492
+ const z = Math.sin(angle) * TRACK_RADIUS;
493
+ const y = Math.sin(angle * 3) * 8 + Math.cos(angle * 1.5) * 4 + 3;
494
+
495
+ const type = Math.floor(Math.random() * 4);
496
+ const color = COLORS.powerup[type];
497
+ const powerup = createSphere(1, color, [x, y, z]);
498
+
499
+ powerUps.push({
500
+ mesh: powerup,
501
+ type: ['boost', 'health', 'shield', 'speed'][type],
502
+ x: x,
503
+ y: y,
504
+ z: z,
505
+ rotation: 0,
506
+ active: true,
507
+ });
508
+ }
509
+ }
510
+
511
+ function initRaceParticles() {
512
+ // Create exhaust particles for all vehicles
513
+ for (let i = 0; i < 50; i++) {
514
+ createExhaustParticle(0, 0, 0);
515
+ }
516
+ }
517
+
518
+ function handleInput(dt) {
519
+ const acceleration = 80;
520
+ const steering = 40;
521
+ const braking = 60;
522
+
523
+ // Acceleration
524
+ if (btn(2)) {
525
+ // Up
526
+ speed = Math.min(speed + acceleration * dt, maxSpeed);
527
+ } else {
528
+ speed = Math.max(speed - 20 * dt, 0);
529
+ }
530
+
531
+ // Braking
532
+ if (btn(3)) {
533
+ // Down
534
+ speed = Math.max(speed - braking * dt, 0);
535
+ }
536
+
537
+ // Steering
538
+ if (btn(0)) {
539
+ // Left
540
+ player.lanePosition = Math.max(player.lanePosition - steering * dt, -TRACK_WIDTH / 2);
541
+ player.tilt = Math.max(player.tilt - dt * 3, -0.5);
542
+ } else if (btn(1)) {
543
+ // Right
544
+ player.lanePosition = Math.min(player.lanePosition + steering * dt, TRACK_WIDTH / 2);
545
+ player.tilt = Math.min(player.tilt + dt * 3, 0.5);
546
+ } else {
547
+ player.tilt *= 0.9;
548
+ }
549
+
550
+ // Boost
551
+ if (btnp(5) && boost > 20) {
552
+ // X
553
+ speed += 40;
554
+ boost -= 20;
555
+ createBoostEffect();
556
+ }
557
+ }
558
+
559
+ function updatePlayer(dt) {
560
+ // Update track position
561
+ player.trackPosition += speed * dt;
562
+
563
+ // Wrap around track
564
+ const trackLength = TRACK_SEGMENTS * 10; // Approximate
565
+ if (player.trackPosition >= trackLength) {
566
+ player.trackPosition -= trackLength;
567
+ currentLap++;
568
+ lapTime = 0;
569
+
570
+ // Spectacular lap completion celebration
571
+ createLapCompletionEffect();
572
+ console.log(`🏁 LAP ${currentLap - 1} COMPLETED! Spectacular celebration!`);
573
+ }
574
+
575
+ // Calculate world position from track position
576
+ const normalizedPos = (player.trackPosition / trackLength) * Math.PI * 2;
577
+ const trackX = Math.cos(normalizedPos) * TRACK_RADIUS;
578
+ const trackZ = Math.sin(normalizedPos) * TRACK_RADIUS;
579
+ const trackY = Math.sin(normalizedPos * 3) * 8 + Math.cos(normalizedPos * 1.5) * 4;
580
+
581
+ // Apply lane offset
582
+ const laneX = trackX + Math.cos(normalizedPos + Math.PI / 2) * player.lanePosition;
583
+ const laneZ = trackZ + Math.sin(normalizedPos + Math.PI / 2) * player.lanePosition;
584
+
585
+ player.x = laneX;
586
+ player.y = trackY + 2;
587
+ player.z = laneZ;
588
+ player.rotation = normalizedPos + Math.PI / 2;
589
+
590
+ // Update mesh positions
591
+ setPosition(player.body, player.x, player.y, player.z);
592
+ setPosition(player.cockpit, player.x, player.y + 0.5, player.z + 0.3);
593
+ setPosition(player.wing, player.x, player.y + 0.8, player.z - 1.5);
594
+ setPosition(player.glow, player.x, player.y - 1, player.z);
595
+
596
+ setPosition(player.engines[0], player.x - 1.5, player.y - 0.5, player.z - 0.8);
597
+ setPosition(player.engines[1], player.x + 1.5, player.y - 0.5, player.z - 0.8);
598
+
599
+ // Apply rotations
600
+ setRotation(player.body, player.tilt * 0.2, player.rotation, player.tilt);
601
+ setRotation(player.cockpit, 0, player.rotation, 0);
602
+ setRotation(player.wing, 0, player.rotation, player.tilt * 0.5);
603
+
604
+ // Create exhaust particles
605
+ if (speed > 10) {
606
+ createExhaustParticle(player.x, player.y - 0.5, player.z - 2);
607
+ }
608
+
609
+ // Boost decay
610
+ boost = Math.max(0, boost - 10 * dt);
611
+
612
+ // Speed effects
613
+ if (speed > 80) {
614
+ createSpeedParticles();
615
+ }
616
+ }
617
+
618
+ function updateOpponents(dt) {
619
+ opponents.forEach((opponent, index) => {
620
+ // AI decision making
621
+ opponent.nextDecision -= dt;
622
+ if (opponent.nextDecision <= 0) {
623
+ // Change lane or adjust speed based on AI type
624
+ if (opponent.aiType === 'aggressive') {
625
+ opponent.speed = Math.min(opponent.speed + 10, opponent.maxSpeed);
626
+ opponent.lanePosition += (Math.random() - 0.5) * 8;
627
+ } else {
628
+ opponent.speed = Math.max(opponent.speed - 5, 20);
629
+ // Stay in lane more
630
+ }
631
+
632
+ opponent.lanePosition = Math.max(
633
+ -TRACK_WIDTH / 2,
634
+ Math.min(TRACK_WIDTH / 2, opponent.lanePosition)
635
+ );
636
+ opponent.nextDecision = 1 + Math.random() * 3;
637
+ }
638
+
639
+ // Update position similar to player
640
+ opponent.trackPosition += opponent.speed * dt;
641
+
642
+ const trackLength = TRACK_SEGMENTS * 10;
643
+ if (opponent.trackPosition >= trackLength) {
644
+ opponent.trackPosition -= trackLength;
645
+ }
646
+
647
+ const normalizedPos = (opponent.trackPosition / trackLength) * Math.PI * 2;
648
+ const trackX = Math.cos(normalizedPos) * TRACK_RADIUS;
649
+ const trackZ = Math.sin(normalizedPos) * TRACK_RADIUS;
650
+ const trackY = Math.sin(normalizedPos * 3) * 8 + Math.cos(normalizedPos * 1.5) * 4;
651
+
652
+ const laneX = trackX + Math.cos(normalizedPos + Math.PI / 2) * opponent.lanePosition;
653
+ const laneZ = trackZ + Math.sin(normalizedPos + Math.PI / 2) * opponent.lanePosition;
654
+
655
+ opponent.x = laneX;
656
+ opponent.y = trackY + 2;
657
+ opponent.z = laneZ;
658
+ opponent.rotation = normalizedPos + Math.PI / 2;
659
+
660
+ // Update mesh positions
661
+ setPosition(opponent.body, opponent.x, opponent.y, opponent.z);
662
+ setPosition(opponent.cockpit, opponent.x, opponent.y + 0.4, opponent.z + 0.2);
663
+ setPosition(opponent.glow, opponent.x, opponent.y - 1, opponent.z);
664
+
665
+ setRotation(opponent.body, 0, opponent.rotation, 0);
666
+
667
+ // Occasional exhaust
668
+ if (Math.random() < 0.3) {
669
+ createExhaustParticle(opponent.x, opponent.y - 0.5, opponent.z - 1.5);
670
+ }
671
+ });
672
+ }
673
+
674
+ function updateTrack(dt) {
675
+ // Animate boost pads
676
+ trackPieces.forEach(piece => {
677
+ if (piece.type === 'boost') {
678
+ piece.pulsePhase = (piece.pulsePhase || 0) + dt * 4;
679
+ const intensity = 0.7 + 0.3 * Math.sin(piece.pulsePhase);
680
+ // In real implementation, would modify material emission
681
+ }
682
+ });
683
+ }
684
+
685
+ function updateParticles(dt) {
686
+ for (let i = particles.length - 1; i >= 0; i--) {
687
+ const particle = particles[i];
688
+
689
+ // Physics
690
+ particle.x += particle.vx * dt;
691
+ particle.y += particle.vy * dt;
692
+ particle.z += particle.vz * dt;
693
+
694
+ particle.vy -= 20 * dt; // Gravity
695
+ particle.life -= dt;
696
+
697
+ // Update position
698
+ setPosition(particle.mesh, particle.x, particle.y, particle.z);
699
+
700
+ // Fade out
701
+ const alpha = particle.life / particle.maxLife;
702
+ setScale(particle.mesh, alpha);
703
+
704
+ // Remove dead particles
705
+ if (particle.life <= 0) {
706
+ destroyMesh(particle.mesh);
707
+ particles.splice(i, 1);
708
+ }
709
+ }
710
+ }
711
+
712
+ function updatePowerUps(dt) {
713
+ powerUps.forEach(powerup => {
714
+ if (!powerup.active) return;
715
+
716
+ // Rotation animation
717
+ powerup.rotation += dt * 3;
718
+ setRotation(powerup.mesh, 0, powerup.rotation, 0);
719
+
720
+ // Bob up and down
721
+ const bobY = powerup.y + Math.sin(gameTime * 2 + powerup.x) * 0.5;
722
+ setPosition(powerup.mesh, powerup.x, bobY, powerup.z);
723
+
724
+ // Check collision with player
725
+ const dx = powerup.x - player.x;
726
+ const dy = powerup.y - player.y;
727
+ const dz = powerup.z - player.z;
728
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
729
+
730
+ if (distance < 3) {
731
+ collectPowerUp(powerup);
732
+ }
733
+ });
734
+ }
735
+
736
+ function updateCamera(dt) {
737
+ // Dynamic racing camera
738
+ const followDistance = 8 + speed * 0.1;
739
+ const followHeight = 4 + speed * 0.02;
740
+ const lookAhead = speed * 0.05;
741
+
742
+ const cameraX = player.x - Math.sin(player.rotation) * followDistance;
743
+ const cameraY = player.y + followHeight;
744
+ const cameraZ = player.z - Math.cos(player.rotation) * followDistance;
745
+
746
+ const targetX = player.x + Math.sin(player.rotation) * lookAhead;
747
+ const targetY = player.y + 1;
748
+ const targetZ = player.z + Math.cos(player.rotation) * lookAhead;
749
+
750
+ setCameraPosition(cameraX, cameraY, cameraZ);
751
+ setCameraTarget(targetX, targetY, targetZ);
752
+
753
+ // Field of view based on speed
754
+ const fov = 90 + speed * 0.2;
755
+ setCameraFOV(Math.min(fov, 120));
756
+ }
757
+
758
+ function updateRaceLogic(dt) {
759
+ // Calculate race position
760
+ let position = 1;
761
+ opponents.forEach(opponent => {
762
+ if (opponent.trackPosition > player.trackPosition) {
763
+ position++;
764
+ }
765
+ });
766
+ racePosition = position;
767
+
768
+ // Check for race completion
769
+ if (currentLap > maxLaps) {
770
+ // Race finished!
771
+ console.log(`Race completed! Final position: ${racePosition}`);
772
+ }
773
+ }
774
+
775
+ function collectPowerUp(powerup) {
776
+ switch (powerup.type) {
777
+ case 'boost':
778
+ boost = Math.min(100, boost + 30);
779
+ break;
780
+ case 'health':
781
+ health = Math.min(100, health + 25);
782
+ break;
783
+ case 'shield':
784
+ // Temporary invincibility
785
+ break;
786
+ case 'speed':
787
+ maxSpeed += 10;
788
+ break;
789
+ }
790
+
791
+ // Hide powerup
792
+ powerup.active = false;
793
+ setScale(powerup.mesh, 0);
794
+
795
+ // Create spectacular collection effect
796
+ const pos = getPosition(powerup.mesh);
797
+ if (pos) {
798
+ createPowerUpCollectionEffect(pos[0], pos[1], pos[2], powerup.type);
799
+ }
800
+ createCollectionEffect(powerup.x, powerup.y, powerup.z);
801
+ }
802
+
803
+ function createExhaustParticle(x, y, z) {
804
+ // Dynamic particle color based on speed and boost
805
+ let particleColor;
806
+ const speedFactor = Math.min(speed / maxSpeed, 1.0);
807
+ const boostFactor = boost / 100;
808
+
809
+ if (boostFactor > 0.5) {
810
+ // Boost particles - hot orange/yellow
811
+ particleColor = 0xff6600 + Math.floor(boostFactor * 0x009900);
812
+ } else if (speedFactor > 0.7) {
813
+ // High speed particles - blue to white
814
+ const intensity = Math.floor(speedFactor * 255);
815
+ particleColor = (intensity << 16) | (intensity << 8) | 0xff;
816
+ } else {
817
+ // Normal particles - use original random colors
818
+ particleColor = COLORS.particle[Math.floor(Math.random() * COLORS.particle.length)];
819
+ }
820
+
821
+ const particle = createSphere(0.3, particleColor, [x, y, z]);
822
+
823
+ particles.push({
824
+ mesh: particle,
825
+ x: x,
826
+ y: y,
827
+ z: z,
828
+ vx: (Math.random() - 0.5) * 8,
829
+ vy: Math.random() * 3,
830
+ vz: -Math.random() * 12 - 5,
831
+ life: 1.5,
832
+ maxLife: 1.5,
833
+ type: 'exhaust',
834
+ color: particleColor,
835
+ });
836
+ }
837
+
838
+ function createSpeedParticles() {
839
+ const speedFactor = Math.min(speed / maxSpeed, 1.0);
840
+ const particleCount = Math.floor(3 + speedFactor * 3); // More particles at higher speeds
841
+
842
+ for (let i = 0; i < particleCount; i++) {
843
+ // Dynamic speed particle colors
844
+ const intensity = Math.floor(128 + speedFactor * 127);
845
+ const speedColor = (intensity << 8) | 0xff | (Math.floor(speedFactor * 0xaa) << 16);
846
+
847
+ const particle = createSphere(0.15 + speedFactor * 0.1, speedColor, [
848
+ player.x + (Math.random() - 0.5) * 8,
849
+ player.y + (Math.random() - 0.5) * 3,
850
+ player.z + (Math.random() - 0.5) * 6,
851
+ ]);
852
+
853
+ particles.push({
854
+ mesh: particle,
855
+ x: player.x,
856
+ y: player.y,
857
+ z: player.z,
858
+ vx: (Math.random() - 0.5) * 20,
859
+ vy: (Math.random() - 0.5) * 5,
860
+ vz: -Math.random() * 30 - 10,
861
+ life: 0.8,
862
+ maxLife: 0.8,
863
+ type: 'speed',
864
+ });
865
+ }
866
+ }
867
+
868
+ function createBoostEffect() {
869
+ for (let i = 0; i < 15; i++) {
870
+ const particle = createSphere(0.4, 0x00ffaa, [
871
+ player.x + (Math.random() - 0.5) * 4,
872
+ player.y + (Math.random() - 0.5) * 2,
873
+ player.z - 2,
874
+ ]);
875
+
876
+ particles.push({
877
+ mesh: particle,
878
+ x: player.x,
879
+ y: player.y,
880
+ z: player.z - 2,
881
+ vx: (Math.random() - 0.5) * 15,
882
+ vy: (Math.random() - 0.5) * 8,
883
+ vz: -Math.random() * 20 - 15,
884
+ life: 1.2,
885
+ maxLife: 1.2,
886
+ type: 'boost',
887
+ });
888
+ }
889
+ }
890
+
891
+ function createCollectionEffect(x, y, z) {
892
+ for (let i = 0; i < 10; i++) {
893
+ const particle = createSphere(0.2, 0xffffff, [x, y, z]);
894
+
895
+ particles.push({
896
+ mesh: particle,
897
+ x: x,
898
+ y: y,
899
+ z: z,
900
+ vx: (Math.random() - 0.5) * 12,
901
+ vy: Math.random() * 8,
902
+ vz: (Math.random() - 0.5) * 12,
903
+ life: 1,
904
+ maxLife: 1,
905
+ type: 'collection',
906
+ });
907
+ }
908
+ }
909
+
910
+ function drawRacingHUD() {
911
+ // Main race HUD
912
+ rect(16, 16, 600, 140, rgba8(0, 0, 40, 200), true);
913
+ rect(16, 16, 600, 140, rgba8(0, 150, 255, 120), false);
914
+
915
+ // Title
916
+ print('🏁 F-ZERO NOVA', 24, 24, rgba8(0, 255, 255, 255));
917
+ print('ULTIMATE N64 RACING EXPERIENCE', 24, 40, rgba8(255, 200, 0, 255));
918
+
919
+ // Race stats
920
+ print(`LAP: ${currentLap}/${maxLaps}`, 24, 60, rgba8(255, 255, 100, 255));
921
+ print(`POSITION: ${racePosition}/${OPPONENT_COUNT + 1}`, 24, 76, rgba8(255, 150, 50, 255));
922
+ print(`LAP TIME: ${lapTime.toFixed(1)}s`, 24, 92, rgba8(100, 255, 100, 255));
923
+
924
+ // Vehicle stats
925
+ print(`SPEED: ${speed.toFixed(0)} KM/H`, 200, 60, rgba8(255, 100, 100, 255));
926
+ print(`MAX SPEED: ${maxSpeed}`, 200, 76, rgba8(255, 200, 200, 255));
927
+ print(`BOOST: ${boost.toFixed(0)}%`, 200, 92, rgba8(0, 200, 255, 255));
928
+ print(`HEALTH: ${health.toFixed(0)}%`, 200, 108, rgba8(255, 255, 255, 255));
929
+
930
+ // Technical stats
931
+ print(`TRACK POS: ${player.trackPosition.toFixed(0)}`, 380, 60, rgba8(200, 200, 255, 255));
932
+ print(`LANE: ${player.lanePosition.toFixed(1)}`, 380, 76, rgba8(200, 200, 255, 255));
933
+ print(`TILT: ${player.tilt.toFixed(2)}`, 380, 92, rgba8(200, 200, 255, 255));
934
+
935
+ // 3D Performance & Effects
936
+ const stats = get3DStats();
937
+ if (stats) {
938
+ print(`3D MESHES: ${stats.meshes || 0}`, 500, 60, rgba8(150, 255, 150, 255));
939
+ print(`GPU: ${stats.renderer || 'ThreeJS'}`, 500, 76, rgba8(150, 255, 150, 255));
940
+ }
941
+
942
+ // Visual effects status
943
+ const speedFactor = Math.min(speed / maxSpeed, 1.0);
944
+ const effectsColor = speedFactor > 0.8 ? rgba8(255, 255, 0, 255) : rgba8(150, 255, 150, 255);
945
+ print(`EFFECTS: ${(speedFactor * 100).toFixed(0)}%`, 500, 92, effectsColor);
946
+ print(
947
+ `BLOOM: ${postProcessing.bloom.enabled ? 'ON' : 'OFF'}`,
948
+ 500,
949
+ 108,
950
+ rgba8(255, 150, 255, 255)
951
+ );
952
+
953
+ // Speed gauge (circular)
954
+ const gaugeX = 580;
955
+ const gaugeY = 200;
956
+ const gaugeRadius = 40;
957
+
958
+ // Gauge background
959
+ for (let i = 0; i < 32; i++) {
960
+ const angle = (i / 32) * Math.PI * 2 - Math.PI / 2;
961
+ const x1 = gaugeX + Math.cos(angle) * (gaugeRadius - 5);
962
+ const y1 = gaugeY + Math.sin(angle) * (gaugeRadius - 5);
963
+ const x2 = gaugeX + Math.cos(angle) * gaugeRadius;
964
+ const y2 = gaugeY + Math.sin(angle) * gaugeRadius;
965
+
966
+ const intensity = i < (speed / maxSpeed) * 32 ? 255 : 50;
967
+ const greenIntensity = Math.floor(intensity / 2); // Ensure integer division
968
+ line(x1, y1, x2, y2, rgba8(intensity, greenIntensity, 0, 255));
969
+ }
970
+
971
+ // Speed needle
972
+ const needleAngle = (speed / maxSpeed) * Math.PI * 2 - Math.PI / 2;
973
+ const needleX = gaugeX + Math.cos(needleAngle) * (gaugeRadius - 8);
974
+ const needleY = gaugeY + Math.sin(needleAngle) * (gaugeRadius - 8);
975
+ line(gaugeX, gaugeY, needleX, needleY, rgba8(255, 255, 255, 255));
976
+
977
+ print('SPEED', gaugeX - 15, gaugeY + gaugeRadius + 8, rgba8(255, 255, 255, 255));
978
+
979
+ // Mini-map (track layout)
980
+ const mapX = 500;
981
+ const mapY = 300;
982
+ const mapSize = 80;
983
+
984
+ rect(mapX - mapSize / 2, mapY - mapSize / 2, mapSize, mapSize, rgba8(0, 0, 50, 150), true);
985
+
986
+ // Draw track outline
987
+ for (let i = 0; i < 32; i++) {
988
+ const angle = (i / 32) * Math.PI * 2;
989
+ const x = mapX + Math.cos(angle) * mapSize * 0.3;
990
+ const y = mapY + Math.sin(angle) * mapSize * 0.3;
991
+ pset(x, y, rgba8(100, 100, 255, 200));
992
+ }
993
+
994
+ // Player position
995
+ const playerAngle = (player.trackPosition / (TRACK_SEGMENTS * 10)) * Math.PI * 2;
996
+ const playerMapX = mapX + Math.cos(playerAngle) * mapSize * 0.3;
997
+ const playerMapY = mapY + Math.sin(playerAngle) * mapSize * 0.3;
998
+ pset(playerMapX, playerMapY, rgba8(255, 255, 255, 255));
999
+
1000
+ // Opponent positions
1001
+ opponents.forEach(opponent => {
1002
+ const oppAngle = (opponent.trackPosition / (TRACK_SEGMENTS * 10)) * Math.PI * 2;
1003
+ const oppMapX = mapX + Math.cos(oppAngle) * mapSize * 0.3;
1004
+ const oppMapY = mapY + Math.sin(oppAngle) * mapSize * 0.3;
1005
+ pset(oppMapX, oppMapY, rgba8(255, 100, 100, 200));
1006
+ });
1007
+
1008
+ print('TRACK', mapX - 15, mapY + mapSize / 2 + 8, rgba8(255, 255, 255, 255));
1009
+
1010
+ // Controls
1011
+ print('↑↓ ACCELERATE/BRAKE | ←→ STEER | X BOOST', 24, 340, rgba8(255, 255, 255, 200));
1012
+ print(
1013
+ 'Experience ultimate Nintendo 64 / F-Zero style 3D racing!',
1014
+ 24,
1015
+ 360,
1016
+ rgba8(100, 255, 100, 180)
1017
+ );
1018
+ }
1019
+
1020
+ // ============================================================================
1021
+ // ADVANCED LIGHTING & POST-PROCESSING SYSTEM
1022
+ // ============================================================================
1023
+
1024
+ let lightingSystem = {
1025
+ mainLight: { color: 0xaaaaff, intensity: 1.0, direction: [-0.4, -0.8, -0.3] },
1026
+ ambientLight: { color: 0x444466, intensity: 0.3 },
1027
+ speedLight: { color: 0x00aaff, intensity: 0.0 },
1028
+ boostLight: { color: 0xffaa00, intensity: 0.0 },
1029
+ trackLights: [],
1030
+ dynamicFog: { color: 0x220044, near: 50, far: 300, pulse: 0 },
1031
+ };
1032
+
1033
+ let postProcessing = {
1034
+ bloom: { enabled: true, intensity: 0.8, threshold: 0.6 },
1035
+ motionBlur: { enabled: true, intensity: 0.8 },
1036
+ chromaticAberration: { enabled: true, intensity: 0.3 },
1037
+ vignette: { enabled: true, intensity: 0.4 },
1038
+ scanlines: { enabled: true, intensity: 0.2 },
1039
+ pixelation: { enabled: true, factor: 1.2 },
1040
+ colorGrading: { enabled: true, temperature: 0.1, tint: -0.05 },
1041
+ };
1042
+
1043
+ function setupAdvancedLighting() {
1044
+ console.log('🌟 Setting up advanced lighting system...');
1045
+
1046
+ // Main directional light (sun/moon)
1047
+ if (typeof setLightDirection === 'function') {
1048
+ setLightDirection(...lightingSystem.mainLight.direction);
1049
+ }
1050
+ if (typeof setLightColor === 'function') {
1051
+ setLightColor(lightingSystem.mainLight.color);
1052
+ }
1053
+ if (typeof setLightIntensity === 'function') {
1054
+ setLightIntensity(lightingSystem.mainLight.intensity);
1055
+ }
1056
+
1057
+ // Enhanced ambient lighting
1058
+ if (typeof setAmbientLight === 'function') {
1059
+ setAmbientLight(lightingSystem.ambientLight.color, lightingSystem.ambientLight.intensity);
1060
+ }
1061
+
1062
+ // Add point lights for track illumination
1063
+ setupTrackLighting();
1064
+
1065
+ console.log('✅ Advanced lighting system initialized');
1066
+ }
1067
+
1068
+ function setupTrackLighting() {
1069
+ // Create point lights along the track for dynamic illumination
1070
+ console.log('🔸 Setting up track point lights...');
1071
+
1072
+ for (let i = 0; i < 8; i++) {
1073
+ const angle = (i / 8) * Math.PI * 2;
1074
+ const x = Math.cos(angle) * TRACK_RADIUS;
1075
+ const z = Math.sin(angle) * TRACK_RADIUS;
1076
+ const y = 15; // Elevated light positions
1077
+
1078
+ const lightColor = i % 2 === 0 ? 0x00aaff : 0xaa00ff; // Alternating blue/purple
1079
+
1080
+ if (typeof addPointLight === 'function') {
1081
+ try {
1082
+ const lightId = addPointLight(x, y, z, lightColor, 2.0, 80);
1083
+ lightingSystem.trackLights.push({
1084
+ id: lightId,
1085
+ position: [x, y, z],
1086
+ color: lightColor,
1087
+ intensity: 2.0,
1088
+ baseIntensity: 2.0,
1089
+ pulsePhase: i * 0.5,
1090
+ });
1091
+ console.log(`✅ Point light ${i} created successfully`);
1092
+ } catch (error) {
1093
+ console.warn(`⚠️ Failed to create point light ${i}:`, error);
1094
+ }
1095
+ } else {
1096
+ console.log('🔸 Point lights not supported, using basic lighting');
1097
+ }
1098
+ }
1099
+ }
1100
+
1101
+ function setupDynamicFog() {
1102
+ console.log('🌫️ Setting up dynamic atmospheric fog...');
1103
+
1104
+ // Initial fog setup
1105
+ setFog(
1106
+ lightingSystem.dynamicFog.color,
1107
+ lightingSystem.dynamicFog.near,
1108
+ lightingSystem.dynamicFog.far
1109
+ );
1110
+
1111
+ console.log('✅ Dynamic fog system initialized');
1112
+ }
1113
+
1114
+ function setupPostProcessingEffects() {
1115
+ console.log('🎪 Setting up post-processing pipeline...');
1116
+
1117
+ // Enable core post-processing effects
1118
+ if (typeof enableBloom === 'function' && postProcessing.bloom.enabled) {
1119
+ enableBloom(postProcessing.bloom.intensity);
1120
+ if (typeof setBloomThreshold === 'function') {
1121
+ setBloomThreshold(postProcessing.bloom.threshold);
1122
+ }
1123
+ }
1124
+
1125
+ if (typeof enableMotionBlur === 'function' && postProcessing.motionBlur.enabled) {
1126
+ enableMotionBlur(postProcessing.motionBlur.intensity);
1127
+ }
1128
+
1129
+ if (typeof enablePixelation === 'function' && postProcessing.pixelation.enabled) {
1130
+ enablePixelation(postProcessing.pixelation.factor);
1131
+ }
1132
+
1133
+ if (typeof enableDithering === 'function') {
1134
+ enableDithering(true);
1135
+ }
1136
+
1137
+ // Advanced effects (if available)
1138
+ if (
1139
+ typeof enableChromaticAberration === 'function' &&
1140
+ postProcessing.chromaticAberration.enabled
1141
+ ) {
1142
+ enableChromaticAberration(postProcessing.chromaticAberration.intensity);
1143
+ }
1144
+
1145
+ if (typeof enableVignette === 'function' && postProcessing.vignette.enabled) {
1146
+ enableVignette(postProcessing.vignette.intensity);
1147
+ }
1148
+
1149
+ if (typeof enableScanlines === 'function' && postProcessing.scanlines.enabled) {
1150
+ enableScanlines(postProcessing.scanlines.intensity);
1151
+ }
1152
+
1153
+ if (typeof enableColorGrading === 'function' && postProcessing.colorGrading.enabled) {
1154
+ enableColorGrading(postProcessing.colorGrading.temperature, postProcessing.colorGrading.tint);
1155
+ }
1156
+
1157
+ console.log('✅ Post-processing pipeline initialized');
1158
+ }
1159
+
1160
+ function updateAdvancedLighting(dt) {
1161
+ lightingSystem.dynamicFog.pulse += dt * 2;
1162
+
1163
+ // Dynamic fog color shifting based on speed and time
1164
+ const speedFactor = Math.min(speed / maxSpeed, 1.0);
1165
+ const timeFactor = Math.sin(gameTime * 0.5) * 0.5 + 0.5;
1166
+
1167
+ // Base fog color with speed-based intensity
1168
+ const baseR = 0x22 + Math.floor(speedFactor * 0x44);
1169
+ const baseG = 0x00 + Math.floor(timeFactor * 0x22);
1170
+ const baseB = 0x44 + Math.floor((speedFactor + timeFactor) * 0x33);
1171
+
1172
+ const fogColor = (baseR << 16) | (baseG << 8) | baseB;
1173
+
1174
+ // Dynamic fog distance based on speed
1175
+ const dynamicNear = 50 - speedFactor * 20;
1176
+ const dynamicFar = 300 + speedFactor * 100;
1177
+
1178
+ setFog(fogColor, dynamicNear, dynamicFar);
1179
+
1180
+ // Update track point lights with pulsing effects
1181
+ if (lightingSystem.trackLights && lightingSystem.trackLights.length > 0) {
1182
+ lightingSystem.trackLights.forEach((light, index) => {
1183
+ light.pulsePhase += dt * 3;
1184
+ const pulseIntensity = 0.7 + 0.3 * Math.sin(light.pulsePhase);
1185
+ const newIntensity = light.baseIntensity * pulseIntensity;
1186
+
1187
+ if (typeof updatePointLight === 'function') {
1188
+ try {
1189
+ updatePointLight(
1190
+ light.id,
1191
+ light.position[0],
1192
+ light.position[1],
1193
+ light.position[2],
1194
+ light.color,
1195
+ newIntensity,
1196
+ 80
1197
+ );
1198
+ } catch (error) {
1199
+ // Silently ignore point light update errors
1200
+ }
1201
+ }
1202
+ });
1203
+ }
1204
+
1205
+ // Speed-based lighting effects
1206
+ lightingSystem.speedLight.intensity = Math.min(speedFactor * 0.5, 0.5);
1207
+ lightingSystem.boostLight.intensity = Math.min((boost / 100) * 0.8, 0.8);
1208
+
1209
+ // Boost lighting effect
1210
+ if (boost > 50) {
1211
+ const boostPulse = Math.sin(gameTime * 8) * 0.5 + 0.5;
1212
+ if (typeof setAmbientLight === 'function') {
1213
+ const boostAmbient = 0x444466 + Math.floor(boostPulse * 0x222200);
1214
+ setAmbientLight(boostAmbient, 0.3 + boostPulse * 0.2);
1215
+ }
1216
+ }
1217
+
1218
+ // Main light direction sway for dynamic feel
1219
+ const lightSway = Math.sin(gameTime * 0.3) * 0.1;
1220
+ if (typeof setLightDirection === 'function') {
1221
+ setLightDirection(-0.4 + lightSway, -0.8, -0.3 + lightSway * 0.5);
1222
+ }
1223
+ }
1224
+
1225
+ function updatePostProcessing(dt) {
1226
+ const speedFactor = Math.min(speed / maxSpeed, 1.0);
1227
+
1228
+ // Dynamic motion blur based on speed
1229
+ if (typeof setMotionBlurIntensity === 'function') {
1230
+ const motionBlurIntensity = 0.3 + speedFactor * 0.7;
1231
+ setMotionBlurIntensity(motionBlurIntensity);
1232
+ }
1233
+
1234
+ // Dynamic bloom based on boost and speed
1235
+ if (typeof setBloomIntensity === 'function') {
1236
+ const boostFactor = boost / 100;
1237
+ const bloomIntensity = 0.5 + speedFactor * 0.3 + boostFactor * 0.4;
1238
+ setBloomIntensity(Math.min(bloomIntensity, 1.2));
1239
+ }
1240
+
1241
+ // Chromatic aberration increases with speed
1242
+ if (typeof setChromaticAberrationIntensity === 'function') {
1243
+ const aberrationIntensity = 0.1 + speedFactor * 0.4;
1244
+ setChromaticAberrationIntensity(aberrationIntensity);
1245
+ }
1246
+
1247
+ // Vignette intensity based on health and speed
1248
+ if (typeof setVignetteIntensity === 'function') {
1249
+ const healthFactor = (100 - health) / 100;
1250
+ const vignetteIntensity = 0.2 + healthFactor * 0.5 + speedFactor * 0.2;
1251
+ setVignetteIntensity(Math.min(vignetteIntensity, 0.8));
1252
+ }
1253
+
1254
+ // Scanlines flicker with speed
1255
+ if (typeof setScanlineIntensity === 'function') {
1256
+ const scanlineFlicker = Math.sin(gameTime * 30) * 0.1 + 0.9;
1257
+ const scanlineIntensity = (0.1 + speedFactor * 0.2) * scanlineFlicker;
1258
+ setScanlineIntensity(scanlineIntensity);
1259
+ }
1260
+
1261
+ // Dynamic pixelation for extreme speeds
1262
+ if (typeof setPixelationFactor === 'function') {
1263
+ let pixelationFactor = 1.0;
1264
+ if (speed > 100) {
1265
+ pixelationFactor = 1.2 + (speed - 100) / 100;
1266
+ }
1267
+ setPixelationFactor(Math.min(pixelationFactor, 2.0));
1268
+ }
1269
+
1270
+ // Color grading shifts for different racing conditions
1271
+ if (typeof updateColorGrading === 'function') {
1272
+ const temperature = -0.1 + speedFactor * 0.3; // Warmer at high speeds
1273
+ const tint = -0.05 + Math.sin(gameTime * 0.2) * 0.1; // Subtle color shifting
1274
+ updateColorGrading(temperature, tint);
1275
+ }
1276
+ }
1277
+
1278
+ // ============================================================================
1279
+ // ENHANCED TRACK LIGHTING SYSTEM
1280
+ // ============================================================================
1281
+
1282
+ function createTrackLighting(x, y, z, angle, segmentIndex) {
1283
+ // Create glowing track markers
1284
+ const markerHeight = y + 8;
1285
+ const markerSize = 0.8;
1286
+
1287
+ // Alternating neon colors for track sides
1288
+ const leftColor = segmentIndex % 4 === 0 ? 0x00ffff : 0xff00ff;
1289
+ const rightColor = segmentIndex % 4 === 0 ? 0xff00ff : 0x00ffff;
1290
+
1291
+ // Left side marker
1292
+ const leftX = x + Math.cos(angle + Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
1293
+ const leftZ = z + Math.sin(angle + Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
1294
+ const leftMarker = createCube(leftX, markerHeight, leftZ, markerSize, {
1295
+ material: 'emissive',
1296
+ color: leftColor,
1297
+ emissive: leftColor,
1298
+ transparent: true,
1299
+ opacity: 0.8,
1300
+ });
1301
+
1302
+ // Right side marker
1303
+ const rightX = x + Math.cos(angle - Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
1304
+ const rightZ = z + Math.sin(angle - Math.PI / 2) * (TRACK_WIDTH / 2 + 3);
1305
+ const rightMarker = createCube(rightX, markerHeight, rightZ, markerSize, {
1306
+ material: 'emissive',
1307
+ color: rightColor,
1308
+ emissive: rightColor,
1309
+ transparent: true,
1310
+ opacity: 0.8,
1311
+ });
1312
+
1313
+ // Store markers for animation
1314
+ trackPieces[trackPieces.length - 1].leftMarker = leftMarker;
1315
+ trackPieces[trackPieces.length - 1].rightMarker = rightMarker;
1316
+ trackPieces[trackPieces.length - 1].markerColors = { left: leftColor, right: rightColor };
1317
+ }
1318
+
1319
+ function createStartLineLighting(x, y, z, angle) {
1320
+ // Create spectacular start/finish line lighting
1321
+ for (let i = -3; i <= 3; i++) {
1322
+ const markerX = x + Math.cos(angle + Math.PI / 2) * i * 3;
1323
+ const markerZ = z + Math.sin(angle + Math.PI / 2) * i * 3;
1324
+ const markerY = y + 12;
1325
+
1326
+ const startMarker = createSphere(markerX, markerY, markerZ, 1.2, {
1327
+ material: 'holographic',
1328
+ color: 0xffff00,
1329
+ emissive: 0xaaaa00,
1330
+ transparent: true,
1331
+ opacity: 0.9,
1332
+ });
1333
+
1334
+ // Store for special animation
1335
+ if (!lightingSystem.startLineMarkers) {
1336
+ lightingSystem.startLineMarkers = [];
1337
+ }
1338
+ lightingSystem.startLineMarkers.push({
1339
+ mesh: startMarker,
1340
+ baseY: markerY,
1341
+ pulsePhase: i * 0.5,
1342
+ });
1343
+ }
1344
+
1345
+ // Add rainbow light beams
1346
+ for (let i = 0; i < 5; i++) {
1347
+ const beamColor = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x0088ff][i];
1348
+ const beamX = x + Math.cos(angle + Math.PI / 2) * (i - 2) * 2;
1349
+ const beamZ = z + Math.sin(angle + Math.PI / 2) * (i - 2) * 2;
1350
+
1351
+ const lightBeam = createPlane(beamX, y + 15, beamZ, 1, 25, {
1352
+ material: 'emissive',
1353
+ color: beamColor,
1354
+ emissive: beamColor,
1355
+ transparent: true,
1356
+ opacity: 0.6,
1357
+ });
1358
+ rotateMesh(lightBeam, -Math.PI / 2, angle, 0);
1359
+ }
1360
+ }
1361
+
1362
+ function updateTrackLighting(dt) {
1363
+ // Animate track markers
1364
+ trackPieces.forEach((piece, index) => {
1365
+ if (piece.leftMarker && piece.rightMarker) {
1366
+ const pulsePhase = gameTime * 4 + index * 0.2;
1367
+ const pulseIntensity = 0.6 + 0.4 * Math.sin(pulsePhase);
1368
+
1369
+ // Update marker opacity based on pulse
1370
+ if (typeof setMeshOpacity === 'function') {
1371
+ setMeshOpacity(piece.leftMarker, pulseIntensity);
1372
+ setMeshOpacity(piece.rightMarker, pulseIntensity);
1373
+ }
1374
+
1375
+ // Rotate markers for dynamic effect
1376
+ rotateMesh(piece.leftMarker, 0, dt * 2, 0);
1377
+ rotateMesh(piece.rightMarker, 0, -dt * 2, 0);
1378
+ }
1379
+ });
1380
+
1381
+ // Animate start line markers
1382
+ if (lightingSystem.startLineMarkers) {
1383
+ lightingSystem.startLineMarkers.forEach(marker => {
1384
+ marker.pulsePhase += dt * 3;
1385
+ const bounce = Math.sin(marker.pulsePhase) * 2;
1386
+ const currentPos = getPosition(marker.mesh);
1387
+ if (currentPos) {
1388
+ setPosition(marker.mesh, currentPos[0], marker.baseY + bounce, currentPos[2]);
1389
+ }
1390
+
1391
+ // Rainbow color cycling
1392
+ const hue = (gameTime * 2 + marker.pulsePhase) % (Math.PI * 2);
1393
+ const r = Math.floor((Math.sin(hue) * 0.5 + 0.5) * 255);
1394
+ const g = Math.floor((Math.sin(hue + (Math.PI * 2) / 3) * 0.5 + 0.5) * 255);
1395
+ const b = Math.floor((Math.sin(hue + (Math.PI * 4) / 3) * 0.5 + 0.5) * 255);
1396
+ const rainbowColor = (r << 16) | (g << 8) | b;
1397
+
1398
+ if (typeof setMeshColor === 'function') {
1399
+ setMeshColor(marker.mesh, rainbowColor);
1400
+ }
1401
+ });
1402
+ }
1403
+ }
1404
+
1405
+ // ============================================================================
1406
+ // SPECTACULAR VISUAL EFFECTS SYSTEM
1407
+ // ============================================================================
1408
+
1409
+ function createSpectacularExplosion(x, y, z, size = 1.0, color = 0xff6600) {
1410
+ // Create multi-layered explosion effect
1411
+ const explosionLayers = [
1412
+ { size: size * 0.5, color: 0xffffff, life: 0.3 },
1413
+ { size: size * 1.0, color: 0xffff00, life: 0.6 },
1414
+ { size: size * 1.5, color: 0xff6600, life: 0.9 },
1415
+ { size: size * 2.0, color: 0xff0000, life: 1.2 },
1416
+ { size: size * 2.5, color: 0x880000, life: 1.5 },
1417
+ ];
1418
+
1419
+ explosionLayers.forEach((layer, index) => {
1420
+ setTimeout(() => {
1421
+ const explosionSphere = createSphere(x, y, z, layer.size, {
1422
+ material: 'emissive',
1423
+ color: layer.color,
1424
+ emissive: layer.color,
1425
+ transparent: true,
1426
+ opacity: 0.8,
1427
+ });
1428
+
1429
+ particles.push({
1430
+ mesh: explosionSphere,
1431
+ x: x,
1432
+ y: y,
1433
+ z: z,
1434
+ vx: 0,
1435
+ vy: 0,
1436
+ vz: 0,
1437
+ life: layer.life,
1438
+ maxLife: layer.life,
1439
+ type: 'explosion',
1440
+ expandRate: layer.size * 2,
1441
+ });
1442
+ }, index * 50);
1443
+ });
1444
+
1445
+ // Create particle shower
1446
+ for (let i = 0; i < 20; i++) {
1447
+ const sparkParticle = createSphere(0.1, 0xffff00, [x, y, z]);
1448
+ particles.push({
1449
+ mesh: sparkParticle,
1450
+ x: x,
1451
+ y: y,
1452
+ z: z,
1453
+ vx: (Math.random() - 0.5) * 20,
1454
+ vy: Math.random() * 15 + 5,
1455
+ vz: (Math.random() - 0.5) * 20,
1456
+ life: 2.0,
1457
+ maxLife: 2.0,
1458
+ type: 'spark',
1459
+ });
1460
+ }
1461
+ }
1462
+
1463
+ function createPowerUpCollectionEffect(x, y, z, powerUpType) {
1464
+ const colors = {
1465
+ boost: [0xff6600, 0xffaa00, 0xffff00],
1466
+ health: [0x00ff00, 0x44ff44, 0x88ff88],
1467
+ shield: [0x0088ff, 0x44aaff, 0x88ccff],
1468
+ speed: [0xff0088, 0xff44aa, 0xff88cc],
1469
+ };
1470
+
1471
+ const typeColors = colors[powerUpType] || colors['boost'];
1472
+
1473
+ // Create expanding rings
1474
+ for (let ring = 0; ring < 3; ring++) {
1475
+ setTimeout(() => {
1476
+ const ringMesh = createSphere(x, y, z, ring + 1, {
1477
+ material: 'holographic',
1478
+ color: typeColors[ring],
1479
+ emissive: typeColors[ring],
1480
+ transparent: true,
1481
+ opacity: 0.6,
1482
+ wireframe: true,
1483
+ });
1484
+
1485
+ particles.push({
1486
+ mesh: ringMesh,
1487
+ x: x,
1488
+ y: y,
1489
+ z: z,
1490
+ vx: 0,
1491
+ vy: 2,
1492
+ vz: 0,
1493
+ life: 1.5,
1494
+ maxLife: 1.5,
1495
+ type: 'collection_ring',
1496
+ expandRate: 3,
1497
+ });
1498
+ }, ring * 100);
1499
+ }
1500
+
1501
+ // Create upward energy stream
1502
+ for (let i = 0; i < 10; i++) {
1503
+ const energyParticle = createSphere(0.2, typeColors[i % typeColors.length], [
1504
+ x + (Math.random() - 0.5) * 2,
1505
+ y,
1506
+ z + (Math.random() - 0.5) * 2,
1507
+ ]);
1508
+
1509
+ particles.push({
1510
+ mesh: energyParticle,
1511
+ x: x,
1512
+ y: y,
1513
+ z: z,
1514
+ vx: (Math.random() - 0.5) * 2,
1515
+ vy: Math.random() * 8 + 4,
1516
+ vz: (Math.random() - 0.5) * 2,
1517
+ life: 2.0,
1518
+ maxLife: 2.0,
1519
+ type: 'energy_stream',
1520
+ });
1521
+ }
1522
+ }
1523
+
1524
+ function createLapCompletionEffect() {
1525
+ // Spectacular lap completion celebration
1526
+ const celebrationColors = [0xff0000, 0xff8800, 0xffff00, 0x00ff00, 0x0088ff, 0x8800ff];
1527
+
1528
+ for (let i = 0; i < 30; i++) {
1529
+ setTimeout(() => {
1530
+ const fireworkParticle = createSphere(0.3, celebrationColors[i % celebrationColors.length], [
1531
+ player.x + (Math.random() - 0.5) * 20,
1532
+ player.y + Math.random() * 15 + 10,
1533
+ player.z + (Math.random() - 0.5) * 20,
1534
+ ]);
1535
+
1536
+ particles.push({
1537
+ mesh: fireworkParticle,
1538
+ x: player.x,
1539
+ y: player.y + 15,
1540
+ z: player.z,
1541
+ vx: (Math.random() - 0.5) * 15,
1542
+ vy: Math.random() * 10 + 5,
1543
+ vz: (Math.random() - 0.5) * 15,
1544
+ life: 3.0,
1545
+ maxLife: 3.0,
1546
+ type: 'firework',
1547
+ });
1548
+ }, i * 20);
1549
+ }
1550
+
1551
+ // Screen flash effect
1552
+ if (typeof screenFlash === 'function') {
1553
+ screenFlash(0xffffff, 0.3);
1554
+ }
1555
+ }