awacke1 commited on
Commit
edac4ed
Β·
verified Β·
1 Parent(s): a6e7662

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1740 -19
index.html CHANGED
@@ -1,19 +1,1740 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Enhanced AI Traffic Evolution Simulator</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ overflow: hidden;
11
+ font-family: Arial, sans-serif;
12
+ background: #000;
13
+ }
14
+ #ui {
15
+ position: absolute;
16
+ top: 10px;
17
+ left: 10px;
18
+ color: white;
19
+ background-color: rgba(0,0,0,0.9);
20
+ padding: 15px;
21
+ border-radius: 8px;
22
+ z-index: 100;
23
+ font-size: 14px;
24
+ min-width: 200px;
25
+ }
26
+ #controls {
27
+ position: absolute;
28
+ top: 10px;
29
+ right: 10px;
30
+ color: white;
31
+ background-color: rgba(0,0,0,0.9);
32
+ padding: 15px;
33
+ border-radius: 8px;
34
+ z-index: 100;
35
+ }
36
+ button {
37
+ background-color: #4CAF50;
38
+ border: none;
39
+ color: white;
40
+ padding: 8px 16px;
41
+ margin: 5px;
42
+ cursor: pointer;
43
+ border-radius: 4px;
44
+ font-size: 12px;
45
+ }
46
+ button:hover {
47
+ background-color: #45a049;
48
+ }
49
+ #stats {
50
+ position: absolute;
51
+ bottom: 10px;
52
+ left: 10px;
53
+ color: white;
54
+ background-color: rgba(0,0,0,0.9);
55
+ padding: 15px;
56
+ border-radius: 8px;
57
+ z-index: 100;
58
+ font-size: 12px;
59
+ min-width: 200px;
60
+ }
61
+ #flockingStats {
62
+ position: absolute;
63
+ bottom: 10px;
64
+ right: 10px;
65
+ color: white;
66
+ background-color: rgba(0,0,0,0.9);
67
+ padding: 15px;
68
+ border-radius: 8px;
69
+ z-index: 100;
70
+ font-size: 12px;
71
+ min-width: 180px;
72
+ }
73
+ #trafficStats {
74
+ position: absolute;
75
+ top: 50%;
76
+ right: 10px;
77
+ transform: translateY(-50%);
78
+ color: white;
79
+ background-color: rgba(0,0,0,0.9);
80
+ padding: 15px;
81
+ border-radius: 8px;
82
+ z-index: 100;
83
+ font-size: 12px;
84
+ min-width: 180px;
85
+ }
86
+ .highlight { color: #ffcc00; font-weight: bold; }
87
+ .success { color: #00ff00; font-weight: bold; }
88
+ .flocking { color: #00aaff; }
89
+ .solo { color: #ff8800; }
90
+ .leader { color: #ff00ff; font-weight: bold; }
91
+ .convoy { color: #00ffff; }
92
+ .parked { color: #88ff88; }
93
+ .species-0 { color: #ff6b6b; }
94
+ .species-1 { color: #4ecdc4; }
95
+ .species-2 { color: #45b7d1; }
96
+ .species-3 { color: #96ceb4; }
97
+ .species-4 { color: #ffd93d; }
98
+ .progress-bar {
99
+ width: 100%;
100
+ height: 10px;
101
+ background-color: #333;
102
+ border-radius: 5px;
103
+ overflow: hidden;
104
+ margin: 5px 0;
105
+ }
106
+ .progress-fill {
107
+ height: 100%;
108
+ background: linear-gradient(90deg, #ff6b6b, #4ecdc4, #45b7d1);
109
+ transition: width 0.3s ease;
110
+ }
111
+ </style>
112
+ </head>
113
+ <body>
114
+ <div id="ui">
115
+ <div class="highlight">AI Traffic Evolution Simulator</div>
116
+ <div>Epoch: <span id="epoch">1</span></div>
117
+ <div>Time: <span id="epochTime">60</span>s</div>
118
+ <div class="progress-bar"><div class="progress-fill" id="timeProgress"></div></div>
119
+ <div>Population: <span id="population">100</span></div>
120
+ <div>Species: <span id="speciesCount">1</span></div>
121
+ <div>Best Fitness: <span id="bestFitness">0</span></div>
122
+ <div>Traffic IQ: <span id="trafficIQ">50</span></div>
123
+ <div>Road Mastery: <span id="roadMastery">0</span>%</div>
124
+ </div>
125
+
126
+ <div id="controls">
127
+ <button id="pauseBtn">Pause</button>
128
+ <button id="resetBtn">Reset</button>
129
+ <button id="speedBtn">Speed: 1x</button>
130
+ <button id="viewBtn">View: Overview</button>
131
+ <button id="flockBtn">Networks: ON</button>
132
+ <button id="trafficBtn">Traffic Rules: ON</button>
133
+ </div>
134
+
135
+ <div id="stats">
136
+ <div><span class="highlight">Top Performers:</span></div>
137
+ <div id="topPerformers"></div>
138
+ <div style="margin-top: 10px;"><span class="highlight">Generation Stats:</span></div>
139
+ <div>Crashes: <span id="crashCount">0</span></div>
140
+ <div>Total Distance: <span id="totalDistance">0</span></div>
141
+ <div>Parking Events: <span id="parkingEvents">0</span></div>
142
+ <div>Lane Violations: <span id="laneViolations">0</span></div>
143
+ <div>Convoy Length: <span id="convoyLength">0</span></div>
144
+ </div>
145
+
146
+ <div id="flockingStats">
147
+ <div><span class="highlight">Convoy Behavior:</span></div>
148
+ <div><span class="leader">Leaders:</span> <span id="leaderCount">0</span></div>
149
+ <div><span class="convoy">In Convoy:</span> <span id="convoyCount">0</span></div>
150
+ <div><span class="parked">Parked:</span> <span id="parkedCount">0</span></div>
151
+ <div><span class="solo">Solo:</span> <span id="soloCount">0</span></div>
152
+ <div>Largest Convoy: <span id="largestConvoy">0</span></div>
153
+ <div>Formation Quality: <span id="formationQuality">0</span>%</div>
154
+ <div>Parking Efficiency: <span id="parkingEfficiency">0</span>%</div>
155
+ </div>
156
+
157
+ <div id="trafficStats">
158
+ <div><span class="highlight">Traffic Intelligence:</span></div>
159
+ <div>Lane Discipline: <span id="laneDiscipline">0</span>%</div>
160
+ <div>Following Distance: <span id="followingDistance">0</span>m</div>
161
+ <div>Road Adherence: <span id="roadAdherence">0</span>%</div>
162
+ <div>Turn Signals: <span id="turnSignals">0</span>%</div>
163
+ <div style="margin-top: 10px;"><span class="highlight">Parking:</span></div>
164
+ <div>Spots Occupied: <span id="spotsOccupied">0</span></div>
165
+ <div>Parking Success: <span id="parkingSuccess">0</span>%</div>
166
+ <div>Queue Efficiency: <span id="queueEfficiency">0</span>%</div>
167
+ </div>
168
+
169
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
170
+ <script>
171
+ // Global variables
172
+ let scene, camera, renderer, clock;
173
+ let world = {
174
+ roads: [],
175
+ intersections: [],
176
+ buildings: [],
177
+ parkingLots: [],
178
+ flockLines: []
179
+ };
180
+
181
+ // Enhanced evolution system
182
+ let epoch = 1;
183
+ let epochTime = 60;
184
+ let timeLeft = 60;
185
+ let population = [];
186
+ let species = [];
187
+ let populationSize = 100;
188
+ let bestFitness = 0;
189
+ let crashCount = 0;
190
+ let paused = false;
191
+ let speedMultiplier = 1;
192
+ let cameraMode = 'overview'; // 'overview', 'follow_best', 'follow_convoy'
193
+ let showFlockLines = true;
194
+ let trafficRules = true;
195
+ let parkingEvents = 0;
196
+ let laneViolations = 0;
197
+
198
+ // Traffic and road parameters
199
+ const ROAD_WIDTH = 12;
200
+ const LANE_WIDTH = 6;
201
+ const ROAD_SPACING = 150;
202
+ const FOLLOW_DISTANCE = 8;
203
+ const CONVOY_MAX_DISTANCE = 12;
204
+ const PARKING_SPOT_SIZE = { width: 4, length: 8 };
205
+
206
+ // Enhanced Neural Network for traffic behavior
207
+ class TrafficAI {
208
+ constructor() {
209
+ this.inputSize = 28; // Enhanced traffic-aware inputs
210
+ this.hiddenLayers = [36, 28, 20];
211
+ this.outputSize = 10; // More nuanced traffic outputs
212
+ this.memorySize = 8;
213
+
214
+ this.weights = [];
215
+ this.biases = [];
216
+ this.memory = new Array(this.memorySize).fill(0);
217
+ this.memoryPointer = 0;
218
+
219
+ // Build network
220
+ let prevSize = this.inputSize + this.memorySize;
221
+ for (let i = 0; i < this.hiddenLayers.length; i++) {
222
+ this.weights.push(this.randomMatrix(prevSize, this.hiddenLayers[i]));
223
+ this.biases.push(this.randomArray(this.hiddenLayers[i]));
224
+ prevSize = this.hiddenLayers[i];
225
+ }
226
+
227
+ this.weights.push(this.randomMatrix(prevSize, this.outputSize));
228
+ this.biases.push(this.randomArray(this.outputSize));
229
+
230
+ // Traffic-specific traits
231
+ this.trafficTraits = {
232
+ laneKeeping: Math.random(),
233
+ followingBehavior: Math.random(),
234
+ parkingSkill: Math.random(),
235
+ convoyDiscipline: Math.random(),
236
+ roadPriority: Math.random()
237
+ };
238
+ }
239
+
240
+ randomMatrix(rows, cols) {
241
+ let matrix = [];
242
+ for (let i = 0; i < rows; i++) {
243
+ matrix[i] = [];
244
+ for (let j = 0; j < cols; j++) {
245
+ matrix[i][j] = (Math.random() - 0.5) * 2;
246
+ }
247
+ }
248
+ return matrix;
249
+ }
250
+
251
+ randomArray(size) {
252
+ return Array(size).fill().map(() => (Math.random() - 0.5) * 2);
253
+ }
254
+
255
+ activate(inputs) {
256
+ let currentInput = [...inputs, ...this.memory];
257
+
258
+ for (let layer = 0; layer < this.hiddenLayers.length; layer++) {
259
+ currentInput = this.forwardLayer(currentInput, this.weights[layer], this.biases[layer]);
260
+ }
261
+
262
+ const outputs = this.forwardLayer(currentInput,
263
+ this.weights[this.weights.length - 1],
264
+ this.biases[this.biases.length - 1]);
265
+
266
+ this.updateMemory(inputs, outputs);
267
+ return outputs;
268
+ }
269
+
270
+ forwardLayer(inputs, weights, biases) {
271
+ const outputs = new Array(weights[0].length).fill(0);
272
+
273
+ for (let i = 0; i < outputs.length; i++) {
274
+ for (let j = 0; j < inputs.length; j++) {
275
+ outputs[i] += inputs[j] * weights[j][i];
276
+ }
277
+ outputs[i] += biases[i];
278
+ outputs[i] = this.sigmoid(outputs[i]);
279
+ }
280
+
281
+ return outputs;
282
+ }
283
+
284
+ sigmoid(x) {
285
+ return 1 / (1 + Math.exp(-Math.max(-10, Math.min(10, x))));
286
+ }
287
+
288
+ updateMemory(inputs, outputs) {
289
+ const roadInfo = inputs.slice(20, 24).reduce((a, b) => a + b, 0) / 4;
290
+ this.memory[this.memoryPointer] = roadInfo;
291
+ this.memoryPointer = (this.memoryPointer + 1) % this.memorySize;
292
+ }
293
+
294
+ mutate(rate = 0.1) {
295
+ this.weights.forEach(weightMatrix => {
296
+ this.mutateMatrix(weightMatrix, rate);
297
+ });
298
+ this.biases.forEach(biasArray => {
299
+ this.mutateArray(biasArray, rate);
300
+ });
301
+
302
+ Object.keys(this.trafficTraits).forEach(trait => {
303
+ if (Math.random() < rate) {
304
+ this.trafficTraits[trait] += (Math.random() - 0.5) * 0.2;
305
+ this.trafficTraits[trait] = Math.max(0, Math.min(1, this.trafficTraits[trait]));
306
+ }
307
+ });
308
+ }
309
+
310
+ mutateMatrix(matrix, rate) {
311
+ for (let i = 0; i < matrix.length; i++) {
312
+ for (let j = 0; j < matrix[i].length; j++) {
313
+ if (Math.random() < rate) {
314
+ matrix[i][j] += (Math.random() - 0.5) * 0.5;
315
+ matrix[i][j] = Math.max(-3, Math.min(3, matrix[i][j]));
316
+ }
317
+ }
318
+ }
319
+ }
320
+
321
+ mutateArray(array, rate) {
322
+ for (let i = 0; i < array.length; i++) {
323
+ if (Math.random() < rate) {
324
+ array[i] += (Math.random() - 0.5) * 0.5;
325
+ array[i] = Math.max(-3, Math.min(3, array[i]));
326
+ }
327
+ }
328
+ }
329
+
330
+ copy() {
331
+ const newAI = new TrafficAI();
332
+ newAI.weights = this.weights.map(matrix => matrix.map(row => [...row]));
333
+ newAI.biases = this.biases.map(bias => [...bias]);
334
+ newAI.memory = [...this.memory];
335
+ newAI.memoryPointer = this.memoryPointer;
336
+ newAI.trafficTraits = {...this.trafficTraits};
337
+ return newAI;
338
+ }
339
+ }
340
+
341
+ // Enhanced AI Car with traffic behavior
342
+ class TrafficCar {
343
+ constructor(x = 0, z = 0) {
344
+ this.brain = new TrafficAI();
345
+ this.mesh = this.createCarMesh();
346
+ this.mesh.position.set(x, 1, z);
347
+
348
+ // Movement and traffic properties
349
+ this.velocity = new THREE.Vector3();
350
+ this.acceleration = new THREE.Vector3();
351
+ this.maxSpeed = 20;
352
+ this.minSpeed = 2;
353
+ this.currentLane = null;
354
+ this.targetLane = null;
355
+ this.lanePosition = 0; // -1 to 1 within lane
356
+
357
+ // Convoy and flock behavior
358
+ this.flockId = -1;
359
+ this.convoyPosition = -1; // Position in convoy (-1 = not in convoy)
360
+ this.convoyLeader = null;
361
+ this.convoyFollowers = [];
362
+ this.followTarget = null;
363
+ this.role = 'driver'; // driver, leader, parker
364
+
365
+ // Traffic behavior
366
+ this.isParked = false;
367
+ this.parkingSpot = null;
368
+ this.targetParkingLot = null;
369
+ this.turnSignal = 'none'; // left, right, none
370
+ this.laneDiscipline = 0;
371
+ this.followingDistance = FOLLOW_DISTANCE;
372
+
373
+ // Fitness and metrics
374
+ this.fitness = 0;
375
+ this.roadTime = 0;
376
+ this.convoyTime = 0;
377
+ this.parkingScore = 0;
378
+ this.trafficViolations = 0;
379
+ this.distanceTraveled = 0;
380
+ this.crashed = false;
381
+ this.timeAlive = 100;
382
+
383
+ // Sensors and visualization
384
+ this.sensors = Array(16).fill(0);
385
+ this.roadSensors = Array(8).fill(0);
386
+ this.trafficSensors = Array(4).fill(0);
387
+ this.sensorRays = [];
388
+ this.flockLines = [];
389
+ this.neighbors = [];
390
+
391
+ this.lastPosition = new THREE.Vector3(x, 1, z);
392
+ this.createSensorRays();
393
+ this.createFlockVisualization();
394
+ this.initializeMovement();
395
+ }
396
+
397
+ createCarMesh() {
398
+ const group = new THREE.Group();
399
+
400
+ // Car body
401
+ const bodyGeometry = new THREE.BoxGeometry(1.5, 0.8, 3.5);
402
+ this.bodyMaterial = new THREE.MeshLambertMaterial({
403
+ color: new THREE.Color().setHSL(Math.random(), 0.8, 0.6)
404
+ });
405
+ const body = new THREE.Mesh(bodyGeometry, this.bodyMaterial);
406
+ body.position.y = 0.4;
407
+ body.castShadow = true;
408
+ group.add(body);
409
+
410
+ // Turn signals
411
+ const signalGeometry = new THREE.SphereGeometry(0.15, 6, 4);
412
+ this.leftSignal = new THREE.Mesh(signalGeometry,
413
+ new THREE.MeshLambertMaterial({ color: 0xff8800, transparent: true, opacity: 0.5 }));
414
+ this.leftSignal.position.set(-0.8, 0.8, 1.2);
415
+ group.add(this.leftSignal);
416
+
417
+ this.rightSignal = new THREE.Mesh(signalGeometry,
418
+ new THREE.MeshLambertMaterial({ color: 0xff8800, transparent: true, opacity: 0.5 }));
419
+ this.rightSignal.position.set(0.8, 0.8, 1.2);
420
+ group.add(this.rightSignal);
421
+
422
+ // Role indicator
423
+ const indicatorGeometry = new THREE.ConeGeometry(0.2, 0.8, 6);
424
+ this.roleIndicator = new THREE.Mesh(indicatorGeometry,
425
+ new THREE.MeshLambertMaterial({ color: 0xffffff }));
426
+ this.roleIndicator.position.set(0, 1.5, 0);
427
+ group.add(this.roleIndicator);
428
+
429
+ // Wheels with proper rotation
430
+ const wheelGeometry = new THREE.CylinderGeometry(0.3, 0.3, 0.2, 8);
431
+ const wheelMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
432
+
433
+ this.wheels = [];
434
+ const wheelPositions = [
435
+ [-0.7, 0, 1.4], [0.7, 0, 1.4],
436
+ [-0.7, 0, -1.4], [0.7, 0, -1.4]
437
+ ];
438
+
439
+ wheelPositions.forEach((pos, i) => {
440
+ const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
441
+ wheel.position.set(...pos);
442
+ wheel.rotation.z = Math.PI / 2;
443
+ this.wheels.push(wheel);
444
+ group.add(wheel);
445
+ });
446
+
447
+ return group;
448
+ }
449
+
450
+ createSensorRays() {
451
+ const sensorMaterial = new THREE.LineBasicMaterial({
452
+ color: 0xff0000,
453
+ transparent: true,
454
+ opacity: 0.2
455
+ });
456
+
457
+ for (let i = 0; i < 16; i++) {
458
+ const geometry = new THREE.BufferGeometry().setFromPoints([
459
+ new THREE.Vector3(0, 0, 0),
460
+ new THREE.Vector3(0, 0, 10)
461
+ ]);
462
+ const ray = new THREE.Line(geometry, sensorMaterial);
463
+ this.sensorRays.push(ray);
464
+ this.mesh.add(ray);
465
+ }
466
+ }
467
+
468
+ createFlockVisualization() {
469
+ const flockMaterial = new THREE.LineBasicMaterial({
470
+ color: 0x00ff00,
471
+ transparent: true,
472
+ opacity: 0.6,
473
+ linewidth: 2
474
+ });
475
+
476
+ for (let i = 0; i < 10; i++) {
477
+ const geometry = new THREE.BufferGeometry().setFromPoints([
478
+ new THREE.Vector3(0, 2, 0),
479
+ new THREE.Vector3(0, 2, 0)
480
+ ]);
481
+ const line = new THREE.Line(geometry, flockMaterial);
482
+ this.flockLines.push(line);
483
+ if (showFlockLines) scene.add(line);
484
+ }
485
+ }
486
+
487
+ initializeMovement() {
488
+ // Start on a road if possible
489
+ const nearestRoad = this.findNearestRoad();
490
+ if (nearestRoad) {
491
+ this.currentLane = nearestRoad.lane;
492
+ this.mesh.rotation.y = nearestRoad.direction;
493
+ this.velocity.set(
494
+ Math.sin(nearestRoad.direction) * 8,
495
+ 0,
496
+ Math.cos(nearestRoad.direction) * 8
497
+ );
498
+ } else {
499
+ this.mesh.rotation.y = Math.random() * Math.PI * 2;
500
+ this.velocity.set(
501
+ Math.sin(this.mesh.rotation.y) * 6,
502
+ 0,
503
+ Math.cos(this.mesh.rotation.y) * 6
504
+ );
505
+ }
506
+ }
507
+
508
+ findNearestRoad() {
509
+ const pos = this.mesh.position;
510
+ let nearestRoad = null;
511
+ let minDistance = Infinity;
512
+
513
+ // Check horizontal roads
514
+ for (let roadZ = -300; roadZ <= 300; roadZ += ROAD_SPACING) {
515
+ const distToRoad = Math.abs(pos.z - roadZ);
516
+ if (distToRoad < ROAD_WIDTH / 2 && distToRoad < minDistance) {
517
+ minDistance = distToRoad;
518
+ const laneCenter = roadZ + (pos.x > 0 ? -LANE_WIDTH/2 : LANE_WIDTH/2);
519
+ nearestRoad = {
520
+ lane: 'horizontal',
521
+ center: laneCenter,
522
+ direction: pos.x > 0 ? Math.PI : 0 // Left side driving
523
+ };
524
+ }
525
+ }
526
+
527
+ // Check vertical roads
528
+ for (let roadX = -300; roadX <= 300; roadX += ROAD_SPACING) {
529
+ const distToRoad = Math.abs(pos.x - roadX);
530
+ if (distToRoad < ROAD_WIDTH / 2 && distToRoad < minDistance) {
531
+ minDistance = distToRoad;
532
+ const laneCenter = roadX + (pos.z > 0 ? -LANE_WIDTH/2 : LANE_WIDTH/2);
533
+ nearestRoad = {
534
+ lane: 'vertical',
535
+ center: laneCenter,
536
+ direction: pos.z > 0 ? -Math.PI/2 : Math.PI/2 // Left side driving
537
+ };
538
+ }
539
+ }
540
+
541
+ return nearestRoad;
542
+ }
543
+
544
+ updateSensors() {
545
+ const maxDistance = 10;
546
+ const raycaster = new THREE.Raycaster();
547
+
548
+ // 16-direction obstacle sensors
549
+ for (let i = 0; i < 16; i++) {
550
+ const angle = (i * Math.PI * 2) / 16;
551
+ const direction = new THREE.Vector3(
552
+ Math.sin(angle), 0, Math.cos(angle)
553
+ );
554
+ direction.applyQuaternion(this.mesh.quaternion);
555
+
556
+ raycaster.set(this.mesh.position, direction);
557
+ const intersects = raycaster.intersectObjects(this.getObstacles(), true);
558
+
559
+ if (intersects.length > 0 && intersects[0].distance <= maxDistance) {
560
+ this.sensors[i] = 1 - (intersects[0].distance / maxDistance);
561
+ } else {
562
+ this.sensors[i] = 0;
563
+ }
564
+
565
+ // Update visual rays
566
+ const endDistance = intersects.length > 0 ?
567
+ Math.min(intersects[0].distance, maxDistance) : maxDistance;
568
+ const rayEnd = direction.clone().multiplyScalar(endDistance);
569
+ this.sensorRays[i].geometry.setFromPoints([
570
+ new THREE.Vector3(0, 0, 0), rayEnd
571
+ ]);
572
+ }
573
+
574
+ // Road-specific sensors
575
+ this.updateRoadSensors();
576
+ this.updateTrafficSensors();
577
+ }
578
+
579
+ updateRoadSensors() {
580
+ const pos = this.mesh.position;
581
+
582
+ // Road position and lane detection
583
+ this.roadSensors[0] = this.getRoadPosition();
584
+ this.roadSensors[1] = this.getLanePosition();
585
+ this.roadSensors[2] = this.getRoadDirection();
586
+ this.roadSensors[3] = this.getDistanceToIntersection();
587
+
588
+ // Parking lot detection
589
+ this.roadSensors[4] = this.getNearestParkingLot();
590
+ this.roadSensors[5] = this.getParkingAvailability();
591
+
592
+ // Traffic flow
593
+ this.roadSensors[6] = this.getTrafficDensity();
594
+ this.roadSensors[7] = this.getOptimalSpeed();
595
+ }
596
+
597
+ updateTrafficSensors() {
598
+ // Convoy and following behavior
599
+ this.trafficSensors[0] = this.getConvoyStatus();
600
+ this.trafficSensors[1] = this.getFollowingDistance();
601
+ this.trafficSensors[2] = this.getLeaderDistance();
602
+ this.trafficSensors[3] = this.getNeedToPark();
603
+ }
604
+
605
+ getRoadPosition() {
606
+ const pos = this.mesh.position;
607
+
608
+ // Check horizontal roads
609
+ for (let roadZ = -300; roadZ <= 300; roadZ += ROAD_SPACING) {
610
+ const distToRoad = Math.abs(pos.z - roadZ);
611
+ if (distToRoad <= ROAD_WIDTH / 2) {
612
+ return 1 - (distToRoad / (ROAD_WIDTH / 2));
613
+ }
614
+ }
615
+
616
+ // Check vertical roads
617
+ for (let roadX = -300; roadX <= 300; roadX += ROAD_SPACING) {
618
+ const distToRoad = Math.abs(pos.x - roadX);
619
+ if (distToRoad <= ROAD_WIDTH / 2) {
620
+ return 1 - (distToRoad / (ROAD_WIDTH / 2));
621
+ }
622
+ }
623
+
624
+ return 0; // Off road
625
+ }
626
+
627
+ getLanePosition() {
628
+ const pos = this.mesh.position;
629
+ const roadInfo = this.findNearestRoad();
630
+
631
+ if (!roadInfo) return 0.5;
632
+
633
+ if (roadInfo.lane === 'horizontal') {
634
+ const laneOffset = pos.z - roadInfo.center;
635
+ return 0.5 + (laneOffset / LANE_WIDTH);
636
+ } else {
637
+ const laneOffset = pos.x - roadInfo.center;
638
+ return 0.5 + (laneOffset / LANE_WIDTH);
639
+ }
640
+ }
641
+
642
+ getRoadDirection() {
643
+ const roadInfo = this.findNearestRoad();
644
+ if (!roadInfo) return 0.5;
645
+
646
+ const currentDirection = Math.atan2(this.velocity.x, this.velocity.z);
647
+ const targetDirection = roadInfo.direction;
648
+
649
+ let angleDiff = targetDirection - currentDirection;
650
+ while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
651
+ while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
652
+
653
+ return 0.5 + (angleDiff / Math.PI) * 0.5;
654
+ }
655
+
656
+ getDistanceToIntersection() {
657
+ const pos = this.mesh.position;
658
+ let minDist = Infinity;
659
+
660
+ // Find distance to nearest intersection
661
+ for (let x = -300; x <= 300; x += ROAD_SPACING) {
662
+ for (let z = -300; z <= 300; z += ROAD_SPACING) {
663
+ const dist = pos.distanceTo(new THREE.Vector3(x, 0, z));
664
+ minDist = Math.min(minDist, dist);
665
+ }
666
+ }
667
+
668
+ return Math.max(0, 1 - minDist / 50);
669
+ }
670
+
671
+ getNearestParkingLot() {
672
+ const pos = this.mesh.position;
673
+ let nearestDist = Infinity;
674
+
675
+ world.parkingLots.forEach(lot => {
676
+ const dist = pos.distanceTo(lot.center);
677
+ if (dist < nearestDist) {
678
+ nearestDist = dist;
679
+ this.targetParkingLot = lot;
680
+ }
681
+ });
682
+
683
+ return Math.max(0, 1 - nearestDist / 100);
684
+ }
685
+
686
+ getParkingAvailability() {
687
+ if (!this.targetParkingLot) return 0;
688
+
689
+ const availableSpots = this.targetParkingLot.spots.filter(spot => !spot.occupied).length;
690
+ return availableSpots / this.targetParkingLot.spots.length;
691
+ }
692
+
693
+ getTrafficDensity() {
694
+ const pos = this.mesh.position;
695
+ let nearbyCount = 0;
696
+
697
+ population.forEach(other => {
698
+ if (other !== this && !other.crashed && !other.isParked) {
699
+ const dist = pos.distanceTo(other.mesh.position);
700
+ if (dist < 30) nearbyCount++;
701
+ }
702
+ });
703
+
704
+ return Math.min(nearbyCount / 10, 1);
705
+ }
706
+
707
+ getOptimalSpeed() {
708
+ const roadPos = this.getRoadPosition();
709
+ const density = this.getTrafficDensity();
710
+ return roadPos * (1 - density * 0.5);
711
+ }
712
+
713
+ getConvoyStatus() {
714
+ return this.convoyPosition >= 0 ? 1 : 0;
715
+ }
716
+
717
+ getFollowingDistance() {
718
+ if (!this.followTarget) return 1;
719
+
720
+ const dist = this.mesh.position.distanceTo(this.followTarget.mesh.position);
721
+ return Math.min(dist / 20, 1);
722
+ }
723
+
724
+ getLeaderDistance() {
725
+ if (!this.convoyLeader) return 0;
726
+
727
+ const dist = this.mesh.position.distanceTo(this.convoyLeader.mesh.position);
728
+ return Math.max(0, 1 - dist / 50);
729
+ }
730
+
731
+ getNeedToPark() {
732
+ return (this.timeAlive < 30 && !this.isParked) ? 1 : 0;
733
+ }
734
+
735
+ updateConvoyBehavior() {
736
+ this.neighbors = [];
737
+
738
+ // Find nearby cars for convoy formation
739
+ population.forEach(other => {
740
+ if (other !== this && !other.crashed && !other.isParked) {
741
+ const distance = this.mesh.position.distanceTo(other.mesh.position);
742
+
743
+ if (distance < 25) {
744
+ this.neighbors.push(other);
745
+ }
746
+ }
747
+ });
748
+
749
+ // Determine role and convoy behavior
750
+ this.updateRole();
751
+ this.updateConvoyFormation();
752
+ }
753
+
754
+ updateRole() {
755
+ const roadPos = this.getRoadPosition();
756
+
757
+ if (roadPos > 0.8 && this.neighbors.length > 2 && this.brain.trafficTraits.convoyDiscipline > 0.7) {
758
+ this.role = 'leader';
759
+ this.roleIndicator.material.color.setHex(0xff00ff);
760
+ } else if (this.getNeedToPark() > 0.5) {
761
+ this.role = 'parker';
762
+ this.roleIndicator.material.color.setHex(0x00ff00);
763
+ } else {
764
+ this.role = 'driver';
765
+ this.roleIndicator.material.color.setHex(0xffffff);
766
+ }
767
+ }
768
+
769
+ updateConvoyFormation() {
770
+ if (this.role === 'leader') {
771
+ // Leaders organize convoys
772
+ this.convoyFollowers = this.neighbors
773
+ .filter(car => car.role === 'driver')
774
+ .sort((a, b) =>
775
+ this.mesh.position.distanceTo(a.mesh.position) -
776
+ this.mesh.position.distanceTo(b.mesh.position)
777
+ )
778
+ .slice(0, 5); // Max 5 followers
779
+
780
+ this.convoyFollowers.forEach((follower, index) => {
781
+ follower.convoyLeader = this;
782
+ follower.convoyPosition = index;
783
+ follower.followTarget = index === 0 ? this : this.convoyFollowers[index - 1];
784
+ });
785
+ } else if (this.convoyPosition >= 0) {
786
+ // Update following behavior
787
+ if (this.followTarget && this.followTarget.crashed) {
788
+ this.convoyPosition = -1;
789
+ this.convoyLeader = null;
790
+ this.followTarget = null;
791
+ }
792
+ }
793
+ }
794
+
795
+ getEnhancedInputs() {
796
+ return [
797
+ ...this.sensors, // 16 obstacle sensors
798
+ ...this.roadSensors, // 8 road/navigation sensors
799
+ ...this.trafficSensors, // 4 traffic behavior sensors
800
+ ];
801
+ }
802
+
803
+ update(deltaTime) {
804
+ if (this.crashed || this.isParked) return;
805
+
806
+ this.timeAlive -= deltaTime;
807
+ if (this.timeAlive <= 0) {
808
+ this.attemptParking();
809
+ return;
810
+ }
811
+
812
+ this.updateSensors();
813
+ this.updateConvoyBehavior();
814
+ this.updateVisuals();
815
+
816
+ // Get AI decision
817
+ const inputs = this.getEnhancedInputs();
818
+ const outputs = this.brain.activate(inputs);
819
+
820
+ // Apply traffic-aware movement
821
+ this.applyTrafficMovement(outputs, deltaTime);
822
+ this.updateFitness(deltaTime);
823
+
824
+ this.lastPosition.copy(this.mesh.position);
825
+ this.checkCollisions();
826
+ this.keepInBounds();
827
+ }
828
+
829
+ applyTrafficMovement(outputs, deltaTime) {
830
+ const [
831
+ acceleration, braking, steerLeft, steerRight,
832
+ laneChange, followConvoy, parkingManeuver, turnSignalLeft,
833
+ turnSignalRight, emergencyStop
834
+ ] = outputs;
835
+
836
+ // Update turn signals
837
+ this.turnSignal = 'none';
838
+ if (turnSignalLeft > 0.7) this.turnSignal = 'left';
839
+ if (turnSignalRight > 0.7) this.turnSignal = 'right';
840
+
841
+ this.leftSignal.material.opacity = this.turnSignal === 'left' ? 1.0 : 0.3;
842
+ this.rightSignal.material.opacity = this.turnSignal === 'right' ? 1.0 : 0.3;
843
+
844
+ // Emergency stop
845
+ if (emergencyStop > 0.8) {
846
+ this.velocity.multiplyScalar(0.7);
847
+ return;
848
+ }
849
+
850
+ // Parking maneuver
851
+ if (parkingManeuver > 0.8 && this.targetParkingLot) {
852
+ this.executeParking(deltaTime);
853
+ return;
854
+ }
855
+
856
+ // Road-following behavior
857
+ this.followRoad(deltaTime);
858
+
859
+ // Convoy following
860
+ if (followConvoy > 0.6 && this.followTarget) {
861
+ this.followConvoyTarget(deltaTime);
862
+ }
863
+
864
+ // Basic movement
865
+ const forward = new THREE.Vector3(0, 0, 1);
866
+ forward.applyQuaternion(this.mesh.quaternion);
867
+
868
+ // Acceleration and braking
869
+ if (acceleration > 0.3) {
870
+ this.velocity.add(forward.multiplyScalar(acceleration * 8 * deltaTime));
871
+ }
872
+
873
+ if (braking > 0.5) {
874
+ this.velocity.multiplyScalar(1 - braking * deltaTime * 3);
875
+ }
876
+
877
+ // Steering
878
+ const steering = (steerRight - steerLeft) * 0.08 * deltaTime;
879
+ this.mesh.rotation.y += steering;
880
+
881
+ // Speed limits
882
+ const currentSpeed = this.velocity.length();
883
+ if (currentSpeed > this.maxSpeed) {
884
+ this.velocity.normalize().multiplyScalar(this.maxSpeed);
885
+ } else if (currentSpeed < this.minSpeed && currentSpeed > 0.1) {
886
+ this.velocity.normalize().multiplyScalar(this.minSpeed);
887
+ }
888
+
889
+ // Apply movement
890
+ this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));
891
+
892
+ // Wheel rotation
893
+ this.wheels.forEach(wheel => {
894
+ wheel.rotation.x += currentSpeed * deltaTime * 0.1;
895
+ });
896
+ }
897
+
898
+ followRoad(deltaTime) {
899
+ const roadInfo = this.findNearestRoad();
900
+ if (!roadInfo) return;
901
+
902
+ // Lane-keeping force
903
+ const laneKeepingForce = this.brain.trafficTraits.laneKeeping;
904
+ let targetDirection = roadInfo.direction;
905
+
906
+ // Adjust for lane position
907
+ if (roadInfo.lane === 'horizontal') {
908
+ const laneOffset = this.mesh.position.z - roadInfo.center;
909
+ if (Math.abs(laneOffset) > LANE_WIDTH / 4) {
910
+ const correction = -laneOffset * 0.02 * laneKeepingForce;
911
+ this.velocity.z += correction;
912
+ if (Math.abs(laneOffset) > LANE_WIDTH / 2) {
913
+ this.trafficViolations++;
914
+ laneViolations++;
915
+ }
916
+ }
917
+ } else {
918
+ const laneOffset = this.mesh.position.x - roadInfo.center;
919
+ if (Math.abs(laneOffset) > LANE_WIDTH / 4) {
920
+ const correction = -laneOffset * 0.02 * laneKeepingForce;
921
+ this.velocity.x += correction;
922
+ if (Math.abs(laneOffset) > LANE_WIDTH / 2) {
923
+ this.trafficViolations++;
924
+ laneViolations++;
925
+ }
926
+ }
927
+ }
928
+
929
+ // Direction alignment
930
+ const currentDirection = Math.atan2(this.velocity.x, this.velocity.z);
931
+ let angleDiff = targetDirection - currentDirection;
932
+ while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
933
+ while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
934
+
935
+ if (Math.abs(angleDiff) > 0.1) {
936
+ this.mesh.rotation.y += angleDiff * 0.05 * laneKeepingForce;
937
+ }
938
+
939
+ this.roadTime += deltaTime;
940
+ this.laneDiscipline = Math.max(0, 1 - this.trafficViolations * 0.1);
941
+ }
942
+
943
+ followConvoyTarget(deltaTime) {
944
+ if (!this.followTarget) return;
945
+
946
+ const targetPos = this.followTarget.mesh.position;
947
+ const distance = this.mesh.position.distanceTo(targetPos);
948
+ const idealDistance = FOLLOW_DISTANCE + (this.convoyPosition * 2);
949
+
950
+ if (distance > idealDistance + 3) {
951
+ // Too far - speed up
952
+ const catchUpForce = this.followTarget.velocity.clone().multiplyScalar(0.3);
953
+ this.velocity.add(catchUpForce.multiplyScalar(deltaTime));
954
+ } else if (distance < idealDistance - 2) {
955
+ // Too close - slow down
956
+ this.velocity.multiplyScalar(0.95);
957
+ }
958
+
959
+ // Align with target's direction
960
+ const targetDirection = Math.atan2(this.followTarget.velocity.x, this.followTarget.velocity.z);
961
+ const currentDirection = Math.atan2(this.velocity.x, this.velocity.z);
962
+ let angleDiff = targetDirection - currentDirection;
963
+
964
+ while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
965
+ while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
966
+
967
+ this.mesh.rotation.y += angleDiff * 0.1;
968
+
969
+ this.convoyTime += deltaTime;
970
+ this.followingDistance = distance;
971
+ }
972
+
973
+ executeParking(deltaTime) {
974
+ if (!this.targetParkingLot) return;
975
+
976
+ // Find available parking spot
977
+ const availableSpot = this.targetParkingLot.spots.find(spot => !spot.occupied);
978
+ if (!availableSpot) return;
979
+
980
+ const spotPos = availableSpot.position;
981
+ const distance = this.mesh.position.distanceTo(spotPos);
982
+
983
+ if (distance < 2) {
984
+ // Park successfully
985
+ this.isParked = true;
986
+ this.parkingSpot = availableSpot;
987
+ availableSpot.occupied = true;
988
+ availableSpot.car = this;
989
+ this.mesh.position.copy(spotPos);
990
+ this.velocity.set(0, 0, 0);
991
+ this.parkingScore += 100;
992
+ parkingEvents++;
993
+ this.updateCarColor();
994
+ } else {
995
+ // Move toward parking spot
996
+ const direction = spotPos.clone().sub(this.mesh.position).normalize();
997
+ this.velocity.copy(direction.multiplyScalar(5));
998
+ this.mesh.position.add(this.velocity.clone().multiplyScalar(deltaTime));
999
+
1000
+ // Align with parking spot
1001
+ const targetAngle = Math.atan2(direction.x, direction.z);
1002
+ this.mesh.rotation.y = targetAngle;
1003
+ }
1004
+ }
1005
+
1006
+ attemptParking() {
1007
+ if (!this.targetParkingLot) {
1008
+ this.crashed = true;
1009
+ return;
1010
+ }
1011
+
1012
+ this.executeParking(0.016); // Try one more time
1013
+ if (!this.isParked) {
1014
+ this.crashed = true;
1015
+ }
1016
+ }
1017
+
1018
+ updateFitness(deltaTime) {
1019
+ const distance = this.mesh.position.distanceTo(this.lastPosition);
1020
+ this.distanceTraveled += distance;
1021
+
1022
+ // Multi-objective fitness
1023
+ const roadBonus = this.getRoadPosition() * distance * 5;
1024
+ const laneBonus = this.laneDiscipline * distance * 3;
1025
+ const convoyBonus = this.convoyTime * 2;
1026
+ const parkingBonus = this.parkingScore;
1027
+ const violationPenalty = this.trafficViolations * -10;
1028
+
1029
+ this.fitness = this.distanceTraveled +
1030
+ roadBonus +
1031
+ laneBonus +
1032
+ convoyBonus +
1033
+ parkingBonus +
1034
+ violationPenalty;
1035
+ }
1036
+
1037
+ updateVisuals() {
1038
+ this.updateCarColor();
1039
+ this.updateFlockVisualization();
1040
+ }
1041
+
1042
+ updateCarColor() {
1043
+ let hue = 0.6; // Default blue
1044
+ let saturation = 0.7;
1045
+ let lightness = 0.5;
1046
+
1047
+ if (this.isParked) {
1048
+ hue = 0.3; // Green for parked
1049
+ lightness = 0.7;
1050
+ } else if (this.role === 'leader') {
1051
+ hue = 0.8; // Purple for leaders
1052
+ saturation = 1.0;
1053
+ lightness = 0.6;
1054
+ } else if (this.convoyPosition >= 0) {
1055
+ hue = 0.5; // Cyan for convoy members
1056
+ saturation = 0.8;
1057
+ lightness = 0.6;
1058
+ } else if (this.getRoadPosition() < 0.3) {
1059
+ hue = 0.1; // Orange for off-road
1060
+ saturation = 1.0;
1061
+ lightness = 0.5;
1062
+ }
1063
+
1064
+ // Performance-based brightness
1065
+ const performanceBonus = Math.min(this.fitness / 500, 0.2);
1066
+ lightness += performanceBonus;
1067
+
1068
+ this.bodyMaterial.color.setHSL(hue, saturation, lightness);
1069
+ }
1070
+
1071
+ updateFlockVisualization() {
1072
+ if (!showFlockLines) return;
1073
+
1074
+ // Show convoy connections
1075
+ let connectionIndex = 0;
1076
+
1077
+ // Leader to followers
1078
+ if (this.role === 'leader') {
1079
+ this.convoyFollowers.forEach(follower => {
1080
+ if (connectionIndex < this.flockLines.length) {
1081
+ const start = this.mesh.position.clone();
1082
+ start.y = 3;
1083
+ const end = follower.mesh.position.clone();
1084
+ end.y = 3;
1085
+
1086
+ this.flockLines[connectionIndex].geometry.setFromPoints([start, end]);
1087
+ this.flockLines[connectionIndex].material.color.setHex(0xff00ff);
1088
+ this.flockLines[connectionIndex].visible = true;
1089
+ connectionIndex++;
1090
+ }
1091
+ });
1092
+ }
1093
+
1094
+ // Following connection
1095
+ if (this.followTarget && connectionIndex < this.flockLines.length) {
1096
+ const start = this.mesh.position.clone();
1097
+ start.y = 3;
1098
+ const end = this.followTarget.mesh.position.clone();
1099
+ end.y = 3;
1100
+
1101
+ this.flockLines[connectionIndex].geometry.setFromPoints([start, end]);
1102
+ this.flockLines[connectionIndex].material.color.setHex(0x00ffff);
1103
+ this.flockLines[connectionIndex].visible = true;
1104
+ connectionIndex++;
1105
+ }
1106
+
1107
+ // Neighbor connections
1108
+ this.neighbors.slice(0, 8 - connectionIndex).forEach(neighbor => {
1109
+ if (connectionIndex < this.flockLines.length) {
1110
+ const start = this.mesh.position.clone();
1111
+ start.y = 3;
1112
+ const end = neighbor.mesh.position.clone();
1113
+ end.y = 3;
1114
+
1115
+ this.flockLines[connectionIndex].geometry.setFromPoints([start, end]);
1116
+ this.flockLines[connectionIndex].material.color.setHex(0x00ff00);
1117
+ this.flockLines[connectionIndex].visible = true;
1118
+ connectionIndex++;
1119
+ }
1120
+ });
1121
+
1122
+ // Hide unused lines
1123
+ for (let i = connectionIndex; i < this.flockLines.length; i++) {
1124
+ this.flockLines[i].visible = false;
1125
+ }
1126
+ }
1127
+
1128
+ getObstacles() {
1129
+ let obstacles = [];
1130
+
1131
+ population.forEach(car => {
1132
+ if (car !== this && !car.crashed) {
1133
+ obstacles.push(car.mesh);
1134
+ }
1135
+ });
1136
+
1137
+ world.buildings.forEach(building => {
1138
+ obstacles.push(building.mesh);
1139
+ });
1140
+
1141
+ return obstacles;
1142
+ }
1143
+
1144
+ checkCollisions() {
1145
+ const carBox = new THREE.Box3().setFromObject(this.mesh);
1146
+
1147
+ // Soft collision with other cars
1148
+ population.forEach(otherCar => {
1149
+ if (otherCar !== this && !otherCar.crashed && !otherCar.isParked) {
1150
+ const otherBox = new THREE.Box3().setFromObject(otherCar.mesh);
1151
+ if (carBox.intersectsBox(otherBox)) {
1152
+ const separation = new THREE.Vector3()
1153
+ .subVectors(this.mesh.position, otherCar.mesh.position)
1154
+ .normalize()
1155
+ .multiplyScalar(3);
1156
+
1157
+ this.velocity.add(separation.multiplyScalar(0.3));
1158
+ otherCar.velocity.sub(separation.multiplyScalar(0.3));
1159
+
1160
+ this.fitness -= 20;
1161
+ otherCar.fitness -= 20;
1162
+ this.trafficViolations++;
1163
+ }
1164
+ }
1165
+ });
1166
+
1167
+ // Building collisions
1168
+ world.buildings.forEach(building => {
1169
+ const buildingBox = new THREE.Box3().setFromObject(building.mesh);
1170
+ if (carBox.intersectsBox(buildingBox)) {
1171
+ this.crashed = true;
1172
+ crashCount++;
1173
+ }
1174
+ });
1175
+ }
1176
+
1177
+ keepInBounds() {
1178
+ const bounds = 400;
1179
+ if (Math.abs(this.mesh.position.x) > bounds ||
1180
+ Math.abs(this.mesh.position.z) > bounds) {
1181
+ if (Math.abs(this.mesh.position.x) > bounds) {
1182
+ this.mesh.position.x = Math.sign(this.mesh.position.x) * bounds;
1183
+ this.velocity.x *= -0.6;
1184
+ }
1185
+ if (Math.abs(this.mesh.position.z) > bounds) {
1186
+ this.mesh.position.z = Math.sign(this.mesh.position.z) * bounds;
1187
+ this.velocity.z *= -0.6;
1188
+ }
1189
+ this.fitness -= 10;
1190
+ }
1191
+ }
1192
+
1193
+ destroy() {
1194
+ if (this.parkingSpot) {
1195
+ this.parkingSpot.occupied = false;
1196
+ this.parkingSpot.car = null;
1197
+ }
1198
+
1199
+ this.flockLines.forEach(line => {
1200
+ if (line.parent) scene.remove(line);
1201
+ });
1202
+
1203
+ if (this.mesh.parent) {
1204
+ scene.remove(this.mesh);
1205
+ }
1206
+ }
1207
+ }
1208
+
1209
+ function init() {
1210
+ // Enhanced scene setup
1211
+ scene = new THREE.Scene();
1212
+ scene.background = new THREE.Color(0x87CEEB);
1213
+ scene.fog = new THREE.Fog(0x87CEEB, 300, 1000);
1214
+
1215
+ camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 2000);
1216
+ camera.position.set(0, 150, 150);
1217
+ camera.lookAt(0, 0, 0);
1218
+
1219
+ renderer = new THREE.WebGLRenderer({ antialias: true });
1220
+ renderer.setSize(window.innerWidth, window.innerHeight);
1221
+ renderer.shadowMap.enabled = true;
1222
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
1223
+ document.body.appendChild(renderer.domElement);
1224
+
1225
+ // Lighting
1226
+ const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
1227
+ scene.add(ambientLight);
1228
+
1229
+ const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
1230
+ directionalLight.position.set(100, 100, 50);
1231
+ directionalLight.castShadow = true;
1232
+ directionalLight.shadow.mapSize.width = 2048;
1233
+ directionalLight.shadow.mapSize.height = 2048;
1234
+ scene.add(directionalLight);
1235
+
1236
+ createTrafficWorld();
1237
+ createInitialPopulation();
1238
+
1239
+ clock = new THREE.Clock();
1240
+
1241
+ // Event listeners
1242
+ window.addEventListener('resize', onWindowResize);
1243
+ setupEventListeners();
1244
+
1245
+ animate();
1246
+ }
1247
+
1248
+ function createTrafficWorld() {
1249
+ // Ground
1250
+ const groundGeometry = new THREE.PlaneGeometry(1000, 1000);
1251
+ const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x228B22 });
1252
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
1253
+ ground.rotation.x = -Math.PI / 2;
1254
+ ground.receiveShadow = true;
1255
+ scene.add(ground);
1256
+
1257
+ createRoadNetwork();
1258
+ createBuildingsWithParkingLots();
1259
+ }
1260
+
1261
+ function createRoadNetwork() {
1262
+ const roadMaterial = new THREE.MeshLambertMaterial({ color: 0x444444 });
1263
+ const lineMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
1264
+
1265
+ // Create road grid
1266
+ for (let i = -300; i <= 300; i += ROAD_SPACING) {
1267
+ // Horizontal roads
1268
+ const hRoadGeometry = new THREE.PlaneGeometry(600, ROAD_WIDTH);
1269
+ const hRoad = new THREE.Mesh(hRoadGeometry, roadMaterial);
1270
+ hRoad.rotation.x = -Math.PI / 2;
1271
+ hRoad.position.set(0, 0.1, i);
1272
+ scene.add(hRoad);
1273
+
1274
+ // Lane dividers for horizontal roads
1275
+ const hLineGeometry = new THREE.PlaneGeometry(600, 0.3);
1276
+ const hLine = new THREE.Mesh(hLineGeometry, lineMaterial);
1277
+ hLine.rotation.x = -Math.PI / 2;
1278
+ hLine.position.set(0, 0.15, i);
1279
+ scene.add(hLine);
1280
+
1281
+ // Vertical roads
1282
+ const vRoadGeometry = new THREE.PlaneGeometry(ROAD_WIDTH, 600);
1283
+ const vRoad = new THREE.Mesh(vRoadGeometry, roadMaterial);
1284
+ vRoad.rotation.x = -Math.PI / 2;
1285
+ vRoad.position.set(i, 0.1, 0);
1286
+ scene.add(vRoad);
1287
+
1288
+ // Lane dividers for vertical roads
1289
+ const vLineGeometry = new THREE.PlaneGeometry(0.3, 600);
1290
+ const vLine = new THREE.Mesh(vLineGeometry, lineMaterial);
1291
+ vLine.rotation.x = -Math.PI / 2;
1292
+ vLine.position.set(i, 0.15, 0);
1293
+ scene.add(vLine);
1294
+ }
1295
+ }
1296
+
1297
+ function createBuildingsWithParkingLots() {
1298
+ world.buildings = [];
1299
+ world.parkingLots = [];
1300
+
1301
+ const buildingMaterial = new THREE.MeshLambertMaterial({ color: 0x666666 });
1302
+ const parkingMaterial = new THREE.MeshLambertMaterial({ color: 0x333333 });
1303
+ const spotMaterial = new THREE.MeshLambertMaterial({ color: 0xffffff });
1304
+
1305
+ // Create buildings at strategic locations
1306
+ const buildingLocations = [
1307
+ { x: -75, z: -75 }, { x: 75, z: -75 },
1308
+ { x: -75, z: 75 }, { x: 75, z: 75 },
1309
+ { x: -225, z: -225 }, { x: 225, z: -225 },
1310
+ { x: -225, z: 225 }, { x: 225, z: 225 },
1311
+ { x: 0, z: -150 }, { x: 0, z: 150 },
1312
+ { x: -150, z: 0 }, { x: 150, z: 0 }
1313
+ ];
1314
+
1315
+ buildingLocations.forEach(loc => {
1316
+ // Create building
1317
+ const width = 25 + Math.random() * 15;
1318
+ const height = 15 + Math.random() * 25;
1319
+ const depth = 25 + Math.random() * 15;
1320
+
1321
+ const buildingGeometry = new THREE.BoxGeometry(width, height, depth);
1322
+ const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
1323
+ building.position.set(loc.x, height / 2, loc.z);
1324
+ building.castShadow = true;
1325
+ scene.add(building);
1326
+
1327
+ world.buildings.push({ mesh: building });
1328
+
1329
+ // Create large parking lot next to building
1330
+ const parkingLot = {
1331
+ center: new THREE.Vector3(loc.x + width/2 + 20, 0.1, loc.z),
1332
+ spots: []
1333
+ };
1334
+
1335
+ // Parking lot ground
1336
+ const lotGeometry = new THREE.PlaneGeometry(40, 30);
1337
+ const lot = new THREE.Mesh(lotGeometry, parkingMaterial);
1338
+ lot.rotation.x = -Math.PI / 2;
1339
+ lot.position.copy(parkingLot.center);
1340
+ scene.add(lot);
1341
+
1342
+ // Create parking spots (4x6 grid = 24 spots)
1343
+ for (let row = 0; row < 4; row++) {
1344
+ for (let col = 0; col < 6; col++) {
1345
+ const spotX = parkingLot.center.x + (col - 2.5) * 6;
1346
+ const spotZ = parkingLot.center.z + (row - 1.5) * 7;
1347
+
1348
+ // Spot marking
1349
+ const spotGeometry = new THREE.PlaneGeometry(PARKING_SPOT_SIZE.width, PARKING_SPOT_SIZE.length);
1350
+ const spotMesh = new THREE.Mesh(spotGeometry, spotMaterial);
1351
+ spotMesh.rotation.x = -Math.PI / 2;
1352
+ spotMesh.position.set(spotX, 0.12, spotZ);
1353
+ scene.add(spotMesh);
1354
+
1355
+ const spot = {
1356
+ position: new THREE.Vector3(spotX, 1, spotZ),
1357
+ occupied: false,
1358
+ car: null,
1359
+ mesh: spotMesh
1360
+ };
1361
+
1362
+ parkingLot.spots.push(spot);
1363
+ }
1364
+ }
1365
+
1366
+ world.parkingLots.push(parkingLot);
1367
+ });
1368
+ }
1369
+
1370
+ function createInitialPopulation() {
1371
+ population = [];
1372
+
1373
+ for (let i = 0; i < populationSize; i++) {
1374
+ // Start cars on roads when possible
1375
+ const roadPositions = [
1376
+ { x: -280, z: 0 }, { x: 280, z: 0 },
1377
+ { x: 0, z: -280 }, { x: 0, z: 280 },
1378
+ { x: -130, z: 0 }, { x: 130, z: 0 },
1379
+ { x: 0, z: -130 }, { x: 0, z: 130 }
1380
+ ];
1381
+
1382
+ const startPos = roadPositions[i % roadPositions.length];
1383
+ const car = new TrafficCar(
1384
+ startPos.x + (Math.random() - 0.5) * 10,
1385
+ startPos.z + (Math.random() - 0.5) * 10
1386
+ );
1387
+
1388
+ population.push(car);
1389
+ scene.add(car.mesh);
1390
+ }
1391
+ }
1392
+
1393
+ function evolvePopulation() {
1394
+ // Sort by fitness
1395
+ population.sort((a, b) => b.fitness - a.fitness);
1396
+
1397
+ // Advanced selection
1398
+ const eliteCount = Math.floor(populationSize * 0.15);
1399
+ const tournamentCount = Math.floor(populationSize * 0.25);
1400
+ const mutatedCount = populationSize - eliteCount - tournamentCount;
1401
+
1402
+ const survivors = population.slice(0, eliteCount);
1403
+
1404
+ // Tournament selection
1405
+ for (let i = 0; i < tournamentCount; i++) {
1406
+ const tournamentSize = 5;
1407
+ let best = null;
1408
+ let bestFitness = -Infinity;
1409
+
1410
+ for (let j = 0; j < tournamentSize; j++) {
1411
+ const candidate = population[Math.floor(Math.random() * Math.min(population.length, populationSize * 0.5))];
1412
+ if (candidate.fitness > bestFitness) {
1413
+ best = candidate;
1414
+ bestFitness = candidate.fitness;
1415
+ }
1416
+ }
1417
+ if (best) survivors.push(best);
1418
+ }
1419
+
1420
+ // Clean up old population
1421
+ population.forEach(car => car.destroy());
1422
+
1423
+ // Create new population
1424
+ const newPopulation = [];
1425
+ const roadPositions = [
1426
+ { x: -280, z: 0 }, { x: 280, z: 0 },
1427
+ { x: 0, z: -280 }, { x: 0, z: 280 },
1428
+ { x: -130, z: 0 }, { x: 130, z: 0 },
1429
+ { x: 0, z: -130 }, { x: 0, z: 130 }
1430
+ ];
1431
+
1432
+ // Elite reproduction
1433
+ survivors.forEach((parent, index) => {
1434
+ const startPos = roadPositions[index % roadPositions.length];
1435
+ const newCar = new TrafficCar(
1436
+ startPos.x + (Math.random() - 0.5) * 10,
1437
+ startPos.z + (Math.random() - 0.5) * 10
1438
+ );
1439
+ newCar.brain = parent.brain.copy();
1440
+ newPopulation.push(newCar);
1441
+ scene.add(newCar.mesh);
1442
+ });
1443
+
1444
+ // Mutated offspring
1445
+ while (newPopulation.length < populationSize) {
1446
+ const parentIndex = Math.floor(Math.random() * Math.min(survivors.length, eliteCount * 2));
1447
+ const parent = survivors[parentIndex];
1448
+ const startPos = roadPositions[newPopulation.length % roadPositions.length];
1449
+
1450
+ const child = new TrafficCar(
1451
+ startPos.x + (Math.random() - 0.5) * 10,
1452
+ startPos.z + (Math.random() - 0.5) * 10
1453
+ );
1454
+ child.brain = parent.brain.copy();
1455
+
1456
+ const mutationRate = parent.fitness > bestFitness * 0.8 ? 0.05 : 0.15;
1457
+ child.brain.mutate(mutationRate);
1458
+
1459
+ newPopulation.push(child);
1460
+ scene.add(child.mesh);
1461
+ }
1462
+
1463
+ population = newPopulation;
1464
+
1465
+ // Update epoch
1466
+ epoch++;
1467
+ timeLeft = epochTime;
1468
+ bestFitness = Math.max(bestFitness, survivors[0]?.fitness || 0);
1469
+ crashCount = 0;
1470
+ parkingEvents = 0;
1471
+ laneViolations = 0;
1472
+
1473
+ console.log(`Epoch ${epoch}: Best fitness: ${bestFitness.toFixed(1)}, Parking events: ${parkingEvents}`);
1474
+ }
1475
+
1476
+ function animate() {
1477
+ requestAnimationFrame(animate);
1478
+
1479
+ if (!paused) {
1480
+ const deltaTime = Math.min(clock.getDelta() * speedMultiplier, 0.1);
1481
+
1482
+ timeLeft -= deltaTime;
1483
+ if (timeLeft <= 0) {
1484
+ evolvePopulation();
1485
+ }
1486
+
1487
+ updatePopulation(deltaTime);
1488
+ updateCamera();
1489
+ updateUI();
1490
+ }
1491
+
1492
+ renderer.render(scene, camera);
1493
+ }
1494
+
1495
+ function updatePopulation(deltaTime) {
1496
+ let stats = {
1497
+ alive: 0,
1498
+ leaders: 0,
1499
+ convoy: 0,
1500
+ parked: 0,
1501
+ solo: 0,
1502
+ maxConvoySize: 0,
1503
+ totalRoadTime: 0,
1504
+ totalConvoyTime: 0,
1505
+ totalParkingScore: 0,
1506
+ totalViolations: 0,
1507
+ totalFollowingDistance: 0,
1508
+ followingCount: 0
1509
+ };
1510
+
1511
+ population.forEach(car => {
1512
+ car.update(deltaTime);
1513
+
1514
+ if (!car.crashed) {
1515
+ stats.alive++;
1516
+ stats.totalRoadTime += car.roadTime;
1517
+ stats.totalConvoyTime += car.convoyTime;
1518
+ stats.totalParkingScore += car.parkingScore;
1519
+ stats.totalViolations += car.trafficViolations;
1520
+
1521
+ if (car.isParked) {
1522
+ stats.parked++;
1523
+ } else if (car.role === 'leader') {
1524
+ stats.leaders++;
1525
+ stats.maxConvoySize = Math.max(stats.maxConvoySize, car.convoyFollowers.length + 1);
1526
+ } else if (car.convoyPosition >= 0) {
1527
+ stats.convoy++;
1528
+ if (car.followingDistance > 0) {
1529
+ stats.totalFollowingDistance += car.followingDistance;
1530
+ stats.followingCount++;
1531
+ }
1532
+ } else {
1533
+ stats.solo++;
1534
+ }
1535
+ }
1536
+ });
1537
+
1538
+ window.populationStats = stats;
1539
+ }
1540
+
1541
+ function updateCamera() {
1542
+ if (cameraMode === 'follow_best') {
1543
+ // Follow best performing car
1544
+ let bestCar = population.reduce((best, car) => {
1545
+ if (car.crashed || car.isParked) return best;
1546
+ return !best || car.fitness > best.fitness ? car : best;
1547
+ }, null);
1548
+
1549
+ if (bestCar) {
1550
+ const targetPos = bestCar.mesh.position.clone();
1551
+ targetPos.y += 40;
1552
+ targetPos.add(bestCar.velocity.clone().normalize().multiplyScalar(25));
1553
+
1554
+ camera.position.lerp(targetPos, 0.03);
1555
+ camera.lookAt(bestCar.mesh.position);
1556
+ }
1557
+ } else if (cameraMode === 'follow_convoy') {
1558
+ // Follow largest convoy
1559
+ let largestConvoy = population.find(car =>
1560
+ car.role === 'leader' && car.convoyFollowers.length > 0
1561
+ );
1562
+
1563
+ if (largestConvoy) {
1564
+ const targetPos = largestConvoy.mesh.position.clone();
1565
+ targetPos.y += 50;
1566
+ targetPos.add(largestConvoy.velocity.clone().normalize().multiplyScalar(30));
1567
+
1568
+ camera.position.lerp(targetPos, 0.03);
1569
+ camera.lookAt(largestConvoy.mesh.position);
1570
+ }
1571
+ } else {
1572
+ // Overview mode
1573
+ camera.position.lerp(new THREE.Vector3(0, 180, 180), 0.02);
1574
+ camera.lookAt(0, 0, 0);
1575
+ }
1576
+ }
1577
+
1578
+ function updateUI() {
1579
+ const stats = window.populationStats || {};
1580
+
1581
+ // Main UI
1582
+ document.getElementById('epoch').textContent = epoch;
1583
+ document.getElementById('epochTime').textContent = Math.ceil(timeLeft);
1584
+ document.getElementById('population').textContent = stats.alive || 0;
1585
+ document.getElementById('bestFitness').textContent = Math.round(bestFitness);
1586
+
1587
+ // Progress bar
1588
+ const progress = ((epochTime - timeLeft) / epochTime) * 100;
1589
+ document.getElementById('timeProgress').style.width = `${progress}%`;
1590
+
1591
+ // Traffic stats
1592
+ if (stats.alive > 0) {
1593
+ document.getElementById('trafficIQ').textContent = Math.round(50 + (bestFitness / 20));
1594
+ document.getElementById('roadMastery').textContent = Math.round((stats.totalRoadTime / stats.alive) * 10);
1595
+ document.getElementById('laneDiscipline').textContent = Math.round(Math.max(0, 100 - (stats.totalViolations / stats.alive) * 10));
1596
+ document.getElementById('roadAdherence').textContent = Math.round((stats.totalRoadTime / (stats.totalRoadTime + 1)) * 100);
1597
+ }
1598
+
1599
+ // Convoy stats
1600
+ document.getElementById('leaderCount').textContent = stats.leaders || 0;
1601
+ document.getElementById('convoyCount').textContent = stats.convoy || 0;
1602
+ document.getElementById('parkedCount').textContent = stats.parked || 0;
1603
+ document.getElementById('soloCount').textContent = stats.solo || 0;
1604
+ document.getElementById('largestConvoy').textContent = stats.maxConvoySize || 0;
1605
+
1606
+ // Following distance
1607
+ if (stats.followingCount > 0) {
1608
+ document.getElementById('followingDistance').textContent = (stats.totalFollowingDistance / stats.followingCount).toFixed(1);
1609
+ }
1610
+
1611
+ // Generation stats
1612
+ const totalDistance = population.reduce((sum, car) => sum + car.distanceTraveled, 0);
1613
+ const maxConvoyLength = Math.max(...population.map(car => car.convoyFollowers?.length || 0));
1614
+
1615
+ document.getElementById('totalDistance').textContent = Math.round(totalDistance);
1616
+ document.getElementById('parkingEvents').textContent = parkingEvents;
1617
+ document.getElementById('laneViolations').textContent = laneViolations;
1618
+ document.getElementById('convoyLength').textContent = maxConvoyLength;
1619
+ document.getElementById('crashCount').textContent = crashCount;
1620
+
1621
+ // Parking stats
1622
+ const totalSpots = world.parkingLots.reduce((sum, lot) => sum + lot.spots.length, 0);
1623
+ const occupiedSpots = world.parkingLots.reduce((sum, lot) =>
1624
+ sum + lot.spots.filter(spot => spot.occupied).length, 0);
1625
+
1626
+ document.getElementById('spotsOccupied').textContent = occupiedSpots;
1627
+ document.getElementById('parkingSuccess').textContent = totalSpots > 0 ? Math.round((occupiedSpots / totalSpots) * 100) : 0;
1628
+
1629
+ updateTopPerformers();
1630
+ }
1631
+
1632
+ function updateTopPerformers() {
1633
+ const sorted = [...population]
1634
+ .filter(car => !car.crashed)
1635
+ .sort((a, b) => b.fitness - a.fitness)
1636
+ .slice(0, 5);
1637
+
1638
+ const topPerformersDiv = document.getElementById('topPerformers');
1639
+ topPerformersDiv.innerHTML = '';
1640
+
1641
+ sorted.forEach((car, i) => {
1642
+ const div = document.createElement('div');
1643
+ const roleIcon = {
1644
+ leader: 'πŸ‘‘',
1645
+ parker: 'πŸ…ΏοΈ',
1646
+ driver: 'πŸš—'
1647
+ }[car.role] || 'πŸš—';
1648
+
1649
+ const statusIcon = car.isParked ? 'πŸ…ΏοΈ' : (car.convoyPosition >= 0 ? 'πŸš›' : 'πŸš—');
1650
+
1651
+ div.innerHTML = `${i + 1}. ${roleIcon}${statusIcon} F:${Math.round(car.fitness)} | Lane:${Math.round(car.laneDiscipline * 100)}% | Road:${Math.round(car.roadTime)}s`;
1652
+ div.className = car.isParked ? 'parked' : (car.role === 'leader' ? 'leader' : (car.convoyPosition >= 0 ? 'convoy' : 'solo'));
1653
+ topPerformersDiv.appendChild(div);
1654
+ });
1655
+ }
1656
+
1657
+ function setupEventListeners() {
1658
+ document.getElementById('pauseBtn').addEventListener('click', togglePause);
1659
+ document.getElementById('resetBtn').addEventListener('click', resetSimulation);
1660
+ document.getElementById('speedBtn').addEventListener('click', toggleSpeed);
1661
+ document.getElementById('viewBtn').addEventListener('click', toggleView);
1662
+ document.getElementById('flockBtn').addEventListener('click', toggleFlockLines);
1663
+ document.getElementById('trafficBtn').addEventListener('click', toggleTrafficRules);
1664
+ }
1665
+
1666
+ function togglePause() {
1667
+ paused = !paused;
1668
+ document.getElementById('pauseBtn').textContent = paused ? 'Resume' : 'Pause';
1669
+ if (!paused) clock.start();
1670
+ }
1671
+
1672
+ function resetSimulation() {
1673
+ epoch = 1;
1674
+ timeLeft = epochTime;
1675
+ bestFitness = 0;
1676
+ crashCount = 0;
1677
+ parkingEvents = 0;
1678
+ laneViolations = 0;
1679
+
1680
+ // Reset parking lots
1681
+ world.parkingLots.forEach(lot => {
1682
+ lot.spots.forEach(spot => {
1683
+ spot.occupied = false;
1684
+ spot.car = null;
1685
+ });
1686
+ });
1687
+
1688
+ population.forEach(car => car.destroy());
1689
+ createInitialPopulation();
1690
+ }
1691
+
1692
+ function toggleSpeed() {
1693
+ speedMultiplier = speedMultiplier === 1 ? 2 : speedMultiplier === 2 ? 5 : 1;
1694
+ document.getElementById('speedBtn').textContent = `Speed: ${speedMultiplier}x`;
1695
+ }
1696
+
1697
+ function toggleView() {
1698
+ const modes = ['overview', 'follow_best', 'follow_convoy'];
1699
+ const currentIndex = modes.indexOf(cameraMode);
1700
+ cameraMode = modes[(currentIndex + 1) % modes.length];
1701
+
1702
+ const displayNames = {
1703
+ overview: 'Overview',
1704
+ follow_best: 'Follow Best',
1705
+ follow_convoy: 'Follow Convoy'
1706
+ };
1707
+
1708
+ document.getElementById('viewBtn').textContent = `View: ${displayNames[cameraMode]}`;
1709
+ }
1710
+
1711
+ function toggleFlockLines() {
1712
+ showFlockLines = !showFlockLines;
1713
+ document.getElementById('flockBtn').textContent = `Networks: ${showFlockLines ? 'ON' : 'OFF'}`;
1714
+
1715
+ population.forEach(car => {
1716
+ car.flockLines.forEach(line => {
1717
+ if (showFlockLines && !line.parent) {
1718
+ scene.add(line);
1719
+ } else if (!showFlockLines && line.parent) {
1720
+ scene.remove(line);
1721
+ }
1722
+ });
1723
+ });
1724
+ }
1725
+
1726
+ function toggleTrafficRules() {
1727
+ trafficRules = !trafficRules;
1728
+ document.getElementById('trafficBtn').textContent = `Traffic Rules: ${trafficRules ? 'ON' : 'OFF'}`;
1729
+ }
1730
+
1731
+ function onWindowResize() {
1732
+ camera.aspect = window.innerWidth / window.innerHeight;
1733
+ camera.updateProjectionMatrix();
1734
+ renderer.setSize(window.innerWidth, window.innerHeight);
1735
+ }
1736
+
1737
+ init();
1738
+ </script>
1739
+ </body>
1740
+ </html>