nova64 0.2.4 → 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 +24 -1
  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,1383 @@
1
+ // CYBERPUNK CITY 3D - Ultimate Nintendo 64 Style 3D City
2
+ // Full GPU-accelerated 3D world with dynamic lighting, flying vehicles, and retro effects
3
+
4
+ let gameTime = 0;
5
+ let cityObjects = [];
6
+ let vehicles = [];
7
+ let particles = [];
8
+ let buildings = [];
9
+ let player = null;
10
+ let camera = { x: 0, y: 20, z: 30, targetX: 0, targetY: 10, targetZ: 0 };
11
+ let cityLights = [];
12
+ let neonSigns = [];
13
+ let flying = false;
14
+ let speed = 0;
15
+
16
+ // Screen management
17
+ let gameState = 'start'; // 'start', 'exploring'
18
+ let startScreenTime = 0;
19
+ let uiButtons = [];
20
+
21
+ let dataPackets = [];
22
+ let playerScore = 0;
23
+
24
+ // Mission system
25
+ let currentMission = null;
26
+ let missionIndex = 0;
27
+ let missionTimer = 0;
28
+ let missionMsg = '';
29
+ let missionMsgTimer = 0;
30
+
31
+ // Security drones
32
+ let drones = [];
33
+ let droneProjectiles = [];
34
+
35
+ // Player combat
36
+ let playerHealth = 100;
37
+ let playerMaxHealth = 100;
38
+ let playerDmgCD = 0;
39
+ let dronesDestroyed = 0;
40
+ let shake = null;
41
+ let checkpointMesh = null;
42
+ let checkpointGlow = null;
43
+
44
+ function spawnPackets() {
45
+ for (let i = 0; i < 20; i++) {
46
+ const x = (Math.random() - 0.5) * CITY_SIZE * 1.5;
47
+ const y = 3 + Math.random() * 20;
48
+ const z = (Math.random() - 0.5) * CITY_SIZE * 1.5;
49
+ const mesh = createAdvancedCube(2, { material: 'emissive', emissive: 0x00ff00, intensity: 3 }, [
50
+ x,
51
+ y,
52
+ z,
53
+ ]);
54
+ dataPackets.push({ mesh, x, y, z, active: true, offset: Math.random() * 10 });
55
+ }
56
+ }
57
+
58
+ function spawnDrones() {
59
+ for (let i = 0; i < 8; i++) {
60
+ const x = (Math.random() - 0.5) * CITY_SIZE * 1.2;
61
+ const y = 8 + Math.random() * 15;
62
+ const z = (Math.random() - 0.5) * CITY_SIZE * 1.2;
63
+ const body = createCube(1.5, 0.6, 1.5, 0xff2222, [x, y, z]);
64
+ const glow = createCube(1.8, 0.3, 1.8, 0xff0000, [x, y - 0.4, z]);
65
+ drones.push({
66
+ body,
67
+ glow,
68
+ x,
69
+ y,
70
+ z,
71
+ vx: 0,
72
+ vy: 0,
73
+ vz: 0,
74
+ hp: 60,
75
+ alive: true,
76
+ shootCD: 2 + Math.random() * 2,
77
+ target: { x: (Math.random() - 0.5) * CITY_SIZE, y, z: (Math.random() - 0.5) * CITY_SIZE },
78
+ waypointTimer: 3 + Math.random() * 4,
79
+ respawnTimer: 0,
80
+ });
81
+ }
82
+ }
83
+
84
+ function updateDrones(dt) {
85
+ for (let i = 0; i < drones.length; i++) {
86
+ const d = drones[i];
87
+ if (!d.alive) {
88
+ d.respawnTimer -= dt;
89
+ if (d.respawnTimer <= 0) {
90
+ d.x = (Math.random() - 0.5) * CITY_SIZE * 1.2;
91
+ d.y = 8 + Math.random() * 15;
92
+ d.z = (Math.random() - 0.5) * CITY_SIZE * 1.2;
93
+ d.body = createCube(1.5, 0.6, 1.5, 0xff2222, [d.x, d.y, d.z]);
94
+ d.glow = createCube(1.8, 0.3, 1.8, 0xff0000, [d.x, d.y - 0.4, d.z]);
95
+ d.hp = 60;
96
+ d.alive = true;
97
+ d.shootCD = 2 + Math.random() * 2;
98
+ }
99
+ continue;
100
+ }
101
+
102
+ const dx = player.x - d.x;
103
+ const dy = player.y - d.y;
104
+ const dz = player.z - d.z;
105
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
106
+
107
+ if (dist < 30) {
108
+ d.vx += (dx / dist) * 12 * dt;
109
+ d.vy += (dy / dist) * 12 * dt;
110
+ d.vz += (dz / dist) * 12 * dt;
111
+
112
+ d.shootCD -= dt;
113
+ if (d.shootCD <= 0 && dist < 25) {
114
+ d.shootCD = 1.5 + Math.random();
115
+ const pspd = 30;
116
+ droneProjectiles.push({
117
+ mesh: createSphere(0.3, 0xff4444, [d.x, d.y, d.z]),
118
+ x: d.x,
119
+ y: d.y,
120
+ z: d.z,
121
+ vx: (dx / dist) * pspd,
122
+ vy: (dy / dist) * pspd,
123
+ vz: (dz / dist) * pspd,
124
+ life: 3,
125
+ });
126
+ sfx('laser');
127
+ }
128
+ } else {
129
+ d.waypointTimer -= dt;
130
+ if (d.waypointTimer <= 0) {
131
+ d.target.x = (Math.random() - 0.5) * CITY_SIZE;
132
+ d.target.z = (Math.random() - 0.5) * CITY_SIZE;
133
+ d.target.y = 8 + Math.random() * 15;
134
+ d.waypointTimer = 4 + Math.random() * 4;
135
+ }
136
+ const tx = d.target.x - d.x;
137
+ const ty = d.target.y - d.y;
138
+ const tz = d.target.z - d.z;
139
+ const td = Math.sqrt(tx * tx + ty * ty + tz * tz) || 1;
140
+ d.vx += (tx / td) * 6 * dt;
141
+ d.vy += (ty / td) * 6 * dt;
142
+ d.vz += (tz / td) * 6 * dt;
143
+ }
144
+
145
+ d.vx *= 0.95;
146
+ d.vy *= 0.95;
147
+ d.vz *= 0.95;
148
+ d.x += d.vx * dt;
149
+ d.y += d.vy * dt;
150
+ d.z += d.vz * dt;
151
+ setPosition(d.body, d.x, d.y, d.z);
152
+ setPosition(d.glow, d.x, d.y - 0.4, d.z);
153
+ setRotation(d.body, 0, gameTime * 3, 0);
154
+
155
+ // Player ram damage when boosting
156
+ if (dist < 3 && player.boost > 1.5) {
157
+ d.hp -= 40;
158
+ sfx('hit');
159
+ triggerShake(shake, 0.5);
160
+ if (d.hp <= 0) {
161
+ d.alive = false;
162
+ d.respawnTimer = 8;
163
+ destroyMesh(d.body);
164
+ destroyMesh(d.glow);
165
+ dronesDestroyed++;
166
+ playerScore += 200;
167
+ sfx('explosion');
168
+ for (let j = 0; j < 10; j++) {
169
+ const p = createSphere(0.2, 0xff4400, [d.x, d.y, d.z]);
170
+ particles.push({
171
+ mesh: p,
172
+ x: d.x,
173
+ y: d.y,
174
+ z: d.z,
175
+ vx: (Math.random() - 0.5) * 15,
176
+ vy: (Math.random() - 0.5) * 15,
177
+ vz: (Math.random() - 0.5) * 15,
178
+ life: 1.5,
179
+ maxLife: 1.5,
180
+ type: 'explosion',
181
+ });
182
+ }
183
+ if (currentMission && currentMission.type === 'DRONE_SWEEP') {
184
+ currentMission.progress++;
185
+ }
186
+ playerHealth = Math.min(playerMaxHealth, playerHealth + 10);
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ function updateProjectiles(dt) {
193
+ for (let i = droneProjectiles.length - 1; i >= 0; i--) {
194
+ const p = droneProjectiles[i];
195
+ p.x += p.vx * dt;
196
+ p.y += p.vy * dt;
197
+ p.z += p.vz * dt;
198
+ p.life -= dt;
199
+ setPosition(p.mesh, p.x, p.y, p.z);
200
+
201
+ const dx = p.x - player.x;
202
+ const dy = p.y - player.y;
203
+ const dz = p.z - player.z;
204
+ if (Math.sqrt(dx * dx + dy * dy + dz * dz) < 3 && playerDmgCD <= 0) {
205
+ playerHealth = Math.max(0, playerHealth - 10);
206
+ playerDmgCD = 0.8;
207
+ triggerShake(shake, 0.4);
208
+ sfx('hit');
209
+ destroyMesh(p.mesh);
210
+ droneProjectiles.splice(i, 1);
211
+ if (playerHealth <= 0) {
212
+ playerHealth = playerMaxHealth;
213
+ playerScore = Math.max(0, playerScore - 500);
214
+ missionMsg = 'SYSTEM CRASH - REBOOTING...';
215
+ missionMsgTimer = 3;
216
+ sfx('death');
217
+ }
218
+ continue;
219
+ }
220
+
221
+ if (p.life <= 0) {
222
+ destroyMesh(p.mesh);
223
+ droneProjectiles.splice(i, 1);
224
+ }
225
+ }
226
+ }
227
+
228
+ const MISSION_TYPES = ['DATA_HEIST', 'DRONE_SWEEP', 'SPEED_RUN'];
229
+
230
+ function startNextMission() {
231
+ removeCheckpoint();
232
+ missionIndex++;
233
+ const type = MISSION_TYPES[(missionIndex - 1) % MISSION_TYPES.length];
234
+ const tier = Math.floor((missionIndex - 1) / 3) + 1;
235
+
236
+ if (type === 'DATA_HEIST') {
237
+ currentMission = {
238
+ type,
239
+ name: `DATA HEIST #${missionIndex}`,
240
+ desc: `Collect ${2 + tier} data packets`,
241
+ target: 2 + tier,
242
+ progress: 0,
243
+ timeLimit: 45 + tier * 10,
244
+ reward: 500 * tier,
245
+ };
246
+ respawnPackets(currentMission.target + 3);
247
+ } else if (type === 'DRONE_SWEEP') {
248
+ currentMission = {
249
+ type,
250
+ name: `DRONE SWEEP #${missionIndex}`,
251
+ desc: `Destroy ${1 + tier} drones`,
252
+ target: 1 + tier,
253
+ progress: 0,
254
+ timeLimit: 60 + tier * 15,
255
+ reward: 800 * tier,
256
+ };
257
+ } else {
258
+ const cx = (Math.random() - 0.5) * CITY_SIZE * 0.8;
259
+ const cz = (Math.random() - 0.5) * CITY_SIZE * 0.8;
260
+ currentMission = {
261
+ type,
262
+ name: `SPEED RUN #${missionIndex}`,
263
+ desc: 'Reach the checkpoint!',
264
+ target: 1,
265
+ progress: 0,
266
+ timeLimit: 25 + tier * 5,
267
+ reward: 400 * tier,
268
+ cx,
269
+ cz,
270
+ };
271
+ createCheckpointMarker(cx, 10, cz);
272
+ }
273
+ missionTimer = currentMission.timeLimit;
274
+ missionMsg = `NEW: ${currentMission.name}`;
275
+ missionMsgTimer = 3;
276
+ sfx('powerup');
277
+ }
278
+
279
+ function respawnPackets(count) {
280
+ const active = dataPackets.filter(p => p.active).length;
281
+ const needed = Math.max(0, count - active);
282
+ for (let i = 0; i < needed; i++) {
283
+ const x = (Math.random() - 0.5) * CITY_SIZE * 1.2;
284
+ const y = 3 + Math.random() * 20;
285
+ const z = (Math.random() - 0.5) * CITY_SIZE * 1.2;
286
+ const mesh = createAdvancedCube(2, { material: 'emissive', emissive: 0x00ff00, intensity: 3 }, [
287
+ x,
288
+ y,
289
+ z,
290
+ ]);
291
+ dataPackets.push({ mesh, x, y, z, active: true, offset: Math.random() * 10 });
292
+ }
293
+ }
294
+
295
+ function createCheckpointMarker(x, y, z) {
296
+ checkpointMesh = createCylinder(1, 30, 0x00ffff, [x, y, z], { segments: 6 });
297
+ checkpointGlow = createCylinder(2, 32, 0x004488, [x, y - 1, z], { segments: 6 });
298
+ }
299
+
300
+ function removeCheckpoint() {
301
+ if (checkpointMesh) {
302
+ destroyMesh(checkpointMesh);
303
+ checkpointMesh = null;
304
+ }
305
+ if (checkpointGlow) {
306
+ destroyMesh(checkpointGlow);
307
+ checkpointGlow = null;
308
+ }
309
+ }
310
+
311
+ function updateMission(dt) {
312
+ if (!currentMission) return;
313
+ missionTimer -= dt;
314
+ if (missionMsgTimer > 0) missionMsgTimer -= dt;
315
+
316
+ if (currentMission.progress >= currentMission.target) {
317
+ playerScore += currentMission.reward;
318
+ missionMsg = `COMPLETE! +${currentMission.reward} CREDITS`;
319
+ missionMsgTimer = 3;
320
+ sfx('coin');
321
+ triggerShake(shake, 0.3);
322
+ playerHealth = Math.min(playerMaxHealth, playerHealth + 20);
323
+ const m = currentMission;
324
+ currentMission = null;
325
+ setTimeout(() => startNextMission(), 2500);
326
+ return;
327
+ }
328
+
329
+ if (currentMission.type === 'SPEED_RUN') {
330
+ const dx = player.x - currentMission.cx;
331
+ const dz = player.z - currentMission.cz;
332
+ if (Math.sqrt(dx * dx + dz * dz) < 8) {
333
+ currentMission.progress = 1;
334
+ }
335
+ if (checkpointMesh) setRotation(checkpointMesh, 0, gameTime * 2, 0);
336
+ }
337
+
338
+ if (missionTimer <= 0) {
339
+ missionMsg = 'MISSION FAILED - RETRYING';
340
+ missionMsgTimer = 2;
341
+ sfx('error');
342
+ currentMission.progress = 0;
343
+ missionTimer = currentMission.timeLimit;
344
+ if (currentMission.type === 'SPEED_RUN') {
345
+ removeCheckpoint();
346
+ currentMission.cx = (Math.random() - 0.5) * CITY_SIZE * 0.8;
347
+ currentMission.cz = (Math.random() - 0.5) * CITY_SIZE * 0.8;
348
+ createCheckpointMarker(currentMission.cx, 10, currentMission.cz);
349
+ }
350
+ }
351
+ }
352
+
353
+ // City configuration
354
+ const CITY_SIZE = 100;
355
+ const BUILDING_COUNT = 50;
356
+ // GPU instancing for city grid elements
357
+ let glowSphereInstanceId = null;
358
+ const VEHICLE_COUNT = 12;
359
+ const PARTICLE_COUNT = 200;
360
+
361
+ // 🌈 NEON COLORS - Bright and vibrant!
362
+ const COLORS = {
363
+ building: [0x4a4a7a, 0x5a5a8a, 0x6a6a9a, 0x3a3a6a], // Brighter buildings
364
+ neon: [0xff0099, 0x00ff99, 0x9900ff, 0xffff00, 0x00ffff, 0xff6600], // Intense neons
365
+ neonGlow: [0xff55cc, 0x55ffcc, 0xcc55ff, 0xffffaa, 0xaaffff, 0xffaa66], // Glow halos
366
+ vehicle: [0xff6666, 0x66ff66, 0x6666ff, 0xffff66, 0xff66ff], // Brighter vehicles
367
+ particle: [0xffaa00, 0xaa00ff, 0x00ffaa, 0xff00aa, 0xaaffff, 0xffaaff], // Glowing particles
368
+ underglow: [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00, 0xff0000], // Vehicle underglows
369
+ };
370
+
371
+ export async function init() {
372
+ cls();
373
+
374
+ // Setup dramatic 3D scene
375
+ setCameraPosition(0, 20, 30);
376
+ setCameraTarget(0, 10, 0);
377
+ setCameraFOV(75);
378
+
379
+ // 🌈 BRIGHT NEON LIGHTING - Make it pop!
380
+ setLightDirection(-0.3, -0.7, -0.4);
381
+ setLightColor(0xffaaff); // Bright magenta/pink key light
382
+ setAmbientLight(0x664488); // Much brighter purple ambient
383
+
384
+ // 🌫️ Atmospheric fog with neon tint
385
+ setFog(0x441166, 30, 180); // Purple/magenta fog
386
+
387
+ // 🎨 Enable ALL visual effects for maximum impact
388
+ enablePixelation(1);
389
+ enableDithering(true);
390
+ enableBloom(1.5, 0.3, 0.25); // Strong neon glow
391
+ enableFXAA(); // Anti-aliasing
392
+ enableChromaticAberration(0.003); // Cyberpunk lens distortion
393
+ enableVignette(1.6, 0.85); // Dark vignette for immersion
394
+
395
+ await buildCyberpunkCity();
396
+ createPlayer();
397
+ spawnVehicles();
398
+ initParticleSystem();
399
+ spawnPackets();
400
+ spawnDrones();
401
+ shake = createShake(0.3);
402
+
403
+ // Initialize start screen
404
+ initStartScreen();
405
+
406
+ console.log('🌃 CYBERPUNK CITY 3D - NEON ENHANCED!');
407
+ console.log('WASD: Move | SHIFT: Fly Mode | SPACE: Boost | X: Switch Vehicle');
408
+ }
409
+
410
+ function initStartScreen() {
411
+ uiButtons = [];
412
+
413
+ uiButtons.push(
414
+ createButton(
415
+ centerX(240),
416
+ 150,
417
+ 240,
418
+ 60,
419
+ '▶ ENTER THE CITY ▶',
420
+ () => {
421
+ gameState = 'exploring';
422
+ startNextMission();
423
+ },
424
+ {
425
+ normalColor: rgba8(255, 0, 100, 255),
426
+ hoverColor: rgba8(255, 60, 150, 255),
427
+ pressedColor: rgba8(200, 0, 80, 255),
428
+ }
429
+ )
430
+ );
431
+
432
+ uiButtons.push(
433
+ createButton(
434
+ centerX(200),
435
+ 355,
436
+ 200,
437
+ 45,
438
+ '🎮 CONTROLS',
439
+ () => {
440
+ console.log('Cyberpunk City - WASD: Move, SHIFT: Fly, SPACE: Boost');
441
+ },
442
+ {
443
+ normalColor: rgba8(0, 255, 255, 255),
444
+ hoverColor: rgba8(60, 255, 255, 255),
445
+ pressedColor: rgba8(0, 200, 200, 255),
446
+ }
447
+ )
448
+ );
449
+ }
450
+
451
+ export function update(dt) {
452
+ gameTime += dt;
453
+
454
+ if (gameState === 'start') {
455
+ startScreenTime += dt;
456
+ updateAllButtons();
457
+
458
+ // Animate scene in background
459
+ updateVehicles(dt);
460
+ updateParticles(dt);
461
+ updatePackets(dt);
462
+ updateCityLights(dt);
463
+ updateNeonSigns(dt);
464
+
465
+ // Slow camera orbit
466
+ camera.x = Math.cos(gameTime * 0.2) * 40;
467
+ camera.z = Math.sin(gameTime * 0.2) * 40;
468
+ camera.y = 25;
469
+ setCameraPosition(camera.x, camera.y, camera.z);
470
+ setCameraTarget(0, 10, 0);
471
+ return;
472
+ }
473
+
474
+ handleInput(dt);
475
+ updatePlayer(dt);
476
+ updateVehicles(dt);
477
+ updateParticles(dt);
478
+ updatePackets(dt);
479
+ updateCityLights(dt);
480
+ updateCamera(dt);
481
+ updateNeonSigns(dt);
482
+ updateDrones(dt);
483
+ updateProjectiles(dt);
484
+ updateMission(dt);
485
+ if (shake) updateShake(shake, dt);
486
+ if (playerDmgCD > 0) playerDmgCD -= dt;
487
+ }
488
+
489
+ export function draw() {
490
+ if (gameState === 'start') {
491
+ drawStartScreen();
492
+ return;
493
+ }
494
+
495
+ // 3D scene automatically rendered by GPU
496
+ drawHUD();
497
+ }
498
+
499
+ function drawStartScreen() {
500
+ // Neon gradient background — dual-band
501
+ drawGradient(0, 0, 640, 200, rgba8(50, 10, 50, 235), rgba8(10, 5, 20, 245), 'v');
502
+ drawGradient(0, 200, 640, 160, rgba8(10, 5, 20, 245), rgba8(20, 5, 40, 240), 'v');
503
+
504
+ // Animated noise "digital rain" static
505
+ drawNoise(0, 0, 640, 360, 22, Math.floor(startScreenTime * 30));
506
+
507
+ // Radial spotlight behind title
508
+ drawRadialGradient(320, 88, 200, rgba8(180, 0, 120, 35), rgba8(0, 0, 0, 0));
509
+
510
+ // Neon title with glow effect
511
+ const neonPulse = Math.sin(startScreenTime * 4) * 0.3 + 0.7;
512
+ const pinkNeon = rgba8(255, Math.floor(neonPulse * 100), Math.floor(neonPulse * 200), 255);
513
+ const cyanNeon = rgba8(0, Math.floor(neonPulse * 255), 255, 255);
514
+
515
+ const flicker = Math.random() > 0.95 ? -2 : 0;
516
+ drawGlowTextCentered('CYBERPUNK', 320, 50 + flicker, pinkNeon, rgba8(150, 0, 100, 150), 2);
517
+ drawGlowTextCentered('CITY 3D', 320, 105 + flicker, cyanNeon, rgba8(0, 80, 140, 150), 2);
518
+
519
+ // Glitch subtitle
520
+ const glitch = Math.random() > 0.97 ? Math.floor(Math.random() * 4) - 2 : 0;
521
+ setFont('large');
522
+ setTextAlign('center');
523
+ drawText('▶ Nintendo 64 / PlayStation Style ◀', 320 + glitch, 162, rgba8(255, 255, 0, 255), 1);
524
+
525
+ // Info panel
526
+ const panel = createPanel(centerX(480), 208, 480, 118, {
527
+ bgColor: rgba8(30, 10, 30, 210),
528
+ borderColor: rgba8(255, 0, 100, 255),
529
+ borderWidth: 3,
530
+ shadow: true,
531
+ gradient: true,
532
+ gradientColor: rgba8(50, 20, 50, 210),
533
+ });
534
+ drawPanel(panel);
535
+
536
+ setFont('normal');
537
+ setTextAlign('center');
538
+ drawText('EXPLORE THE NEON METROPOLIS', 320, 225, rgba8(255, 0, 255, 255), 1);
539
+
540
+ setFont('small');
541
+ drawText('▶ 50+ Procedural Buildings with Neon Lights', 320, 247, uiColors.light, 1);
542
+ drawText('▶ Flying Vehicles & Dynamic Particle System', 320, 262, uiColors.light, 1);
543
+ drawText('▶ Full Player Control + Flying Mode', 320, 277, uiColors.light, 1);
544
+ drawText('▶ Retro N64 Effects: Pixelation, Dithering, Bloom', 320, 292, uiColors.light, 1);
545
+
546
+ setFont('tiny');
547
+ drawText('WASD: Move | SHIFT: Fly | SPACE: Boost', 320, 310, uiColors.secondary, 1);
548
+
549
+ // Draw buttons
550
+ drawAllButtons();
551
+
552
+ // Animated neon wave at horizon
553
+ drawWave(0, 348, 640, 7, 0.032, startScreenTime * 2.5, rgba8(255, 0, 255, 110), 2);
554
+ drawWave(0, 353, 640, 5, 0.045, startScreenTime * 2.5 + 1.2, rgba8(0, 255, 255, 85), 2);
555
+
556
+ // Pulsing neon prompt
557
+ const alpha = Math.floor((Math.sin(startScreenTime * 6) * 0.5 + 0.5) * 255);
558
+ setFont('normal');
559
+ drawText('▶ WELCOME TO THE FUTURE ◀', 320, 430, rgba8(255, 0, 255, alpha), 1);
560
+
561
+ // Build info
562
+ setFont('tiny');
563
+ drawText('GPU-Accelerated 3D City Simulation', 320, 338, rgba8(150, 100, 200, 150), 1);
564
+
565
+ // CRT scanlines
566
+ drawScanlines(52, 2);
567
+ }
568
+
569
+ async function buildCyberpunkCity() {
570
+ // 🌃 Create ground with brighter base
571
+ const ground = createPlane(CITY_SIZE * 2, CITY_SIZE * 2, 0x2a2a55, [0, 0, 0]);
572
+ setRotation(ground, -Math.PI / 2, 0, 0);
573
+
574
+ // ⚡ Add BRIGHT NEON grid lines for cyberpunk aesthetic
575
+ for (let i = -CITY_SIZE; i <= CITY_SIZE; i += 10) {
576
+ // Horizontal lines - CYAN neon
577
+ createCube(CITY_SIZE * 2, 0.15, 0.3, 0x00ffff, [0, 0.15, i]);
578
+ // Vertical lines - MAGENTA neon
579
+ createCube(0.3, 0.15, CITY_SIZE * 2, 0xff00ff, [i, 0.15, 0]);
580
+
581
+ // Add glow effect underneath
582
+ createCube(CITY_SIZE * 2, 0.05, 0.5, 0x0088aa, [0, 0.05, i]);
583
+ createCube(0.5, 0.05, CITY_SIZE * 2, 0xaa0088, [i, 0.05, 0]);
584
+ }
585
+
586
+ // ✨ Intersection glow points — GPU instanced (121 spheres → 1 draw call)
587
+ const gridCount = Math.ceil((CITY_SIZE * 2) / 20 + 1);
588
+ const totalGlows = gridCount * gridCount;
589
+ glowSphereInstanceId = createInstancedMesh('sphere', totalGlows, 0x00ffff, {
590
+ size: 0.5,
591
+ segments: 6,
592
+ emissive: 0x008888,
593
+ emissiveIntensity: 1.0,
594
+ });
595
+ let glowIdx = 0;
596
+ for (let i = -CITY_SIZE; i <= CITY_SIZE; i += 20) {
597
+ for (let j = -CITY_SIZE; j <= CITY_SIZE; j += 20) {
598
+ const neonIdx = Math.floor(Math.random() * COLORS.neon.length);
599
+ setInstanceTransform(glowSphereInstanceId, glowIdx, i, 0.5, j);
600
+ setInstanceColor(glowSphereInstanceId, glowIdx, COLORS.neon[neonIdx]);
601
+ glowIdx++;
602
+ }
603
+ }
604
+ finalizeInstances(glowSphereInstanceId);
605
+
606
+ // Generate procedural buildings
607
+ for (let i = 0; i < BUILDING_COUNT; i++) {
608
+ await createBuilding(i);
609
+ }
610
+
611
+ // Create central megastructure
612
+ await createMegaStructure();
613
+
614
+ // Add flying platforms
615
+ for (let i = 0; i < 8; i++) {
616
+ const angle = (i / 8) * Math.PI * 2;
617
+ const radius = 40;
618
+ const platform = createCube(8, 1, 8, 0x666699, [
619
+ Math.cos(angle) * radius,
620
+ 15 + Math.sin(gameTime + i) * 3,
621
+ Math.sin(angle) * radius,
622
+ ]);
623
+
624
+ // Add neon underglow
625
+ const glow = createCube(8.5, 0.2, 8.5, COLORS.neon[i % COLORS.neon.length], [
626
+ Math.cos(angle) * radius,
627
+ 14.5 + Math.sin(gameTime + i) * 3,
628
+ Math.sin(angle) * radius,
629
+ ]);
630
+
631
+ cityObjects.push({ type: 'platform', mesh: platform, glow: glow, angle: angle, index: i });
632
+ }
633
+ }
634
+
635
+ async function createBuilding(index) {
636
+ const x = (Math.random() - 0.5) * CITY_SIZE * 1.5;
637
+ const z = (Math.random() - 0.5) * CITY_SIZE * 1.5;
638
+
639
+ // Avoid center area
640
+ if (Math.abs(x) < 15 && Math.abs(z) < 15) return;
641
+
642
+ const width = 3 + Math.random() * 6;
643
+ const depth = 3 + Math.random() * 6;
644
+ const height = 8 + Math.random() * 25;
645
+
646
+ // Main building
647
+ const building = createCube(
648
+ width,
649
+ height,
650
+ depth,
651
+ COLORS.building[index % COLORS.building.length],
652
+ [x, height / 2, z]
653
+ );
654
+
655
+ // 🎨 Add COLORFUL detail layers (no more black blocks!)
656
+ const detailColor1 = COLORS.neon[index % COLORS.neon.length];
657
+ const detailColor2 = COLORS.neonGlow[(index + 2) % COLORS.neonGlow.length];
658
+ const detail1 = createCube(width * 0.9, height * 0.3, depth * 0.9, detailColor1, [
659
+ x,
660
+ height * 0.15,
661
+ z,
662
+ ]);
663
+ const detail2 = createCube(width * 0.8, height * 0.2, depth * 0.8, detailColor2, [
664
+ x,
665
+ height * 0.9,
666
+ z,
667
+ ]);
668
+
669
+ // 💡 Windows with BRIGHT NEON animated lighting
670
+ const windowRows = Math.floor(height / 3);
671
+ const windows = [];
672
+
673
+ for (let row = 0; row < windowRows; row++) {
674
+ for (let col = 0; col < 3; col++) {
675
+ const windowX = x + (col - 1) * width * 0.25;
676
+ const windowY = 2 + row * 3;
677
+ const windowZ = z + depth * 0.51;
678
+
679
+ // Use BRIGHT neon glow colors for windows
680
+ const windowColor = COLORS.neonGlow[(row * 3 + col) % COLORS.neonGlow.length];
681
+ const window = createCube(0.8, 0.8, 0.1, windowColor, [windowX, windowY, windowZ]);
682
+
683
+ // Add window glow halo (brighter larger cube behind)
684
+ createCube(1.2, 1.2, 0.05, windowColor, [windowX, windowY, windowZ - 0.1]);
685
+
686
+ windows.push({
687
+ mesh: window,
688
+ flickerTime: Math.random() * 10,
689
+ baseColor: windowColor,
690
+ dimColor: windowColor & 0x555555, // Dim by masking bits
691
+ });
692
+ }
693
+ }
694
+
695
+ // ⚡ Neon sign on top (50% chance, BRIGHT)
696
+ if (Math.random() < 0.5) {
697
+ const signColor = COLORS.neon[Math.floor(Math.random() * COLORS.neon.length)];
698
+ const glowColor = COLORS.neonGlow[Math.floor(Math.random() * COLORS.neonGlow.length)];
699
+
700
+ // Main sign
701
+ const sign = createCube(width * 1.2, 1, 0.2, signColor, [x, height + 1, z]);
702
+
703
+ // Glow halo around sign (larger, behind)
704
+ const signGlow = createCube(width * 1.4, 1.5, 0.1, glowColor, [x, height + 1, z - 0.2]);
705
+
706
+ neonSigns.push({
707
+ mesh: sign,
708
+ glow: signGlow,
709
+ color: signColor,
710
+ glowColor: glowColor,
711
+ pulsePhase: Math.random() * Math.PI * 2,
712
+ });
713
+ }
714
+
715
+ buildings.push({
716
+ main: building,
717
+ details: [detail1, detail2],
718
+ windows: windows,
719
+ x: x,
720
+ z: z,
721
+ height: height,
722
+ });
723
+ }
724
+
725
+ async function createMegaStructure() {
726
+ // 🏙️ Central tower (BRIGHT PURPLE with neon accents)
727
+ createCube(12, 60, 12, 0x8855cc, [0, 30, 0]);
728
+
729
+ // Add colorful stripes to tower
730
+ for (let i = 0; i < 10; i++) {
731
+ const stripeColor = COLORS.neon[i % COLORS.neon.length];
732
+ createCube(12.5, 2, 12.5, stripeColor, [0, 6 + i * 6, 0]);
733
+ }
734
+
735
+ // 🌉 Connecting bridges (BRIGHT CYAN)
736
+ for (let i = 0; i < 4; i++) {
737
+ const angle = (i / 4) * Math.PI * 2;
738
+ const bridgeX = Math.cos(angle) * 20;
739
+ const bridgeZ = Math.sin(angle) * 20;
740
+
741
+ const bridgeColor = COLORS.neonGlow[i % COLORS.neonGlow.length];
742
+ const bridge = createCube(16, 2, 4, bridgeColor, [bridgeX / 2, 25, bridgeZ / 2]);
743
+ setRotation(bridge, 0, angle, 0);
744
+
745
+ // Add underglow to bridges
746
+ const bridgeGlow = createCube(17, 0.5, 4.5, COLORS.underglow[i % COLORS.underglow.length], [
747
+ bridgeX / 2,
748
+ 24,
749
+ bridgeZ / 2,
750
+ ]);
751
+ setRotation(bridgeGlow, 0, angle, 0);
752
+ }
753
+
754
+ // Antenna array on top
755
+ for (let i = 0; i < 6; i++) {
756
+ const antenna = createCube(0.3, 8, 0.3, 0xffffff, [
757
+ Math.random() * 8 - 4,
758
+ 64,
759
+ Math.random() * 8 - 4,
760
+ ]);
761
+
762
+ // Blinking light on antenna
763
+ const light = createSphere(0.5, 0xff0000, [Math.random() * 8 - 4, 68, Math.random() * 8 - 4]);
764
+
765
+ cityLights.push({
766
+ mesh: light,
767
+ blinkTime: Math.random() * 2,
768
+ isOn: true,
769
+ });
770
+ }
771
+ }
772
+
773
+ function createPlayer() {
774
+ // Sleek hovercar
775
+ const body = createCube(3, 0.8, 1.5, 0x4444ff, [0, 2, 0]);
776
+ const cockpit = createSphere(1, 0x2222aa, [0, 2.8, 0.2]);
777
+
778
+ // Thrusters
779
+ const thruster1 = createCube(0.4, 0.4, 0.8, 0xff4400, [-1.2, 1.8, -0.8]);
780
+ const thruster2 = createCube(0.4, 0.4, 0.8, 0xff4400, [1.2, 1.8, -0.8]);
781
+
782
+ // Underglow
783
+ const glow = createCube(3.5, 0.2, 2, 0x00ffff, [0, 1.2, 0]);
784
+
785
+ player = {
786
+ body: body,
787
+ cockpit: cockpit,
788
+ thrusters: [thruster1, thruster2],
789
+ glow: glow,
790
+ x: 0,
791
+ y: 2,
792
+ z: 0,
793
+ vx: 0,
794
+ vy: 0,
795
+ vz: 0,
796
+ rotation: 0,
797
+ tilt: 0,
798
+ boost: 1,
799
+ };
800
+ }
801
+
802
+ function spawnVehicles() {
803
+ for (let i = 0; i < VEHICLE_COUNT; i++) {
804
+ const vehicle = createTrafficVehicle(i);
805
+ vehicles.push(vehicle);
806
+ }
807
+ }
808
+
809
+ function createTrafficVehicle(index) {
810
+ const x = (Math.random() - 0.5) * CITY_SIZE;
811
+ const z = (Math.random() - 0.5) * CITY_SIZE;
812
+ const y = 1.5 + Math.random() * 8;
813
+
814
+ const color = COLORS.vehicle[index % COLORS.vehicle.length];
815
+
816
+ // 🚗 Main vehicle body
817
+ const body = createCube(2.5, 0.6, 1.2, color, [x, y, z]);
818
+
819
+ // ⚡ BRIGHT NEON UNDERGLOW (use underglow colors)
820
+ const underglowColor = COLORS.underglow[index % COLORS.underglow.length];
821
+ const glow = createCube(3.2, 0.3, 1.8, underglowColor, [x, y - 0.5, z]);
822
+
823
+ // ✨ Add second glow layer for extra brightness
824
+ const glow2 = createCube(3.5, 0.15, 2.0, underglowColor, [x, y - 0.6, z]);
825
+
826
+ return {
827
+ body: body,
828
+ glow: glow,
829
+ glow2: glow2,
830
+ underglowColor: underglowColor,
831
+ x: x,
832
+ y: y,
833
+ z: z,
834
+ vx: (Math.random() - 0.5) * 8,
835
+ vy: 0,
836
+ vz: (Math.random() - 0.5) * 8,
837
+ target: { x: x, y: y, z: z },
838
+ speed: 2 + Math.random() * 4,
839
+ turnRate: Math.random() * 2 + 1,
840
+ nextWaypoint: Math.random() * 10,
841
+ };
842
+ }
843
+
844
+ function initParticleSystem() {
845
+ // Create ambient particles (dust, sparks, etc.)
846
+ for (let i = 0; i < PARTICLE_COUNT; i++) {
847
+ createAmbientParticle();
848
+ }
849
+ }
850
+
851
+ function createAmbientParticle() {
852
+ const x = (Math.random() - 0.5) * CITY_SIZE * 2;
853
+ const y = Math.random() * 40;
854
+ const z = (Math.random() - 0.5) * CITY_SIZE * 2;
855
+
856
+ const particle = createSphere(
857
+ 0.1,
858
+ COLORS.particle[Math.floor(Math.random() * COLORS.particle.length)],
859
+ [x, y, z]
860
+ );
861
+
862
+ particles.push({
863
+ mesh: particle,
864
+ x: x,
865
+ y: y,
866
+ z: z,
867
+ vx: (Math.random() - 0.5) * 2,
868
+ vy: Math.random() * 0.5,
869
+ vz: (Math.random() - 0.5) * 2,
870
+ life: 5 + Math.random() * 10,
871
+ maxLife: 15,
872
+ type: 'ambient',
873
+ });
874
+ }
875
+
876
+ function handleInput(dt) {
877
+ // 🚀 IMPROVED CONTROLS - Much more responsive!
878
+ const moveSpeed = flying ? 40 : 25; // Faster base speed
879
+ const accel = flying ? 60 : 45; // Snappy acceleration
880
+ const maxSpeed = flying ? 35 : 25; // Higher max speed
881
+
882
+ let inputX = 0;
883
+ let inputZ = 0;
884
+
885
+ // ⬅️➡️ Horizontal movement with smooth acceleration
886
+ if (btn(0)) {
887
+ // Left
888
+ inputX = -1;
889
+ player.tilt = Math.max(player.tilt - dt * 3, -0.4);
890
+ }
891
+ if (btn(1)) {
892
+ // Right
893
+ inputX = 1;
894
+ player.tilt = Math.min(player.tilt + dt * 3, 0.4);
895
+ }
896
+
897
+ // ⬆️⬇️ Forward/backward movement
898
+ if (btn(2)) {
899
+ // Up
900
+ inputZ = -1;
901
+ }
902
+ if (btn(3)) {
903
+ // Down
904
+ inputZ = 1;
905
+ }
906
+
907
+ // Apply acceleration with max speed cap
908
+ if (inputX !== 0) {
909
+ player.vx += inputX * accel * dt;
910
+ player.vx = Math.max(-maxSpeed, Math.min(maxSpeed, player.vx));
911
+ }
912
+ if (inputZ !== 0) {
913
+ player.vz += inputZ * accel * dt;
914
+ player.vz = Math.max(-maxSpeed, Math.min(maxSpeed, player.vz));
915
+ }
916
+
917
+ // 🎮 Vertical movement (much more responsive)
918
+ if (btn(4)) {
919
+ // Z - Down
920
+ player.vy -= moveSpeed * dt * 0.8; // Faster vertical
921
+ }
922
+ if (btn(5)) {
923
+ // X - Up
924
+ player.vy += moveSpeed * dt * 0.8; // Faster vertical
925
+ }
926
+
927
+ // 💨 BOOST - More powerful!
928
+ if (btnp(6)) {
929
+ // Space
930
+ player.boost = 4; // Stronger boost
931
+ const boostDir = { x: player.vx, z: player.vz };
932
+ const mag = Math.sqrt(boostDir.x * boostDir.x + boostDir.z * boostDir.z);
933
+ if (mag > 0) {
934
+ player.vx += (boostDir.x / mag) * 15; // Add velocity in current direction
935
+ player.vz += (boostDir.z / mag) * 15;
936
+ }
937
+ createBoostParticles();
938
+ }
939
+
940
+ // ✈️ Flight mode toggle
941
+ if (btnp(7)) {
942
+ // Shift
943
+ flying = !flying;
944
+ }
945
+
946
+ // 🎯 Return tilt to center when not turning
947
+ if (inputX === 0) {
948
+ player.tilt *= 0.9; // Smooth return to center
949
+ }
950
+ }
951
+
952
+ function updatePlayer(dt) {
953
+ // 💨 Apply boost multiplier (decays smoothly)
954
+ let boostMult = 1;
955
+ if (player.boost > 1) {
956
+ boostMult = player.boost;
957
+ player.boost = Math.max(1, player.boost - dt * 4);
958
+ }
959
+
960
+ // 🌍 Physics - Apply velocity to position
961
+ player.x += player.vx * boostMult * dt;
962
+ player.y += player.vy * dt;
963
+ player.z += player.vz * boostMult * dt;
964
+
965
+ // ✈️ Hover physics (auto-stabilize height when not flying)
966
+ if (!flying) {
967
+ const groundHeight = 2.5;
968
+ const hoverForce = (groundHeight - player.y) * 8; // Stronger hover
969
+ player.vy += hoverForce * dt;
970
+
971
+ // Clamp vertical position
972
+ if (player.y < groundHeight - 0.5) {
973
+ player.y = groundHeight - 0.5;
974
+ player.vy = Math.max(0, player.vy);
975
+ }
976
+ } else {
977
+ // In flight mode, add slight upward drift
978
+ player.vy += dt * 0.5;
979
+ }
980
+
981
+ // 🎮 IMPROVED DAMPING - Smoother deceleration
982
+ const dampFactor = flying ? 0.92 : 0.88; // More aggressive damping
983
+ player.vx *= dampFactor;
984
+ player.vz *= dampFactor;
985
+ player.vy *= 0.94; // Vertical damping
986
+
987
+ // Stop tiny movements (dead zone)
988
+ if (Math.abs(player.vx) < 0.1) player.vx = 0;
989
+ if (Math.abs(player.vz) < 0.1) player.vz = 0;
990
+ if (Math.abs(player.vy) < 0.1) player.vy = 0;
991
+
992
+ // Update rotation based on movement
993
+ if (Math.abs(player.vx) > 0.1 || Math.abs(player.vz) > 0.1) {
994
+ player.rotation = Math.atan2(player.vx, player.vz);
995
+ }
996
+
997
+ // Update mesh positions
998
+ setPosition(player.body, player.x, player.y, player.z);
999
+ setPosition(player.cockpit, player.x, player.y + 0.8, player.z + 0.2);
1000
+ setPosition(player.glow, player.x, player.y - 0.8, player.z);
1001
+
1002
+ setPosition(player.thrusters[0], player.x - 1.2, player.y - 0.2, player.z - 0.8);
1003
+ setPosition(player.thrusters[1], player.x + 1.2, player.y - 0.2, player.z - 0.8);
1004
+
1005
+ // Apply rotations
1006
+ setRotation(player.body, player.tilt * 0.3, player.rotation, player.tilt);
1007
+ setRotation(player.cockpit, 0, player.rotation, 0);
1008
+
1009
+ // Create thruster particles
1010
+ if (Math.abs(player.vx) > 5 || Math.abs(player.vz) > 5) {
1011
+ createThrusterParticles();
1012
+ }
1013
+
1014
+ // Boundary check
1015
+ const boundary = CITY_SIZE;
1016
+ if (Math.abs(player.x) > boundary) player.x = Math.sign(player.x) * boundary;
1017
+ if (Math.abs(player.z) > boundary) player.z = Math.sign(player.z) * boundary;
1018
+ if (player.y < 1) player.y = 1;
1019
+ if (player.y > 50) player.y = 50;
1020
+ }
1021
+
1022
+ function updateVehicles(dt) {
1023
+ vehicles.forEach(vehicle => {
1024
+ // AI behavior - move to random waypoints
1025
+ vehicle.nextWaypoint -= dt;
1026
+ if (vehicle.nextWaypoint <= 0) {
1027
+ vehicle.target.x = (Math.random() - 0.5) * CITY_SIZE * 0.8;
1028
+ vehicle.target.z = (Math.random() - 0.5) * CITY_SIZE * 0.8;
1029
+ vehicle.target.y = 1.5 + Math.random() * 8;
1030
+ vehicle.nextWaypoint = 3 + Math.random() * 5;
1031
+ }
1032
+
1033
+ // Move towards target
1034
+ const dx = vehicle.target.x - vehicle.x;
1035
+ const dy = vehicle.target.y - vehicle.y;
1036
+ const dz = vehicle.target.z - vehicle.z;
1037
+ const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
1038
+
1039
+ if (distance > 1) {
1040
+ vehicle.vx += (dx / distance) * vehicle.speed * dt;
1041
+ vehicle.vy += (dy / distance) * vehicle.speed * dt;
1042
+ vehicle.vz += (dz / distance) * vehicle.speed * dt;
1043
+ }
1044
+
1045
+ // Apply physics
1046
+ vehicle.x += vehicle.vx * dt;
1047
+ vehicle.y += vehicle.vy * dt;
1048
+ vehicle.z += vehicle.vz * dt;
1049
+
1050
+ // Damping
1051
+ vehicle.vx *= 0.95;
1052
+ vehicle.vy *= 0.98;
1053
+ vehicle.vz *= 0.95;
1054
+
1055
+ // Update mesh positions (body + both glow layers)
1056
+ setPosition(vehicle.body, vehicle.x, vehicle.y, vehicle.z);
1057
+ setPosition(vehicle.glow, vehicle.x, vehicle.y - 0.5, vehicle.z);
1058
+ setPosition(vehicle.glow2, vehicle.x, vehicle.y - 0.6, vehicle.z);
1059
+
1060
+ // Occasional thruster particles
1061
+ if (Math.random() < 0.1) {
1062
+ createVehicleThrusterParticles(vehicle.x, vehicle.y, vehicle.z);
1063
+ }
1064
+ });
1065
+ }
1066
+
1067
+ function updatePackets(dt) {
1068
+ for (let i = dataPackets.length - 1; i >= 0; i--) {
1069
+ let p = dataPackets[i];
1070
+ if (!p.active) continue;
1071
+
1072
+ // Rotate and hover
1073
+ p.offset += dt * 3;
1074
+ setRotation(p.mesh, p.offset, p.offset * 0.5, p.offset * 0.2);
1075
+ setPosition(p.mesh, p.x, p.y + Math.sin(p.offset) * 0.5, p.z);
1076
+
1077
+ // Collision with player
1078
+ const dx = player.x - p.x;
1079
+ const dy = player.y - p.y;
1080
+ const dz = player.z - p.z;
1081
+ const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
1082
+
1083
+ if (dist < 4.0) {
1084
+ p.active = false;
1085
+ playerScore += 100;
1086
+ setScale(p.mesh, 0, 0, 0);
1087
+ sfx('coin');
1088
+ if (currentMission && currentMission.type === 'DATA_HEIST') {
1089
+ currentMission.progress++;
1090
+ }
1091
+ }
1092
+ }
1093
+ }
1094
+
1095
+ function updateParticles(dt) {
1096
+ for (let i = particles.length - 1; i >= 0; i--) {
1097
+ const particle = particles[i];
1098
+
1099
+ // Physics
1100
+ particle.x += particle.vx * dt;
1101
+ particle.y += particle.vy * dt;
1102
+ particle.z += particle.vz * dt;
1103
+
1104
+ particle.life -= dt;
1105
+
1106
+ // Update position
1107
+ setPosition(particle.mesh, particle.x, particle.y, particle.z);
1108
+
1109
+ // Fade out
1110
+ const alpha = particle.life / particle.maxLife;
1111
+ setScale(particle.mesh, alpha);
1112
+
1113
+ // Remove dead particles
1114
+ if (particle.life <= 0) {
1115
+ destroyMesh(particle.mesh);
1116
+ particles.splice(i, 1);
1117
+
1118
+ // Respawn ambient particles
1119
+ if (particle.type === 'ambient') {
1120
+ createAmbientParticle();
1121
+ }
1122
+ }
1123
+ }
1124
+ }
1125
+
1126
+ function updateCityLights(dt) {
1127
+ // Update blinking lights
1128
+ cityLights.forEach(light => {
1129
+ light.blinkTime -= dt;
1130
+ if (light.blinkTime <= 0) {
1131
+ light.isOn = !light.isOn;
1132
+ light.blinkTime = 0.5 + Math.random() * 1.5;
1133
+
1134
+ // Change light color/visibility
1135
+ if (light.isOn) {
1136
+ setScale(light.mesh, 1);
1137
+ } else {
1138
+ setScale(light.mesh, 0.3);
1139
+ }
1140
+ }
1141
+ });
1142
+
1143
+ // Update building windows
1144
+ buildings.forEach(building => {
1145
+ building.windows.forEach(window => {
1146
+ window.flickerTime -= dt;
1147
+ if (window.flickerTime <= 0) {
1148
+ // 💡 Random flicker effect - future enhancement: change mesh color
1149
+ // const _isOn = Math.random() < 0.8; // Would toggle window brightness
1150
+ window.flickerTime = 0.5 + Math.random() * 3;
1151
+ }
1152
+ });
1153
+ });
1154
+ }
1155
+
1156
+ function updateCamera(dt) {
1157
+ // Smooth camera follow
1158
+ const followDistance = 15;
1159
+ const followHeight = 8;
1160
+
1161
+ camera.targetX = player.x - Math.sin(player.rotation) * followDistance;
1162
+ camera.targetY = player.y + followHeight;
1163
+ camera.targetZ = player.z - Math.cos(player.rotation) * followDistance;
1164
+
1165
+ // Smooth interpolation
1166
+ camera.x += (camera.targetX - camera.x) * 3 * dt;
1167
+ camera.y += (camera.targetY - camera.y) * 3 * dt;
1168
+ camera.z += (camera.targetZ - camera.z) * 3 * dt;
1169
+
1170
+ setCameraPosition(camera.x, camera.y, camera.z);
1171
+ setCameraTarget(player.x, player.y + 2, player.z);
1172
+ }
1173
+
1174
+ function updateNeonSigns(dt) {
1175
+ neonSigns.forEach(sign => {
1176
+ sign.pulsePhase += dt * 4;
1177
+ // ⚡ Future enhancement: Use Math.sin(sign.pulsePhase) to pulse glow intensity
1178
+ });
1179
+
1180
+ // Update floating platforms
1181
+ cityObjects.forEach(obj => {
1182
+ if (obj.type === 'platform') {
1183
+ const newY = 15 + Math.sin(gameTime * 0.5 + obj.index) * 3;
1184
+ setPosition(obj.mesh, Math.cos(obj.angle) * 40, newY, Math.sin(obj.angle) * 40);
1185
+ setPosition(obj.glow, Math.cos(obj.angle) * 40, newY - 0.5, Math.sin(obj.angle) * 40);
1186
+ }
1187
+ });
1188
+ }
1189
+
1190
+ function createBoostParticles() {
1191
+ // 💨 BRIGHTER BOOST PARTICLES - More visual impact!
1192
+ for (let i = 0; i < 30; i++) {
1193
+ const boostColors = [0x00ffff, 0xff00ff, 0xffff00, 0x00ff00];
1194
+ const particle = createSphere(0.25, boostColors[i % boostColors.length], [
1195
+ player.x + (Math.random() - 0.5) * 5,
1196
+ player.y + (Math.random() - 0.5) * 2.5,
1197
+ player.z + (Math.random() - 0.5) * 5,
1198
+ ]);
1199
+
1200
+ particles.push({
1201
+ mesh: particle,
1202
+ x: player.x,
1203
+ y: player.y,
1204
+ z: player.z,
1205
+ vx: (Math.random() - 0.5) * 20,
1206
+ vy: (Math.random() - 0.5) * 8,
1207
+ vz: (Math.random() - 0.5) * 20,
1208
+ life: 2.0,
1209
+ maxLife: 2.0,
1210
+ type: 'boost',
1211
+ });
1212
+ }
1213
+ }
1214
+
1215
+ function createThrusterParticles() {
1216
+ // 🔥 BRIGHTER THRUSTER TRAIL - Alternating colors
1217
+ const thrusterColors = [0xff6600, 0xffaa00, 0xff00ff, 0x00ffff];
1218
+ for (let i = 0; i < 3; i++) {
1219
+ const thrusterX = player.x + (i === 0 ? -1.2 : 1.2);
1220
+ const particle = createSphere(
1221
+ 0.2,
1222
+ thrusterColors[Math.floor(Math.random() * thrusterColors.length)],
1223
+ [thrusterX, player.y - 0.5, player.z - 1]
1224
+ );
1225
+
1226
+ particles.push({
1227
+ mesh: particle,
1228
+ x: thrusterX,
1229
+ y: player.y - 0.5,
1230
+ z: player.z - 1,
1231
+ vx: (Math.random() - 0.5) * 4,
1232
+ vy: -Math.random() * 3,
1233
+ vz: Math.random() * 10 + 6,
1234
+ life: 1.0,
1235
+ maxLife: 1.0,
1236
+ type: 'thruster',
1237
+ });
1238
+ }
1239
+ }
1240
+
1241
+ function createVehicleThrusterParticles(x, y, z) {
1242
+ const particle = createSphere(0.1, 0x4488ff, [x, y - 0.3, z - 0.8]);
1243
+
1244
+ particles.push({
1245
+ mesh: particle,
1246
+ x: x,
1247
+ y: y - 0.3,
1248
+ z: z - 0.8,
1249
+ vx: (Math.random() - 0.5) * 2,
1250
+ vy: -Math.random(),
1251
+ vz: Math.random() * 3 + 2,
1252
+ life: 0.5,
1253
+ maxLife: 0.5,
1254
+ type: 'vehicle',
1255
+ });
1256
+ }
1257
+
1258
+ function drawHUD() {
1259
+ // Health bar
1260
+ const hbx = 16,
1261
+ hby = 16,
1262
+ hbw = 150,
1263
+ hbh = 12;
1264
+ rect(hbx, hby, hbw, hbh, rgba8(40, 0, 0, 200), true);
1265
+ const hFrac = playerHealth / playerMaxHealth;
1266
+ if (hFrac > 0) rect(hbx, hby, Math.floor(hbw * hFrac), hbh, rgba8(255, 50, 50, 255), true);
1267
+ rect(hbx, hby, hbw, hbh, rgba8(255, 100, 100, 150), false);
1268
+ print(`HP ${playerHealth}/${playerMaxHealth}`, hbx + 4, hby + 2, rgba8(255, 255, 255, 255));
1269
+
1270
+ // Score and stats
1271
+ print(`CREDITS: ${playerScore}`, 480, 18, rgba8(255, 255, 100, 255));
1272
+ print(`DRONES: ${dronesDestroyed}`, 480, 34, rgba8(255, 150, 150, 255));
1273
+
1274
+ // Mode & speed
1275
+ const speedMag = Math.sqrt(player.vx * player.vx + player.vz * player.vz);
1276
+ print(
1277
+ `${flying ? 'FLIGHT' : 'HOVER'} ${speedMag.toFixed(0)}m/s`,
1278
+ 16,
1279
+ 34,
1280
+ rgba8(0, 255, 255, 255)
1281
+ );
1282
+ print(`ALT: ${player.y.toFixed(0)}m`, 16, 50, rgba8(100, 255, 100, 255));
1283
+
1284
+ // Mission panel
1285
+ if (currentMission) {
1286
+ const mpx = 200,
1287
+ mpy = 16,
1288
+ mpw = 240,
1289
+ mph = 50;
1290
+ rect(mpx, mpy, mpw, mph, rgba8(0, 0, 30, 200), true);
1291
+ rect(mpx, mpy, mpw, mph, rgba8(0, 200, 255, 150), false);
1292
+ print(currentMission.name, mpx + 6, mpy + 4, rgba8(0, 255, 255, 255));
1293
+ print(currentMission.desc, mpx + 6, mpy + 18, rgba8(200, 200, 255, 255));
1294
+ const tColor = missionTimer < 10 ? rgba8(255, 80, 80, 255) : rgba8(255, 255, 200, 255);
1295
+ print(
1296
+ `${currentMission.progress}/${currentMission.target} TIME: ${Math.ceil(missionTimer)}s`,
1297
+ mpx + 6,
1298
+ mpy + 34,
1299
+ tColor
1300
+ );
1301
+ }
1302
+
1303
+ // Mission message (center, fading)
1304
+ if (missionMsgTimer > 0) {
1305
+ const alpha = Math.min(1, missionMsgTimer) * 255;
1306
+ rect(160, 160, 320, 24, rgba8(0, 0, 0, Math.floor(alpha * 0.6)), true);
1307
+ print(missionMsg, 180, 166, rgba8(255, 255, 0, Math.floor(alpha)));
1308
+ }
1309
+
1310
+ // Damage flash
1311
+ if (playerDmgCD > 0.4) {
1312
+ rect(0, 0, 640, 360, rgba8(255, 0, 0, 60), true);
1313
+ }
1314
+
1315
+ // Controls
1316
+ print('WASD:Move SHIFT:Fly SPACE:Boost(ram drones!)', 16, 344, rgba8(200, 200, 255, 180));
1317
+
1318
+ // Mini-map (radar)
1319
+ const radarSize = 80;
1320
+ const radarX = 560;
1321
+ const radarY = 260;
1322
+ rect(
1323
+ radarX - radarSize / 2,
1324
+ radarY - radarSize / 2,
1325
+ radarSize,
1326
+ radarSize,
1327
+ rgba8(0, 50, 0, 150),
1328
+ true
1329
+ );
1330
+ rect(
1331
+ radarX - radarSize / 2,
1332
+ radarY - radarSize / 2,
1333
+ radarSize,
1334
+ radarSize,
1335
+ rgba8(0, 255, 0, 100),
1336
+ false
1337
+ );
1338
+ rect(radarX - 1, radarY - 1, 2, 2, rgba8(255, 255, 255, 255), true);
1339
+
1340
+ // Vehicle dots (magenta)
1341
+ vehicles.forEach(v => {
1342
+ const rx = ((v.x - player.x) / CITY_SIZE) * radarSize * 0.4;
1343
+ const rz = ((v.z - player.z) / CITY_SIZE) * radarSize * 0.4;
1344
+ if (Math.abs(rx) < radarSize / 2 && Math.abs(rz) < radarSize / 2) {
1345
+ rect(radarX + rx - 1, radarY + rz - 1, 2, 2, rgba8(255, 0, 255, 200), true);
1346
+ }
1347
+ });
1348
+
1349
+ // Drone dots (red)
1350
+ drones.forEach(d => {
1351
+ if (!d.alive) return;
1352
+ const rx = ((d.x - player.x) / CITY_SIZE) * radarSize * 0.4;
1353
+ const rz = ((d.z - player.z) / CITY_SIZE) * radarSize * 0.4;
1354
+ if (Math.abs(rx) < radarSize / 2 && Math.abs(rz) < radarSize / 2) {
1355
+ rect(radarX + rx - 1, radarY + rz - 1, 3, 3, rgba8(255, 50, 50, 255), true);
1356
+ }
1357
+ });
1358
+
1359
+ // Checkpoint dot (cyan, blinking)
1360
+ if (currentMission && currentMission.type === 'SPEED_RUN') {
1361
+ const rx = ((currentMission.cx - player.x) / CITY_SIZE) * radarSize * 0.4;
1362
+ const rz = ((currentMission.cz - player.z) / CITY_SIZE) * radarSize * 0.4;
1363
+ if (
1364
+ Math.abs(rx) < radarSize / 2 &&
1365
+ Math.abs(rz) < radarSize / 2 &&
1366
+ Math.sin(gameTime * 8) > 0
1367
+ ) {
1368
+ rect(radarX + rx - 2, radarY + rz - 2, 4, 4, rgba8(0, 255, 255, 255), true);
1369
+ }
1370
+ }
1371
+
1372
+ // Data packet dots (green)
1373
+ dataPackets.forEach(p => {
1374
+ if (!p.active) return;
1375
+ const rx = ((p.x - player.x) / CITY_SIZE) * radarSize * 0.4;
1376
+ const rz = ((p.z - player.z) / CITY_SIZE) * radarSize * 0.4;
1377
+ if (Math.abs(rx) < radarSize / 2 && Math.abs(rz) < radarSize / 2) {
1378
+ rect(radarX + rx, radarY + rz, 2, 2, rgba8(0, 255, 0, 200), true);
1379
+ }
1380
+ });
1381
+
1382
+ print('RADAR', radarX - 15, radarY + radarSize / 2 + 4, rgba8(0, 255, 0, 255));
1383
+ }