awacke1 commited on
Commit
995d988
·
verified ·
1 Parent(s): ba588a2

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +61 -196
index.html CHANGED
@@ -95,7 +95,6 @@
95
  <div class="ui-container" id="race-ui">
96
  <h2>Starship Circuit Commander</h2>
97
  <div id="time">Time: 0</div>
98
- <div id="score">Score: 0</div>
99
  <div id="status">Status: Active</div>
100
  </div>
101
  <div class="sidebar" id="sidebar">
@@ -192,7 +191,6 @@
192
  // Game state
193
  let gameState = 'racing';
194
  let raceTime = 0;
195
- let score = 0;
196
  let currentTrack = 'Phelps Island Drift';
197
  let lastGatePass = 0;
198
  let isJumping = false;
@@ -264,15 +262,10 @@
264
  // Player ship
265
  let playerShip = null;
266
  let playerData = null;
267
- function createShip(typeIndex, isPlayer, scaleMultiplier = 1, splitCount = 0) {
268
  const type = shipTypes[typeIndex];
269
  const ship = new THREE.Group();
270
- const scale = {
271
- x: type.scale.x * scaleMultiplier,
272
- y: type.scale.y * scaleMultiplier,
273
- z: type.scale.z * scaleMultiplier
274
- };
275
- const bodyGeometry = new THREE.BoxGeometry(scale.x, scale.y, scale.z);
276
  const bodyMaterial = new THREE.MeshStandardMaterial({
277
  color: isPlayer ? 0x00ff00 : 0xff0000,
278
  metalness: 0.8,
@@ -281,43 +274,38 @@
281
  const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
282
  ship.add(body);
283
  if (type.name !== 'Rocketman') {
284
- const thrusterGeometry = new THREE.CylinderGeometry(0.2 * scaleMultiplier, 0.2 * scaleMultiplier, 0.5 * scaleMultiplier, 8);
285
  const thrusterMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
286
  for (let i = 0; i < 4; i++) {
287
  const thruster = new THREE.Mesh(thrusterGeometry, thrusterMaterial);
288
  thruster.position.set(
289
- (i % 2 === 0 ? 0.5 : -0.5) * scale.x * 0.8,
290
  0,
291
- (i < 2 ? 0.5 : -0.5) * scale.z * 0.8
292
  );
293
  thruster.rotation.x = Math.PI / 2;
294
  ship.add(thruster);
295
  }
296
  } else {
297
- const jetpackGeometry = new THREE.CylinderGeometry(0.3 * scaleMultiplier, 0.3 * scaleMultiplier, 0.8 * scaleMultiplier, 8);
298
  const jetpack = new THREE.Mesh(jetpackGeometry, new THREE.MeshStandardMaterial({ color: 0xaaaaaa }));
299
- jetpack.position.set(0, -0.5 * scaleMultiplier, 0);
300
  ship.add(jetpack);
301
  }
302
  ship.position.y = 10;
303
  ship.castShadow = true;
304
- const maxHP = type.maxHP * scaleMultiplier;
305
  ship.userData = {
306
  type: type.name,
307
  maxSpeed: type.speed,
308
  acceleration: type.accel,
309
- thrusters: Array(4).fill(maxHP),
310
- body: { front: maxHP, back: maxHP, left: maxHP, right: maxHP },
311
  active: true,
312
  speed: 0,
313
  currentWaypoint: 0,
314
- lastGatePass: 0,
315
- isPlayer: isPlayer,
316
- splitCount: splitCount,
317
- typeIndex: typeIndex
318
  };
319
  scene.add(ship);
320
- if (!isPlayer) aiShips.push(ship);
321
  return ship;
322
  }
323
 
@@ -326,9 +314,8 @@
326
  const typeIndex = Math.floor(Math.random() * shipTypes.length);
327
  playerShip = createShip(typeIndex, true);
328
  playerData = playerShip.userData;
 
329
  Object.keys(skills).forEach(skill => skills[skill].level = 0);
330
- score = 0;
331
- document.getElementById('score').textContent = `Score: ${score}`;
332
  updateCharacterSheet();
333
  updateSkillsSheet();
334
  }
@@ -346,51 +333,20 @@
346
  const startGate = new THREE.Mesh(gateGeometry, gateMaterial);
347
  scene.add(startGate);
348
 
349
- // Missile system
350
- const missiles = [];
351
- function createMissile(position, target) {
352
- const geometry = new THREE.ConeGeometry(0.2, 0.5, 8);
353
- const material = new THREE.MeshStandardMaterial({ color: 0xff0000 });
354
- const missile = new THREE.Mesh(geometry, material);
355
- missile.position.copy(position);
356
- missile.rotation.x = Math.PI / 2;
357
- missile.castShadow = true;
358
-
359
- // Flame trail
360
- const flameCount = 10;
361
- const flameGeometry = new THREE.BufferGeometry();
362
- const flamePositions = new Float32Array(flameCount * 3);
363
- const flameColors = new Float32Array(flameCount * 3);
364
- for (let i = 0; i < flameCount; i++) {
365
- const i3 = i * 3;
366
- flamePositions[i3] = 0;
367
- flamePositions[i3 + 1] = 0;
368
- flamePositions[i3 + 2] = 0;
369
- flameColors[i3] = 1;
370
- flameColors[i3 + 1] = 0;
371
- flameColors[i3 + 2] = 0;
372
- }
373
- flameGeometry.setAttribute('position', new THREE.BufferAttribute(flamePositions, 3));
374
- flameGeometry.setAttribute('color', new THREE.BufferAttribute(flameColors, 3));
375
- const flameMaterial = new THREE.PointsMaterial({
376
- size: 0.2,
377
- vertexColors: true,
378
- transparent: true,
379
- opacity: 0.5
380
- });
381
- const flame = new THREE.Points(flameGeometry, flameMaterial);
382
- flame.position.z = -0.3;
383
- missile.add(flame);
384
-
385
- missile.userData = {
386
- target: target,
387
- speed: 1,
388
- lifetime: 300,
389
  damage: skills.shotPower.effect(skills.shotPower.level)
390
  };
391
- scene.add(missile);
392
- missiles.push(missile);
393
- return missile;
394
  }
395
 
396
  // Particle system for explosions
@@ -433,25 +389,17 @@
433
  }
434
 
435
  // Building and column generation
436
- function createBuilding(x, z, width, depth, height, scaleMultiplier = 1, splitCount = 0) {
437
- const geometry = new THREE.BoxGeometry(width * scaleMultiplier, height * scaleMultiplier, depth * scaleMultiplier);
438
  const material = new THREE.MeshStandardMaterial({
439
  color: Math.random() * 0xffffff,
440
  roughness: 0.7,
441
  metalness: 0.3
442
  });
443
  const building = new THREE.Mesh(geometry, material);
444
- building.position.set(x, (height * scaleMultiplier) / 2, z);
445
  building.castShadow = true;
446
  building.receiveShadow = true;
447
- building.userData = {
448
- splitCount: splitCount,
449
- maxHP: 10 * scaleMultiplier,
450
- currentHP: 10 * scaleMultiplier,
451
- width: width,
452
- depth: depth,
453
- height: height
454
- };
455
  return building;
456
  }
457
 
@@ -723,24 +671,17 @@
723
  // Shooting
724
  let canShoot = true;
725
  let shotCooldown = 200;
726
- const raycaster = new THREE.Raycaster();
727
- const mouse = new THREE.Vector2();
728
  document.addEventListener('mousedown', (event) => {
729
  if (event.button === 0 && canShoot && gameState === 'racing' && playerData.active) {
730
  canShoot = false;
731
  setTimeout(() => canShoot = true, shotCooldown);
732
- mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
733
- mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
734
- raycaster.setFromCamera(mouse, camera);
735
- const targets = [...aiShips, ...(currentTrackGroup ? currentTrackGroup.children.filter(c => c.isMesh && c.geometry.type === 'BoxGeometry') : [])].filter(t => t !== playerShip && (!t.userData.isPlayer || !t.userData.active));
736
- const intersects = raycaster.intersectObjects(targets);
737
- if (intersects.length > 0) {
738
- const target = intersects[0].object;
739
- const position = playerShip.position.clone().add(new THREE.Vector3(0, 0, -2));
740
- const count = skills.shotCount.effect(skills.shotCount.level);
741
- for (let i = 0; i < count; i++) {
742
- createMissile(position.clone().add(new THREE.Vector3((Math.random() - 0.5) * 0.5, (Math.random() - 0.5) * 0.5, 0)), target);
743
- }
744
  }
745
  }
746
  });
@@ -805,36 +746,13 @@
805
  checkShipStatus(ship);
806
  }
807
 
808
- function checkShipStatus(ship, hitPosition) {
809
  const totalHP = ship.userData.thrusters.reduce((a, b) => a + b, 0) +
810
  Object.values(ship.userData.body).reduce((a, b) => a + b, 0);
811
  if (totalHP <= 0) {
812
  ship.userData.active = false;
813
  ship.visible = false;
814
  createExplosion(ship.position);
815
- if (ship.userData.splitCount < 3) {
816
- const newScale = 0.5;
817
- const offsets = [
818
- new THREE.Vector3(1, 0, 1),
819
- new THREE.Vector3(-1, 0, -1)
820
- ];
821
- offsets.forEach(offset => {
822
- const newShip = createShip(
823
- ship.userData.typeIndex,
824
- false,
825
- newScale,
826
- ship.userData.splitCount + 1
827
- );
828
- newShip.position.copy(ship.position).add(offset.multiplyScalar(2));
829
- newShip.userData.currentWaypoint = ship.userData.currentWaypoint;
830
- });
831
- }
832
- if (!ship.userData.isPlayer) {
833
- const index = aiShips.indexOf(ship);
834
- if (index > -1) aiShips.splice(index, 1);
835
- score += 100;
836
- document.getElementById('score').textContent = `Score: ${score}`;
837
- }
838
  if (ship === playerShip) {
839
  document.getElementById('status').textContent = 'Status: Destroyed';
840
  endRace();
@@ -842,36 +760,6 @@
842
  }
843
  }
844
 
845
- // Building damage
846
- function applyBuildingDamage(building, damage, hitPosition) {
847
- building.userData.currentHP -= damage;
848
- createExplosion(hitPosition);
849
- if (building.userData.currentHP <= 0) {
850
- currentTrackGroup.remove(building);
851
- if (building.userData.splitCount < 3) {
852
- const newScale = 0.5;
853
- const offsets = [
854
- new THREE.Vector3(5, 0, 5),
855
- new THREE.Vector3(-5, 0, -5)
856
- ];
857
- offsets.forEach(offset => {
858
- const newBuilding = createBuilding(
859
- building.position.x + offset.x,
860
- building.position.z + offset.z,
861
- building.userData.width,
862
- building.userData.depth,
863
- building.userData.height,
864
- newScale,
865
- building.userData.splitCount + 1
866
- );
867
- currentTrackGroup.add(newBuilding);
868
- });
869
- }
870
- score += 100;
871
- document.getElementById('score').textContent = `Score: ${score}`;
872
- }
873
- }
874
-
875
  // Pickup handling
876
  function updatePickups() {
877
  for (let i = pickups.length - 1; i >= 0; i--) {
@@ -888,48 +776,6 @@
888
  }
889
  }
890
 
891
- // Missile update
892
- function updateMissiles() {
893
- for (let i = missiles.length - 1; i >= 0; i--) {
894
- const missile = missiles[i];
895
- missile.userData.lifetime--;
896
- if (missile.userData.lifetime <= 0 || !missile.userData.target || (!missile.userData.target.userData?.active && !missile.userData.target.isMesh)) {
897
- scene.remove(missile);
898
- missiles.splice(i, 1);
899
- continue;
900
- }
901
- const targetPos = missile.userData.target.isMesh ? missile.userData.target.position : missile.userData.target.position;
902
- const direction = targetPos.clone().sub(missile.position).normalize();
903
- missile.position.add(direction.multiplyScalar(missile.userData.speed));
904
- missile.lookAt(targetPos);
905
- missile.rotation.x += Math.PI / 2;
906
-
907
- // Update flame trail
908
- const flame = missile.children[0];
909
- const positions = flame.geometry.attributes.position.array;
910
- for (let j = 0; j < positions.length / 3; j++) {
911
- const j3 = j * 3;
912
- positions[j3] = (Math.random() - 0.5) * 0.1;
913
- positions[j3 + 1] = (Math.random() - 0.5) * 0.1;
914
- positions[j3 + 2] = -0.3 - Math.random() * 0.2;
915
- }
916
- flame.geometry.attributes.position.needsUpdate = true;
917
-
918
- // Check collision
919
- const missileBox = new THREE.Box3().setFromObject(missile);
920
- const targetBox = new THREE.Box3().setFromObject(missile.userData.target);
921
- if (missileBox.intersectsBox(targetBox)) {
922
- if (missile.userData.target.isMesh && missile.userData.target.geometry.type === 'BoxGeometry') {
923
- applyBuildingDamage(missile.userData.target, missile.userData.damage, missile.position);
924
- } else {
925
- applyDamage(missile.userData.target, missile.userData.damage, missile.position);
926
- }
927
- scene.remove(missile);
928
- missiles.splice(i, 1);
929
- }
930
- }
931
- }
932
-
933
  // Player movement
934
  let rotation = 0;
935
  let bankAngle = 0;
@@ -1007,6 +853,29 @@
1007
  });
1008
  }
1009
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1010
  // Explosion update
1011
  const explosions = [];
1012
  function updateExplosions() {
@@ -1055,9 +924,7 @@
1055
  currentTrack = trackName;
1056
  if (currentTrackGroup) scene.remove(currentTrackGroup);
1057
  pickups.forEach(p => scene.remove(p));
1058
- missiles.forEach(m => scene.remove(m));
1059
  pickups.length = 0;
1060
- missiles.length = 0;
1061
  const { trackGroup, waypoints: newWaypoints, startPosition } = generateTrack(trackConfigs[trackName]);
1062
  currentTrackGroup = trackGroup;
1063
  waypoints = newWaypoints;
@@ -1081,9 +948,7 @@
1081
  startGate.position.y = 12;
1082
  startGate.rotation.y = Math.atan2(waypoints[1].x - waypoints[0].x, waypoints[1].z - waypoints[0].z);
1083
  raceTime = 0;
1084
- score = 0;
1085
  document.getElementById('time').textContent = `Time: 0`;
1086
- document.getElementById('score').textContent = `Score: ${score}`;
1087
  document.getElementById('status').textContent = `Status: Active`;
1088
  updateCharacterSheet();
1089
  updateSkillsSheet();
@@ -1092,12 +957,12 @@
1092
  // Restart race
1093
  function restartRace() {
1094
  scene.remove(playerShip);
 
1095
  explosions.forEach(e => scene.remove(e));
1096
  pickups.forEach(p => scene.remove(p));
1097
- missiles.forEach(m => scene.remove(m));
1098
  explosions.length = 0;
1099
  pickups.length = 0;
1100
- missiles.length = 0;
1101
  initPlayer();
1102
  startRace(currentTrack);
1103
  }
@@ -1106,7 +971,7 @@
1106
  function endRace() {
1107
  gameState = 'menu';
1108
  const activeShips = aiShips.filter(s => s.userData.active).length + (playerData.active ? 1 : 0);
1109
- alert(`Race Over! Ships Remaining: ${activeShips}, Time: ${raceTime.toFixed(2)}s, Score: ${score}`);
1110
  }
1111
 
1112
  // Gallery
@@ -1123,7 +988,7 @@
1123
  if (gameState === 'racing') {
1124
  updatePlayer();
1125
  updateAI();
1126
- updateMissiles();
1127
  updateExplosions();
1128
  raceTime += 1 / 60;
1129
  document.getElementById('time').textContent = `Time: ${raceTime.toFixed(2)}`;
 
95
  <div class="ui-container" id="race-ui">
96
  <h2>Starship Circuit Commander</h2>
97
  <div id="time">Time: 0</div>
 
98
  <div id="status">Status: Active</div>
99
  </div>
100
  <div class="sidebar" id="sidebar">
 
191
  // Game state
192
  let gameState = 'racing';
193
  let raceTime = 0;
 
194
  let currentTrack = 'Phelps Island Drift';
195
  let lastGatePass = 0;
196
  let isJumping = false;
 
262
  // Player ship
263
  let playerShip = null;
264
  let playerData = null;
265
+ function createShip(typeIndex, isPlayer) {
266
  const type = shipTypes[typeIndex];
267
  const ship = new THREE.Group();
268
+ const bodyGeometry = new THREE.BoxGeometry(type.scale.x, type.scale.y, type.scale.z);
 
 
 
 
 
269
  const bodyMaterial = new THREE.MeshStandardMaterial({
270
  color: isPlayer ? 0x00ff00 : 0xff0000,
271
  metalness: 0.8,
 
274
  const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
275
  ship.add(body);
276
  if (type.name !== 'Rocketman') {
277
+ const thrusterGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.5, 8);
278
  const thrusterMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa });
279
  for (let i = 0; i < 4; i++) {
280
  const thruster = new THREE.Mesh(thrusterGeometry, thrusterMaterial);
281
  thruster.position.set(
282
+ (i % 2 === 0 ? 0.5 : -0.5) * type.scale.x * 0.8,
283
  0,
284
+ (i < 2 ? 0.5 : -0.5) * type.scale.z * 0.8
285
  );
286
  thruster.rotation.x = Math.PI / 2;
287
  ship.add(thruster);
288
  }
289
  } else {
290
+ const jetpackGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.8, 8);
291
  const jetpack = new THREE.Mesh(jetpackGeometry, new THREE.MeshStandardMaterial({ color: 0xaaaaaa }));
292
+ jetpack.position.set(0, -0.5, 0);
293
  ship.add(jetpack);
294
  }
295
  ship.position.y = 10;
296
  ship.castShadow = true;
 
297
  ship.userData = {
298
  type: type.name,
299
  maxSpeed: type.speed,
300
  acceleration: type.accel,
301
+ thrusters: Array(4).fill(type.maxHP),
302
+ body: { front: type.maxHP, back: type.maxHP, left: type.maxHP, right: type.maxHP },
303
  active: true,
304
  speed: 0,
305
  currentWaypoint: 0,
306
+ lastGatePass: 0
 
 
 
307
  };
308
  scene.add(ship);
 
309
  return ship;
310
  }
311
 
 
314
  const typeIndex = Math.floor(Math.random() * shipTypes.length);
315
  playerShip = createShip(typeIndex, true);
316
  playerData = playerShip.userData;
317
+ // Reset skills
318
  Object.keys(skills).forEach(skill => skills[skill].level = 0);
 
 
319
  updateCharacterSheet();
320
  updateSkillsSheet();
321
  }
 
333
  const startGate = new THREE.Mesh(gateGeometry, gateMaterial);
334
  scene.add(startGate);
335
 
336
+ // Particle system for bullets
337
+ const bullets = [];
338
+ function createBullet(position, direction) {
339
+ const geometry = new THREE.SphereGeometry(0.2, 8, 8);
340
+ const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
341
+ const bullet = new THREE.Mesh(geometry, material);
342
+ bullet.position.copy(position);
343
+ bullet.userData = {
344
+ direction: direction.clone(),
345
+ lifetime: 120,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
346
  damage: skills.shotPower.effect(skills.shotPower.level)
347
  };
348
+ scene.add(bullet);
349
+ bullets.push(bullet);
 
350
  }
351
 
352
  // Particle system for explosions
 
389
  }
390
 
391
  // Building and column generation
392
+ function createBuilding(x, z, width, depth, height) {
393
+ const geometry = new THREE.BoxGeometry(width, height, depth);
394
  const material = new THREE.MeshStandardMaterial({
395
  color: Math.random() * 0xffffff,
396
  roughness: 0.7,
397
  metalness: 0.3
398
  });
399
  const building = new THREE.Mesh(geometry, material);
400
+ building.position.set(x, height / 2, z);
401
  building.castShadow = true;
402
  building.receiveShadow = true;
 
 
 
 
 
 
 
 
403
  return building;
404
  }
405
 
 
671
  // Shooting
672
  let canShoot = true;
673
  let shotCooldown = 200;
 
 
674
  document.addEventListener('mousedown', (event) => {
675
  if (event.button === 0 && canShoot && gameState === 'racing' && playerData.active) {
676
  canShoot = false;
677
  setTimeout(() => canShoot = true, shotCooldown);
678
+ const direction = new THREE.Vector3();
679
+ camera.getWorldDirection(direction);
680
+ const position = playerShip.position.clone().add(direction.clone().multiplyScalar(2));
681
+ const count = skills.shotCount.effect(skills.shotCount.level);
682
+ for (let i = 0; i < count; i++) {
683
+ const offset = new THREE.Vector3((Math.random() - 0.5) * 0.1, (Math.random() - 0.5) * 0.1, 0);
684
+ createBullet(position.clone().add(offset), direction);
 
 
 
 
 
685
  }
686
  }
687
  });
 
746
  checkShipStatus(ship);
747
  }
748
 
749
+ function checkShipStatus(ship) {
750
  const totalHP = ship.userData.thrusters.reduce((a, b) => a + b, 0) +
751
  Object.values(ship.userData.body).reduce((a, b) => a + b, 0);
752
  if (totalHP <= 0) {
753
  ship.userData.active = false;
754
  ship.visible = false;
755
  createExplosion(ship.position);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  if (ship === playerShip) {
757
  document.getElementById('status').textContent = 'Status: Destroyed';
758
  endRace();
 
760
  }
761
  }
762
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
763
  // Pickup handling
764
  function updatePickups() {
765
  for (let i = pickups.length - 1; i >= 0; i--) {
 
776
  }
777
  }
778
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
779
  // Player movement
780
  let rotation = 0;
781
  let bankAngle = 0;
 
853
  });
854
  }
855
 
856
+ // Bullet update
857
+ function updateBullets() {
858
+ const raycaster = new THREE.Raycaster();
859
+ for (let i = bullets.length - 1; i >= 0; i--) {
860
+ const bullet = bullets[i];
861
+ bullet.userData.lifetime--;
862
+ if (bullet.userData.lifetime <= 0) {
863
+ scene.remove(bullet);
864
+ bullets.splice(i, 1);
865
+ continue;
866
+ }
867
+ bullet.position.add(bullet.userData.direction.clone().multiplyScalar(1));
868
+ raycaster.set(bullet.position, bullet.userData.direction);
869
+ const intersects = raycaster.intersectObjects([...aiShips, playerShip].filter(s => s.userData.active));
870
+ if (intersects.length > 0) {
871
+ const hitShip = intersects[0].object.parent;
872
+ applyDamage(hitShip, bullet.userData.damage, bullet.position);
873
+ scene.remove(bullet);
874
+ bullets.splice(i, 1);
875
+ }
876
+ }
877
+ }
878
+
879
  // Explosion update
880
  const explosions = [];
881
  function updateExplosions() {
 
924
  currentTrack = trackName;
925
  if (currentTrackGroup) scene.remove(currentTrackGroup);
926
  pickups.forEach(p => scene.remove(p));
 
927
  pickups.length = 0;
 
928
  const { trackGroup, waypoints: newWaypoints, startPosition } = generateTrack(trackConfigs[trackName]);
929
  currentTrackGroup = trackGroup;
930
  waypoints = newWaypoints;
 
948
  startGate.position.y = 12;
949
  startGate.rotation.y = Math.atan2(waypoints[1].x - waypoints[0].x, waypoints[1].z - waypoints[0].z);
950
  raceTime = 0;
 
951
  document.getElementById('time').textContent = `Time: 0`;
 
952
  document.getElementById('status').textContent = `Status: Active`;
953
  updateCharacterSheet();
954
  updateSkillsSheet();
 
957
  // Restart race
958
  function restartRace() {
959
  scene.remove(playerShip);
960
+ bullets.forEach(b => scene.remove(b));
961
  explosions.forEach(e => scene.remove(e));
962
  pickups.forEach(p => scene.remove(p));
963
+ bullets.length = 0;
964
  explosions.length = 0;
965
  pickups.length = 0;
 
966
  initPlayer();
967
  startRace(currentTrack);
968
  }
 
971
  function endRace() {
972
  gameState = 'menu';
973
  const activeShips = aiShips.filter(s => s.userData.active).length + (playerData.active ? 1 : 0);
974
+ alert(`Race Over! Ships Remaining: ${activeShips}, Time: ${raceTime.toFixed(2)}s`);
975
  }
976
 
977
  // Gallery
 
988
  if (gameState === 'racing') {
989
  updatePlayer();
990
  updateAI();
991
+ updateBullets();
992
  updateExplosions();
993
  raceTime += 1 / 60;
994
  document.getElementById('time').textContent = `Time: ${raceTime.toFixed(2)}`;