-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathversions.html
More file actions
661 lines (628 loc) · 46.1 KB
/
Copy pathversions.html
File metadata and controls
661 lines (628 loc) · 46.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover" />
<title>INFINI RACER ✦ ALL VERSIONS</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&family=Major+Mono+Display&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="home.css" />
</head>
<body>
<canvas id="psych"></canvas>
<main>
<header class="hero">
<div class="glyphs">▲ ◆ ▼ ✦ ◇ ◆ ▼ ▲ ✦ ◆ ▼ ▲ ◆ ✦</div>
<h1 class="title" aria-label="Infini Racer">
<span class="word"><span style="--i:0">I</span><span style="--i:1">N</span><span style="--i:2">F</span><span style="--i:3">I</span><span style="--i:4">N</span><span style="--i:5">I</span></span>
<span class="word"><span style="--i:6">R</span><span style="--i:7">A</span><span style="--i:8">C</span><span style="--i:9">E</span><span style="--i:10">R</span></span>
</h1>
<p class="subtitle">⚡ ALL VERSIONS ⚡ A LIVING SCROLL OF EVERY ITERATION ⚡</p>
</header>
<a class="all-versions-link" href="index.html">◀ BACK TO HOME</a>
<section class="versions">
<!-- v025 -->
<article class="ver acc-4">
<div class="ver-header">
<div>
<div class="ver-no">V025</div>
<div class="ver-tag">★ easier: checkpoints pulled in ★</div>
</div>
<a class="ver-play" href="v025/">▶ PLAY V025</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>it's a bit to difficult. make the chekpoints and finish line slightly closer together</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Stage distances pulled in: 65 / 150 / 255 k → <strong>55 / 130 / 220 k</strong>. Gaps drop from 65 / 85 / 105 to <strong>55 / 75 / 90</strong> (each ~14 % shorter)</li>
<li>Same flat 60 s per stage and same carry-over rule. Required sustained pace drops from ~1500 u/s (94 % MAX_SPEED) to ~1300 u/s (81 % MAX_SPEED) — challenging but no longer brutal</li>
<li>At MAX_SPEED a clean run now leaves ~42 s on the final clock (was ~21 s). Bonus pool is bigger: 42 × 250 k ≈ 10.6 M</li>
</ul>
</div>
</article>
<!-- v024 -->
<article class="ver acc-2">
<div class="ver-header">
<div>
<div class="ver-no">V024</div>
<div class="ver-tag">★ track-intro: responsive + snappier ★</div>
</div>
<a class="ver-play" href="v024/">▶ PLAY V024</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>oops the track title doesn't fit on a mobile screen, adjust accordingly and fade it out quicker</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>Responsive title sizing.</strong> Measures the track name at the ideal 84 px and shrinks to fit <code>W · 0.88</code>. Long titles fit cleanly on narrow phones; desktops get the full size</li>
<li><strong>Snappier intro.</strong> <code>TRACK_INTRO_DURATION</code> 3.6 s → 2.4 s; <code>TRACK_INTRO_HOLD</code> 1.7 s → 1.3 s</li>
</ul>
</div>
</article>
<!-- v023 -->
<article class="ver acc-3">
<div class="ver-header">
<div>
<div class="ver-no">V023</div>
<div class="ver-tag">★ track-intro: pop + fade-out-while-growing ★</div>
</div>
<a class="ver-play" href="v023/">▶ PLAY V023</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>actually it's fine, but show the track name more prominently at the start of the race then move into into position at the top in a cool way
(follow-up) always show the track name at the top and fade it out instead of moving it to the top. it should fade out while increasing in size</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Big centred track intro at the start of every track. Two-line readout — <code>TRACK NN</code> in amber + the track name in white-on-cyan-halo at ~84 px Press Start 2P</li>
<li>Pop-in (0 → 0.35 s): overshoot bounce scale 0.5 → 1.15 → 1.0 + a brief white screen-flash</li>
<li>Hold (0.35 → 1.7 s): full size centred at <code>H · 0.42</code></li>
<li>Fade-out + grow (1.7 → 3.6 s): scale 1.0× → 2.0× while alpha 1 → 0 — title expands past the camera</li>
<li>HUD <code>#track-label</code> stays visible the whole time; canvas intro plays on top</li>
<li>Race clock paused while <code>introT < TRACK_INTRO_DURATION</code></li>
</ul>
</div>
</article>
<!-- v022 -->
<article class="ver acc-1">
<div class="ver-header">
<div>
<div class="ver-no">V022</div>
<div class="ver-tag">★ multi-track campaign + named tracks ★</div>
</div>
<a class="ver-play" href="v022/">▶ PLAY V022</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok so this is track 1. maintain the random seed for track 1 but choose other seeds for the other tracks, after track 1 comes track 2 and so on, give each track a new name based on boards of canada song titles, these should be random but seeded so procedural. inform the player of which track they're about to play. also if you finish a track, leave the score on a bit longer before going to the next track. after crossing the line the vehicle should slow down so as not to crash</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>Track 1 keeps the legacy seed (7341).</strong> <code>trackSeedFor(n)</code> returns 7341 for track 1; tracks 2+ derive a deterministic seed from <code>hash01(n · 13.37 + 17.6) · 1 000 000</code></li>
<li><strong>Track names.</strong> A <code>TRACK_NAMES</code> array of band-song-title identifiers used as level labels. Track 1 is hard-coded to the first entry (ROYGBIV); <code>trackNameFor(n)</code> picks the rest via <code>hash01(n · 11.7 + 3.3)</code></li>
<li><strong>Multi-track campaign.</strong> <code>race.trackNum / trackName</code>; <code>runScoreAccum</code> carries score across tracks; <code>advanceToNextTrack()</code> regenerates the world with the new seed but preserves score. <code>commitReset()</code> is now the failure path only</li>
<li><strong>Persistent TRACK label</strong> at the top of the HUD with diff-caching</li>
<li><strong>Finish overlay extended</strong> 6 s → 9.5 s with a two-phase crossfade: score breakdown then NEXT TRACK callout</li>
<li><strong>Vehicle slows down past the line.</strong> <code>throttle=0, brake=1</code> override during the post-finish hold; off-track crash check skipped so a curve after the flag can't wipe out the celebration</li>
<li>Crash readout (WIPEOUT / TIME OVER) shows the full accumulated score</li>
</ul>
</div>
</article>
<!-- v021 -->
<article class="ver acc-4">
<div class="ver-header">
<div>
<div class="ver-no">V021</div>
<div class="ver-tag">★ tighter clock + longer race ★</div>
</div>
<a class="ver-play" href="v021/">▶ PLAY V021</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok tweak it so it's only 60 seconds per stage, and make them slightly further apart</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Per-stage duration 90 s → 60 s. Total base budget 270 s → 180 s</li>
<li>Distances 60 / 140 / 240 k → 65 / 150 / 255 k. Gaps now 65 k / 85 k / 105 k</li>
<li>Net: time-out back in play even with carry-over. Need ~1500 u/s (~94% MAX_SPEED) to finish on the wire</li>
</ul>
</div>
</article>
<!-- v020 -->
<article class="ver acc-2">
<div class="ver-header">
<div>
<div class="ver-no">V020</div>
<div class="ver-tag">★ longer stages + carry-over + CHECKPOINT! ★</div>
</div>
<a class="ver-play" href="v020/">▶ PLAY V020</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok a few things, make the stages further apart say 10 times further apart, add any remaining time at checkpoint to the new countdown. give the player 90 seconds per stage and when you pass a checkpoint the word checkpoint! should be displayed in a cool way and then disappear.</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>Stages 10× further apart.</strong> Distances 6000 / 14000 / 24000 → <strong>60 000 / 140 000 / 240 000</strong></li>
<li><strong>90 seconds per stage</strong> across the board (was 22 / 22 / 25)</li>
<li><strong>Time carries over at checkpoints.</strong> Leftover seconds added to the next stage's 90 s — fast play compounds</li>
<li><strong>CHECKPOINT! banner</strong> renders centre-screen on every intermediate cross. Snap-zoom from 1.4× scale to 1.0× while fading in (0.15 s), holds ~0.9 s, then fades out over 0.45 s. Two-pass text — wide amber drop-shadow halo + slightly smaller white-on-amber core</li>
<li>Final flag still skips the CHECKPOINT! banner — the finish overlay takes over</li>
</ul>
</div>
</article>
<!-- v019 -->
<article class="ver acc-3">
<div class="ver-header">
<div>
<div class="ver-no">V019</div>
<div class="ver-tag">★ OutRun-style timed stages ★</div>
</div>
<a class="ver-play" href="v019/">▶ PLAY V019</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok let's make a radical change. it's too easy to get a high score by simply driving slowly. add an outrun style countdown where you must complete 3 stages in a certain time, think of it as 3 checkpoints, when you hit a checkpoint before time runs out the countdown resets after the third stage you get to the chequered flag and any time you had left on the clock is generously factored into your score. maintain the style.</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>3-stage race</strong> with countdown clock. Stage distances: 6000 / 14000 / 24000 units. Per-stage durations: 22 / 22 / 25 seconds. Each stage's checkpoint resets the timer to the next stage's duration. Reach the chequered flag at 24000 to finish</li>
<li><strong>TIME OVER</strong> — when <code>race.timeLeft</code> hits 0, <code>triggerCrash("timeout")</code> fires the existing slow-mo explosion sequence with a "TIME OVER" label instead of "WIPEOUT" (separate <code>crashReason</code> flag distinguishes them)</li>
<li><strong>Generous bonus at the flag</strong> — <code>race.finalBonus = floor(timeLeft × 250,000)</code> added on top of the base score (speed × distance at flag-cross)</li>
<li><strong>Top HUD restructured:</strong> big Press Start 2P <code>#timer</code> at top centre with urgency colours — cyan above 10 s, amber 5–10 s, rose under 5 s with a step-blink animation. Below it a pip indicator <code>1 ✦ 2 ✦ 3</code> with the active stage glowing amber and completed stages in cyan. Score sits below the pips at a smaller size</li>
<li>Intermediate checkpoints draw as a bright amber bar across the track; final is a two-row alternating white + cyan chequered banner</li>
<li><strong>Finish screen</strong> — <code>drawFinishOverlay()</code> renders "FINISH!" plus a score breakdown over a fading backdrop, holds 6 s then auto-resets</li>
</ul>
</div>
</article>
<!-- v018 -->
<article class="ver acc-1">
<div class="ver-header">
<div>
<div class="ver-no">V018</div>
<div class="ver-tag">★ start-screen pickup tutorial ★</div>
</div>
<a class="ver-play" href="v018/">▶ PLAY V018</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>explain what the hexagon pick ups do at the start, maybe show an example of the spinning hexagon pickup</blockquote>
<h3>CHANGES</h3>
<ul>
<li>New <strong>SHIELD PICKUP</strong> tutorial card in the start overlay — a live SVG hexagon that spins (<code>5s linear infinite</code>) with a pulsing white core (<code>0.9s ease-in-out</code>), matching the in-game magenta colour + glow</li>
<li>One-line explanation next to the demo: <em>"grab one to charge the side rails for 5 seconds — the track edges push you back harder, so you can lean into corners"</em></li>
<li>Styled to match the overlay aesthetic: dashed magenta border, low-opacity magenta tint, Press Start 2P label + VT323 description</li>
<li>SVG uses <code>drop-shadow</code> filters for the phosphor glow so the demo reads at the same vector-tube intensity as the in-game pickup</li>
<li><strong>Fix pass</strong> after first cut: the hex was orbiting (translating in a wide circle) rather than spinning in place. Cause: <code>transform-origin: center</code> on an inner <code><g></code> resolves to the SVG viewport's top-left in some browsers because <code>transform-box</code> defaults are inconsistent. Moved the spin to the outer <code><svg></code> element (a DOM element where <code>transform-origin: center</code> is well-behaved), kept the core's pulse on the inner circle with <code>transform-box: fill-box</code> so its scale stays pinned to its own centre. Added 6 white vertex phosphor dots that ride along with the rotation — matches the in-game render where each <code>beam()</code> call auto-emits dots at both endpoints</li>
</ul>
</div>
</article>
<!-- v017 -->
<article class="ver acc-3">
<div class="ver-header">
<div>
<div class="ver-no">V017</div>
<div class="ver-tag">★ brake-to-drift around corners ★</div>
</div>
<a class="ver-play" href="v017/">▶ PLAY V017</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok lets try adding drift around corners when the brake is applied</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>New <code>car.slipAngle</code></strong> field — radians offset between the car's heading and its actual motion direction. Translate now uses <code>motionDir = heading + slipAngle</code> instead of plain heading, so the world scrolls diagonally during a slide</li>
<li><strong>Drift trigger:</strong> brake held + steer commitment over <code>DRIFT_STEER_MIN = 0.18</code> + speed over <code>DRIFT_MIN_SPEED = 220</code>. Below the threshold the brake is just a brake (no slide)</li>
<li><strong>Yaw boost while drifting</strong> (<code>DRIFT_YAW_BOOST = 1.55</code>) — heading rotates faster than motion. Slip angle eases toward <code>±DRIFT_MAX_SLIP = 0.50 rad</code> (~29°) opposite the steer at <code>1/τ = 5.5</code>. When the trigger releases, slip decays back to zero at <code>1/τ = 6.5</code> — grip recovers</li>
<li><strong>Speed dynamics:</strong> brake during drift is softened (<code>DRIFT_BRAKE_MUL = 0.30</code>) so the slide actually continues, plus <code>DRIFT_DRAG = 90 u/s²</code> of bleed so it isn't free speed</li>
<li><strong>Visual cue:</strong> wheels brighten to hot orange (<code>#ff8d3a</code>) and thicken (1.4 → 1.8 px) while <code>|slipAngle| > 0.06 rad</code>. The diagonal world-scroll already shows the slide; the brighter wheels just emphasise it</li>
<li><code>commitReset()</code> clears <code>slipAngle</code> alongside the rest of the car state</li>
</ul>
</div>
</article>
<!-- v016 -->
<article class="ver acc-2">
<div class="ver-header">
<div>
<div class="ver-no">V016</div>
<div class="ver-tag">★ shield power-ups (rolled back from ramps) ★</div>
</div>
<a class="ver-play" href="v016/">▶ PLAY V016</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok I don't like the ramps, keep this version for reference but rollback and add power ups on the course that make the repellent effect of the side of the course stronger, during this period have the vehicle flash</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>Rollback:</strong> ramps + airborne physics dropped from <code>latest/</code>; v015 stays in the snapshot folder as the "we tried ramps" reference. Codebase is back to the v014 baseline before layering the new feature</li>
<li><strong>Power-ups:</strong> sparse pickups every 1500–2600 units (deterministic from <code>worldSeed</code>). Visual: a magenta wireframe hexagon rotating slowly at <code>phase × 1.2</code>, with a pulsing white phosphor core dot — clearly distinct from track (cyan) and scenery (amber)</li>
<li>Pickup detection: when the car centre is within <code>POWERUP_RADIUS = 24</code> of an unpicked one (along-track + lateral together), the pickup is marked taken and <code>car.boostT = 5</code> seconds</li>
<li><strong>Boost effect:</strong> while <code>car.boostT > 0</code>, edge repulsion is multiplied by <strong>3.0×</strong> (max push 620 → 1860 u/s) — well above <code>MAX_SPEED</code>, so the edge becomes a near-soft-wall you can't easily punch through. Returns to baseline when the timer expires</li>
<li><strong>Vehicle flash:</strong> while boost is active, body / cockpit / wheel colours switch to a magenta-tinted palette at ~6 Hz via <code>(Math.floor(phase × 12) & 1)</code>. Square wave gives a sharp on/off blink that reads as "powered up" rather than a soft glow</li>
<li><code>commitReset()</code> clears the powerups array + boost timer; <code>trimWorld()</code> drops pickups (taken or not) once they're well behind</li>
<li><strong>Fix pass:</strong> the "power-ups disappear" bug — four call sites computed track indices as <code>Math.floor(s / SEG_LEN)</code>, but after <code>trimWorld()</code> splices old segments off the front, <code>track[0].s</code> is no longer 0 so that math points at the wrong row (or clamps to the last segment, making the hexagon snap to the end of the visible track). Added a <code>trackIndexAt(s)</code> helper that uses <code>track[0].s</code> as the base offset; rewired scenery placement, power-up gen, power-up render, and distance markers through it</li>
<li>Hexagon visual radius 14 → 21, pickup hit radius 18 → 24</li>
</ul>
</div>
</article>
<!-- v015 -->
<article class="ver acc-5">
<div class="ver-header">
<div>
<div class="ver-no">V015</div>
<div class="ver-tag">★ ramps + airborne ★</div>
</div>
<a class="ver-play" href="v015/">▶ PLAY V015</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok let's try adding occasional ramps to the course, when you hit a ramp the vehicle leaves the ground and the road fades a bit and allows the vehicle to fly over any part of the course but must land on the track again or wipeout</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>Ramps:</strong> sparse procedural launches placed every ~900–1700 units (deterministic from <code>worldSeed</code>). Each ramp is a wireframe wedge — a forward-pointing triangle with two perspective cross-bars narrowing toward the apex — drawn in amber so it pops against the cyan track edges. Width clamped to a fraction of the (variable) track half-width so it never blocks the track</li>
<li><strong>Airborne physics:</strong> hitting a ramp footprint (along-track <em>and</em> lateral) launches the car. <code>altVel</code> = <code>220 + (speed/MAX_SPEED) · 460</code>, gravity <code>720 u/s²</code>. While airborne: forward velocity preserved (no throttle/brake/coast), edge repulsion off, off-track crash check skipped. Steering still works at ~45% authority. On landing, if the vehicle lands inside the track edges → safe; if not → <code>triggerCrash()</code></li>
<li><strong>Visual fade:</strong> <code>worldAlpha = 1 − 0.6 · min(1, altitude/70)</code> applied to <em>only</em> the <code>G_TRACK</code> + <code>G_SCEN</code> beams and dots — track + scenery dim while the car + thrust + intersection sparkles stay full strength. Group tag now travels with each dot too</li>
<li><strong>Car scale-up:</strong> while airborne, render scale <code>= zoom · (1 + altitude · 0.0065)</code> — the vehicle grows toward the camera as it climbs, gives the lift cue without touching the camera transform</li>
<li><code>commitReset()</code> clears airborne state + ramps array; <code>trimWorld()</code> drops ramps far behind the player alongside scenery</li>
<li><strong>Tuning pass:</strong> jumps higher + longer airtime, ramps sparser, fade stronger:
<ul>
<li><code>GRAVITY</code> 720 → 600 / <code>LAUNCH_BASE</code> 220 → 360 / <code>LAUNCH_BONUS</code> 460 → 680 — full-speed jumps now ~3.5 s airtime peaking ~900 u (was ~1.9 s / ~320 u)</li>
<li><code>RAMP_GAP_MIN</code> 900 → 2000, <code>RAMP_GAP_RANGE</code> 800 → 1600 — ramps now every 2000–3600 units (≈ half as many)</li>
<li>World fade: <code>worldAlpha = 1 − 0.78 · altitudeFactor</code> (was 0.60), saturating at altitude 90 (was 70). Track + scenery drop to ~0.22 alpha at peak (was 0.40)</li>
<li>Car-scale formula clamped via <code>Math.min(2.4, 1 + altitude · 0.004)</code> so very high jumps don't make the car fill the screen</li>
</ul>
</li>
</ul>
</div>
</article>
<!-- v014 -->
<article class="ver acc-3">
<div class="ver-header">
<div>
<div class="ver-no">V014</div>
<div class="ver-tag">★ start screen tidy ★</div>
</div>
<a class="ver-play" href="v014/">▶ PLAY V014</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>the key info on the first screen is a little messy, tidy that up a bit</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Controls list rebuilt as a 4-row grid: <strong>keys right-aligned</strong> | <strong>action left-aligned</strong>, with a fixed column gap. Each row covers one direction so the layout is symmetrical (LEFT and RIGHT are now their own rows instead of being lumped together as STEER)</li>
<li><code>kbd</code> elements get a fixed <code>min-width: 28px</code> + <code>display: inline-flex</code> so arrow-glyph and letter keys all render the same size — no more uneven gaps from glyphs of different widths</li>
<li>Arrow + letter alternates sit beside each other (no <code>/</code> separator); the grouping reads naturally without punctuation noise</li>
<li>Action labels coloured cyan with a soft glow so they pop next to the white keycaps; old reliance on <code>&nbsp;</code> for spacing removed</li>
<li>"OR DRAG THE JOYSTICK" → "OR USE THE JOYSTICK" (covers both touch and mouse)</li>
</ul>
</div>
</article>
<!-- v013 -->
<article class="ver acc-1">
<div class="ver-header">
<div>
<div class="ver-no">V013</div>
<div class="ver-tag">★ joystick lifted off bottom edge ★</div>
</div>
<a class="ver-play" href="v013/">▶ PLAY V013</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok - small change move the joystick up a bit because on mobile it's too close to the edge of the screen causing unintentional gestures</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Joystick centre raised: <code>cy = H - r - 80</code> (was <code>H - H · 0.10</code>) — keeps a consistent ~50 px clearance below the BRAKE / STEER labels and out of the iOS / Android swipe-up gesture zone, regardless of viewport height</li>
<li>Pointer hit-zone updated to follow the joystick (<code>py ≥ stick.cy - stick.r - 20</code>) so the top of the knob stays grabbable now that the stick sits higher</li>
</ul>
</div>
</article>
<!-- v012 -->
<article class="ver acc-4">
<div class="ver-header">
<div>
<div class="ver-no">V012</div>
<div class="ver-tag">★ rollback to canvas-2D ★</div>
</div>
<a class="ver-play" href="v012/">▶ PLAY V012</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>do 2 <em>(option 2 from the v011 follow-up: rollback)</em></blockquote>
<h3>CHANGES</h3>
<ul>
<li>WebGPU code path removed from <code>latest/</code> — back to single-canvas, canvas-2D-only rendering</li>
<li>Stripped: WGSL shaders, <code>initWebGPU</code>, two-canvas stack, <code>?renderer=webgpu</code> flag, render-loop branches, resize hooks for the GPU canvas</li>
<li>v011 stays frozen as the "we tried this" reference. The shader work + integration glue is preserved there if we want to revisit when scene density actually outgrows canvas-2D</li>
<li>Net code delta: <code>game.js</code> ~1500 lines → ~1200 lines; <code>styles.css</code> back to single canvas rule; <code>index.html</code> back to single canvas element</li>
</ul>
<h3>WHY</h3>
<ul>
<li>At v010 scene density on a fast Mac, both renderers easily hit the 60 fps cap, so the WebGPU win is invisible — it's <em>headroom</em> for content we don't have yet</li>
<li>The shader-rendered output looked slightly less crisp than the canvas-2D layered strokes. A second shader pass tightened it but at that point we're maintaining two render paths for parity, not headroom</li>
<li>Decision: pay the perf cost when there's content that needs it; until then, the simpler single-path canvas-2D code wins on maintainability</li>
</ul>
</div>
</article>
<!-- v011 -->
<article class="ver acc-3">
<div class="ver-header">
<div>
<div class="ver-no">V011</div>
<div class="ver-tag">★ webgpu opt-in ★</div>
</div>
<a class="ver-play" href="v011/?renderer=webgpu">▶ PLAY V011 (WebGPU)</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>I still feel like if we use webgpu we'd free ourselves up to worry less about performance, we can always rollback if it doesn't work</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>Optional WebGPU renderer for beams + dots.</strong> Activated by <code>?renderer=webgpu</code> in the URL; falls back automatically to canvas 2D if <code>navigator.gpu</code> is missing or device init fails. Game logic, joystick, crash explosion, and HUD stay on the canvas-2D layer</li>
<li>Two-canvas stack: <code><canvas id="game-gpu"></code> at z-index 0 (WebGPU output, opaque, full screen) sits behind the existing <code><canvas id="game"></code> at z-index 1 (canvas-2D, cleared transparent when GPU is active so the layer below shows through)</li>
<li><strong>Line shader (WGSL):</strong> one instance per beam, vertex shader expands a quad along the line with rounded-cap glow padding, fragment shader computes capsule SDF and outputs <code>color × (crisp + exp-falloff glow)</code> with additive blend. Single draw call replaces the 2-pass canvas-2D stroke loop</li>
<li><strong>Dot shader (WGSL):</strong> billboarded quad per dot, fragment evaluates halo + mid + hot-core layers from one radial SDF in a single draw call. Replaces the 3-fill-per-dot canvas-2D loop</li>
<li>Hex-color → RGBA float cache so per-frame instance buffer marshalling is one Map lookup per beam/dot</li>
<li>Per-frame instance buffers grow on demand (start 256 lines / 512 dots, double when needed) and are reused across frames</li>
<li><code>device.lost</code> handler clears the <code>webgpu</code> reference so we automatically fall back to canvas 2D if the GPU disappears mid-session</li>
<li>Canvas-2D crisp beam pass deduplicated (an earlier edit had accidentally left a duplicate copy)</li>
</ul>
<h3>OPENING NOTES</h3>
<ul>
<li>To opt in: append <code>?renderer=webgpu</code> to the URL (the PLAY button on this card already does)</li>
<li>Without the flag, the game runs the same canvas-2D path as v010</li>
<li>If your browser doesn't support WebGPU (Firefox < 141, Safari < 26, Chromium without GPU), it logs a warning and silently falls back</li>
</ul>
<h3>SHADER CRISPNESS FIX</h3>
<ul>
<li>First pass used exponential glow falloff (<code>exp(-d * k)</code>) which bled softer than canvas-2D's hard-edged 2-pass stroke. Switched both shaders to <strong>smoothstep with hard edges</strong> matching the canvas-2D layers exactly: line crisp inner (α 1) + mid-glow ring (α 0.26) at width × 2.6; dot halo + mid + white core as three hard-edged disks. Smoothstep starts at the disk/line edge so the interior keeps full intensity (no AA softening of thin lines)</li>
<li><strong>Performance honesty note:</strong> at 60 fps cap on a fast Mac, both renderers easily hit vsync — the WebGPU win is <em>headroom</em>, only visible if scene density grows past what canvas-2D can sustain. If that headroom isn't currently valuable, this version can be ignored and v010 remains the active baseline</li>
</ul>
</div>
</article>
<!-- v010 -->
<article class="ver acc-2">
<div class="ver-header">
<div>
<div class="ver-no">V010</div>
<div class="ver-tag">★ perf + leak fix ★</div>
</div>
<a class="ver-play" href="v010/">▶ PLAY V010</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>there seems to be a few performance issues, especially in Firefox, please look into improving performance, maybe we can use webgpu related stuff. also with firefox I left ot on overnight and it slowed to a crawl. memory leak?</blockquote>
<h3>CHANGES</h3>
<ul>
<li><strong>Memory leak fix:</strong> <code>trimWorld()</code> splices old <code>track[]</code> segments + deletes far-behind <code>sceneryChunks</code> entries every ~1s; <code>lastSegIdx</code> is adjusted so the window math stays valid</li>
<li>Intersection scan capped at 6000 pair checks per frame so heavy scenery can't make it dominate</li>
<li>Vignette gradient cached and rebuilt only on resize (was being recreated every frame)</li>
<li><code>visibilitychange</code> handler gates the entire frame body when the tab is hidden and resets <code>last</code> on return</li>
<li>FPS readout added to the HUD for quick A/B perception checks without DevTools</li>
<li>WebGPU not used — bottlenecks were JS-side state churn + unbounded arrays; canvas 2D handles this scene fine if we don't fight the rasterizer</li>
<li><em>Hotfix:</em> moved <code>_vignette</code> declaration to the top of the IIFE — initial <code>resize()</code> ran before <code>let _vignette</code>, throwing TDZ <code>ReferenceError</code> on load</li>
<li><em>Reverted:</em> the path-batched beam + dot renderers I shipped first turned out to be slower in both browsers (worse in Firefox). Building one big path with hundreds of arc/line subpaths and filling it under <code>"lighter"</code> hits the complex-path slow path; the state-change savings don't pay back. Back to per-element draws for now</li>
<li><strong>The actual win — Firefox profile pinpointed it:</strong> <code>drawScanlines</code> was eating <strong>67% of frame time</strong> (full-screen <code>fillRect</code> with <code>globalCompositeOperation = "overlay"</code>), and the per-frame radial-gradient vignette another 14%. <strong>81% of CPU</strong> on two effects that don't need to be in JS at all. Both moved to <code>body::before</code> / <code>body::after</code> CSS pseudo-elements layered over the canvas — the browser compositor handles them effectively for free. Visual result identical</li>
<li><strong>Follow-up profile (21× fewer total samples) revealed two smaller spikes</strong> — both addressed:
<ul>
<li>Joystick labels: re-issuing <code>ctx.font</code> + 3× <code>fillText</code> every frame triggered Style computation. Pre-baked to three offscreen canvases at <code>updateStickGeom</code>; render now does <code>drawImage</code></li>
<li><code>updateHUD</code>: was setting <code>textContent</code> on 4 spans + running <code>toLocaleString</code> + writing to <code>localStorage</code> every frame the score climbed. Now diffed against last-displayed values — DOM only updates on actual change. Best-score writes debounced via <code>setInterval(saveBestScore, 3000)</code> + <code>pagehide</code> save</li>
<li><code>carTrackInfo()</code> result cached per frame so <code>render()</code> and <code>updateHUD()</code> share one nearest-segment search</li>
</ul>
</li>
<li><strong>Follow-ups (third profile pass):</strong>
<ul>
<li><strong>Beam glow reduced to 2 passes.</strong> Dropped the wide × 5 / α 0.10 outer-glow pass — widest line caps to rasterize, contributed least to bloom. Mid pass slightly bumped (× 2.4 α 0.22 → × 2.6 α 0.26) so the visual stays close. Kept</li>
<li>Joystick labels bake made dpr-aware (was rendering at 1× and being upscaled on retina displays). Kept</li>
<li><em>Tried + reverted:</em> baked the joystick's static rings / ticks / glows into two offscreen canvases (one source-over, one composited "lighter") so each frame would be 2 <code>drawImage</code> calls. <strong>Profile showed it was slower</strong> in Firefox — drawJoystick went 5.2% → 8.4%, and <code>drawImage</code> appeared as a brand-new 7.9% entry. Likely cause: the bake bbox is mostly transparent space but Firefox's "lighter"-blend canvas-to-canvas <code>drawImage</code> still pays per-pixel work over the full ~350×350 region, while the original live draws only paid for actually-stroked pixels. Reverted to the previous live drawJoystick. Cardinal ticks now use a single 4-subpath stroked path (one <code>stroke</code> call instead of four) — small consolation</li>
</ul>
</li>
</ul>
</div>
</article>
<!-- v009 -->
<article class="ver acc-1">
<div class="ver-header">
<div>
<div class="ver-no">V009</div>
<div class="ver-tag">★ different seed ★</div>
</div>
<a class="ver-play" href="v009/">▶ PLAY V009</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>I don't like this track. change the seed</blockquote>
<h3>CHANGES</h3>
<ul>
<li><code>worldSeed</code> 1 → 7341 in both the initial assignment and <code>commitReset()</code></li>
</ul>
</div>
</article>
<!-- v008 -->
<article class="ver acc-2">
<div class="ver-header">
<div>
<div class="ver-no">V008</div>
<div class="ver-tag">★ deterministic world ★</div>
</div>
<a class="ver-play" href="v008/">▶ PLAY V008</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>just do the first thing</blockquote>
<h3>CHANGES</h3>
<ul>
<li>World seed pinned to <code>1</code> instead of being randomized on load + on reset — same track every run</li>
<li>Same fixed seed used after a crash so the world is identical post-reset</li>
</ul>
</div>
</article>
<!-- v007 -->
<article class="ver acc-3">
<div class="ver-header">
<div>
<div class="ver-no">V007</div>
<div class="ver-tag">★ anti-magnetic edges ★</div>
</div>
<a class="ver-play" href="v007/">▶ PLAY V007</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok the edges of the track are slightly repellent, almost antimagnetic but if you go too fast this won't be enought to stop you going out, the closer to the edges the more you feel it though</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Quadratic edge-repulsion velocity field — <code>proximity²·EDGE_REPEL_MAX</code> (620 u/s) added laterally toward the centerline</li>
<li>Repel zone <code>min(75, hw·0.6)</code> so narrow tracks shrink the field with them</li>
<li>Slow drift is corrected; fast perpendicular runs (lateral component up to 1600 u/s) punch through</li>
<li>Visualisation: nearest edge thickens with proximity + pulsing inward "field ticks" on every nearby segment</li>
<li><code>car.edgeProximity</code> + <code>car.edgeSide</code> exposed for the renderer</li>
</ul>
</div>
</article>
<!-- v006 -->
<article class="ver acc-4">
<div class="ver-header">
<div>
<div class="ver-no">V006</div>
<div class="ver-tag">★ slow-mo wipeout ★</div>
</div>
<a class="ver-play" href="v006/">▶ PLAY V006</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok when we crash we should see a spectacular explosion in slow motion</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Crash sequence runs in slow-mo for 2.4 real seconds via a global <code>timeScale</code> (curve <code>0.10 + t^2.6 · 0.90</code>)</li>
<li>Car shatters into ~17 rotating line-shard debris pieces flying outward, each with phosphor-dot endpoints</li>
<li>90 radial sparks (red / yellow / orange / white) with halos + hot cores</li>
<li>5 staggered shockwave rings, each with halo + mid-glow + crisp ring passes</li>
<li>Initial radial bloom (white→amber→red), then darkening vignette while debris flies</li>
<li>WIPEOUT title + frozen final score in Press Start 2P, mid-screen</li>
<li>HUD score hidden during the crash so it doesn't fight the in-canvas readout</li>
<li><code>commitReset()</code> regenerates the world after the spectacle ends</li>
</ul>
</div>
</article>
<!-- v005 -->
<article class="ver acc-5">
<div class="ver-header">
<div>
<div class="ver-no">V005</div>
<div class="ver-tag">★ score + plateau widths ★</div>
</div>
<a class="ver-play" href="v005/">▶ PLAY V005</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok vary the size of the track more in dimension although we can have lon stretches the same size, include the score in a font reminiscent of asteroids, top centre larger than the distance font, the score should be speed multiplied by distance, use commas if the score gets large.</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Width range expanded to 38–230; double-smoothstep on a slow noise (~5500u wavelength) gives long flat plateaus that snap between bands</li>
<li>Spawn area clamped to ≥55% of max width so you never start in a pinch</li>
<li>Press Start 2P (Google Fonts) loaded for the score readout</li>
<li>Score = <code>floor(car.speed × distance)</code>, formatted via <code>toLocaleString("en-US")</code> for commas</li>
<li>SCORE element top-center, <code>clamp(26px, 4.4vw, 56px)</code> with cyan triple-glow</li>
<li><code>BEST</code> now tracks max score (new <code>infiniracer.bestScore</code> localStorage key) and is also comma-formatted</li>
</ul>
</div>
</article>
<!-- v004 -->
<article class="ver acc-1">
<div class="ver-header">
<div>
<div class="ver-no">V004</div>
<div class="ver-tag">★ joystick + new keys ★</div>
</div>
<a class="ver-play" href="v004/">▶ PLAY V004</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>in the bottom fith of the screen show a joystick I can use to control the ship with the mouse, also change the lettered controls to q up a brake o left and p right</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Vector-styled virtual joystick in the bottom fifth — drag with mouse or touch, knob springs back on release</li>
<li>Outer halo ring + cardinal phosphor ticks + amber stem + hot core knob; <code>THROTTLE / BRAKE / ◀ STEER ▶</code> labels</li>
<li>Keyboard remap: throttle <code>Q</code>/<code>↑</code>, brake <code>A</code>/<code>↓</code>, left <code>O</code>/<code>←</code>, right <code>P</code>/<code>→</code></li>
<li>Unified analog input pipeline (<code>readInput()</code>) — keyboard ORed with stick magnitude per axis</li>
<li>Thrust flame intensity now scales with analog throttle</li>
<li>Cursor changed back from <code>none</code> to <code>crosshair</code>; <code>touch-action: none</code> to stop mobile fighting the stick</li>
<li>Start overlay updated with the new controls + "OR DRAG THE JOYSTICK"</li>
</ul>
</div>
</article>
<!-- v003 -->
<article class="ver acc-2">
<div class="ver-header">
<div>
<div class="ver-no">V003</div>
<div class="ver-tag">★ much faster + variable width ★</div>
</div>
<a class="ver-play" href="v003/">▶ PLAY V003</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok allow the player to go much much faster and alter the width of the track randomly. when the player goes off the track reset the distance</blockquote>
<h3>CHANGES</h3>
<ul>
<li><code>MAX_SPEED</code> 620 → 1600, <code>ACCEL</code> 140 → 280, <code>BRAKE_DECEL</code> 320 → 520</li>
<li>Per-segment <code>hw</code> (50–160) driven by two slow noise octaves; spawn clamped wide</li>
<li>Off-track now triggers a full run reset — world seed re-randomized, track + scenery cleared, car teleported home</li>
<li>Brief red/white WIPEOUT flash with text on reset; best distance preserved</li>
<li>Distance markers + scenery offsets switched from the old constant to per-segment <code>seg.hw</code></li>
</ul>
</div>
</article>
<!-- v002 -->
<article class="ver acc-3">
<div class="ver-header">
<div>
<div class="ver-no">V002</div>
<div class="ver-tag">★ scaling car + faster ★</div>
</div>
<a class="ver-play" href="v002/">▶ PLAY V002</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok cool, the vehicle should also alter size when zooming out and make it possible to go faster</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Car geometry scaled by world zoom in <code>p(x, y)</code> so the ship shrinks as the camera pulls back; line widths stay constant (vector-tube convention)</li>
<li><code>MAX_SPEED</code> 360 → 620, <code>ACCEL</code> 95 → 140, <code>BRAKE_DECEL</code> 220 → 320</li>
<li>Off-road drag rebalanced to <code>BRAKE_DECEL · 0.55</code> so it still bites at the new top speed</li>
</ul>
</div>
</article>
<!-- v001 -->
<article class="ver acc-4">
<div class="ver-header">
<div>
<div class="ver-no">V001</div>
<div class="ver-tag">★ genesis ★</div>
</div>
<a class="ver-play" href="v001/">▶ PLAY V001</a>
</div>
<div class="step">
<h3>PROMPT</h3>
<blockquote>ok in v001 dir create a game using only js html and css, the game is infini racer where you control a vehicle through a landscape that is purposefully minimal, in the vector style of the original asteroid game from the 70s/80s. where lines on landscape object or vehicle intersect there is a more brilliant point or dot. This is a top down racer where the vehicle appears static but the course moves and zooms out the faster you go. You can also look at other vector/retro games for inspiration.</blockquote>
<h3>CHANGES</h3>
<ul>
<li>Vector top-down racer: car drawn at screen-center pointing up; the world rotates + translates around it</li>
<li>Procedural infinite track from two octaves of smooth noise; deterministic chunked scenery (3–6-sided wireframes)</li>
<li>3-pass beam rendering — wide soft glow → medium glow → crisp line — for a phosphor-tube feel</li>
<li>Phosphor endpoint dots at every line endpoint plus extra-bright cross-group line-intersection dots</li>
<li>Camera zoom-out as <code>1.05 / (1 + speed_ratio · 1.35)</code></li>
<li>CRT scanline overlay + radial vignette</li>
<li>Off-road drag, distance + speed HUD, best distance in <code>localStorage</code></li>
</ul>
</div>
</article>
</section>
</section>
<footer>
<div>★ INFINI RACER ★ vector-tube racing in the spirit of 1979 ★</div>
<div>made with HTML · CSS · JS · nothing else</div>
<div class="neon">▲ ◆ ▼ ✦ ◇ ◆ ▼ ▲ ✦ ◆ ▼ ▲</div>
</footer>
</main>
<script src="home.js" defer></script>
</body>
</html>