solanapolis 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/README.md +518 -0
  2. package/bin/solanapolis.js +197 -0
  3. package/convex/_generated/api.d.ts +175 -0
  4. package/convex/_generated/api.js +23 -0
  5. package/convex/_generated/dataModel.d.ts +60 -0
  6. package/convex/_generated/server.d.ts +143 -0
  7. package/convex/_generated/server.js +93 -0
  8. package/convex/agent/conversation.ts +352 -0
  9. package/convex/agent/embeddingsCache.ts +110 -0
  10. package/convex/agent/memory.ts +450 -0
  11. package/convex/agent/schema.ts +53 -0
  12. package/convex/aiChat.ts +54 -0
  13. package/convex/aiTown/agent.ts +382 -0
  14. package/convex/aiTown/agentDescription.ts +27 -0
  15. package/convex/aiTown/agentInputs.ts +155 -0
  16. package/convex/aiTown/agentOperations.ts +178 -0
  17. package/convex/aiTown/conversation.ts +395 -0
  18. package/convex/aiTown/conversationMembership.ts +38 -0
  19. package/convex/aiTown/game.ts +371 -0
  20. package/convex/aiTown/ids.ts +32 -0
  21. package/convex/aiTown/inputHandler.ts +9 -0
  22. package/convex/aiTown/inputs.ts +25 -0
  23. package/convex/aiTown/insertInput.ts +20 -0
  24. package/convex/aiTown/location.ts +32 -0
  25. package/convex/aiTown/main.ts +154 -0
  26. package/convex/aiTown/movement.ts +189 -0
  27. package/convex/aiTown/player.ts +310 -0
  28. package/convex/aiTown/playerDescription.ts +35 -0
  29. package/convex/aiTown/schema.ts +79 -0
  30. package/convex/aiTown/world.ts +65 -0
  31. package/convex/aiTown/worldMap.ts +74 -0
  32. package/convex/chat.ts +79 -0
  33. package/convex/constants.ts +78 -0
  34. package/convex/convex.config.ts +6 -0
  35. package/convex/crons.ts +89 -0
  36. package/convex/engine/abstractGame.ts +199 -0
  37. package/convex/engine/historicalObject.ts +355 -0
  38. package/convex/engine/schema.ts +56 -0
  39. package/convex/http.ts +36 -0
  40. package/convex/init.ts +110 -0
  41. package/convex/messages.ts +53 -0
  42. package/convex/npcCarAgents.ts +415 -0
  43. package/convex/schema.ts +61 -0
  44. package/convex/streaming.ts +23 -0
  45. package/convex/testing.ts +202 -0
  46. package/convex/tsconfig.json +18 -0
  47. package/convex/util/FastIntegerCompression.ts +221 -0
  48. package/convex/util/assertNever.ts +4 -0
  49. package/convex/util/asyncMap.ts +20 -0
  50. package/convex/util/compression.ts +71 -0
  51. package/convex/util/geometry.ts +132 -0
  52. package/convex/util/isSimpleObject.ts +11 -0
  53. package/convex/util/llm.ts +724 -0
  54. package/convex/util/minheap.ts +38 -0
  55. package/convex/util/object.ts +22 -0
  56. package/convex/util/sleep.ts +3 -0
  57. package/convex/util/types.ts +33 -0
  58. package/convex/util/xxhash.ts +228 -0
  59. package/convex/world.ts +257 -0
  60. package/data/animations/campfire.json +45 -0
  61. package/data/animations/gentlesparkle.json +37 -0
  62. package/data/animations/gentlesplash.json +61 -0
  63. package/data/animations/gentlewaterfall.json +61 -0
  64. package/data/animations/windmill.json +78 -0
  65. package/data/characters.ts +121 -0
  66. package/data/convertMap.js +74 -0
  67. package/data/gentle.js +330 -0
  68. package/data/spritesheets/f1.ts +75 -0
  69. package/data/spritesheets/f2.ts +75 -0
  70. package/data/spritesheets/f3.ts +75 -0
  71. package/data/spritesheets/f4.ts +75 -0
  72. package/data/spritesheets/f5.ts +75 -0
  73. package/data/spritesheets/f6.ts +75 -0
  74. package/data/spritesheets/f7.ts +75 -0
  75. package/data/spritesheets/f8.ts +75 -0
  76. package/data/spritesheets/p1.ts +59 -0
  77. package/data/spritesheets/p2.ts +59 -0
  78. package/data/spritesheets/p3.ts +59 -0
  79. package/data/spritesheets/player.ts +59 -0
  80. package/data/spritesheets/types.ts +26 -0
  81. package/eslint.config.mjs +37 -0
  82. package/next.config.ts +7 -0
  83. package/package.json +85 -0
  84. package/postcss.config.mjs +7 -0
  85. package/public/file.svg +1 -0
  86. package/public/globe.svg +1 -0
  87. package/public/helius-icon.svg +84 -0
  88. package/public/helius-logo.svg +85 -0
  89. package/public/next.svg +1 -0
  90. package/public/plane.glb +0 -0
  91. package/public/vercel.svg +1 -0
  92. package/public/window.svg +1 -0
  93. package/scripts/clear-city.ts +74 -0
  94. package/scripts/seed-wallets.ts +185 -0
  95. package/scripts/setup-webhook.ts +73 -0
  96. package/src/app/api/auth/callback/route.ts +6 -0
  97. package/src/app/api/auth/link-wallet/route.ts +6 -0
  98. package/src/app/api/auth/phantom/route.ts +6 -0
  99. package/src/app/api/broadcast-position/route.ts +59 -0
  100. package/src/app/api/leaderboard/route.ts +85 -0
  101. package/src/app/api/network-stats/route.ts +86 -0
  102. package/src/app/api/parcel-reward/route.ts +181 -0
  103. package/src/app/api/queue-status/route.ts +30 -0
  104. package/src/app/api/snapshots/route.ts +37 -0
  105. package/src/app/api/transactions/enhanced/route.ts +57 -0
  106. package/src/app/api/treasury/route.ts +83 -0
  107. package/src/app/api/wallet/[address]/balances/route.ts +124 -0
  108. package/src/app/api/wallet/[address]/identity/route.ts +32 -0
  109. package/src/app/api/wallet/[address]/route.ts +216 -0
  110. package/src/app/api/wallet/[address]/traded-tokens/route.ts +41 -0
  111. package/src/app/api/wallets/route.ts +68 -0
  112. package/src/app/api/webhooks/helius/route.ts +76 -0
  113. package/src/app/auth/callback/page.tsx +29 -0
  114. package/src/app/favicon.ico +0 -0
  115. package/src/app/globals.css +39 -0
  116. package/src/app/layout.tsx +43 -0
  117. package/src/app/page.tsx +16 -0
  118. package/src/components/AITownNPCs.tsx +206 -0
  119. package/src/components/ActivityFeed.tsx +189 -0
  120. package/src/components/AuthPanel.tsx +163 -0
  121. package/src/components/BeachScene.tsx +280 -0
  122. package/src/components/Building.tsx +138 -0
  123. package/src/components/CesiumFlight.tsx +1768 -0
  124. package/src/components/CesiumGlobe.tsx +616 -0
  125. package/src/components/CitizenCard.tsx +442 -0
  126. package/src/components/CitizenCardModal.tsx +153 -0
  127. package/src/components/CityGrid.tsx +313 -0
  128. package/src/components/CityLandmarks.tsx +427 -0
  129. package/src/components/CityScene.tsx +1289 -0
  130. package/src/components/CitySlotsBadge.tsx +68 -0
  131. package/src/components/CockpitHUD.tsx +460 -0
  132. package/src/components/ConvexWrapper.tsx +19 -0
  133. package/src/components/DubaiDistrict.tsx +630 -0
  134. package/src/components/FlightMiniMap.tsx +133 -0
  135. package/src/components/GameChat.tsx +383 -0
  136. package/src/components/GameHUD.tsx +393 -0
  137. package/src/components/Ground.tsx +14 -0
  138. package/src/components/HowItWorksModal.tsx +251 -0
  139. package/src/components/IngestionBanner.tsx +123 -0
  140. package/src/components/InstancedBuildings.tsx +316 -0
  141. package/src/components/InstancedCars.tsx +504 -0
  142. package/src/components/InstancedCityPlanes.tsx +259 -0
  143. package/src/components/InstancedHouses.tsx +246 -0
  144. package/src/components/InstancedLampPosts.tsx +201 -0
  145. package/src/components/InstancedResidentCars.tsx +357 -0
  146. package/src/components/InstancedRoadDashes.tsx +42 -0
  147. package/src/components/InstancedSkyscrapers.tsx +434 -0
  148. package/src/components/InstancedTrees.tsx +67 -0
  149. package/src/components/LeaderboardPanel.tsx +136 -0
  150. package/src/components/MultiplayerPlanes.tsx +128 -0
  151. package/src/components/NetworkStats.tsx +83 -0
  152. package/src/components/NewBuildingSpotlight.tsx +93 -0
  153. package/src/components/ParcelChallengeBanner.tsx +242 -0
  154. package/src/components/ParcelReward.tsx +191 -0
  155. package/src/components/Park.tsx +42 -0
  156. package/src/components/PhantomWrapper.tsx +22 -0
  157. package/src/components/PixelStreamViewer.tsx +335 -0
  158. package/src/components/PlaneMode.tsx +190 -0
  159. package/src/components/PlayerCar.tsx +211 -0
  160. package/src/components/PlayerPlane.tsx +255 -0
  161. package/src/components/ProjectileRenderer.tsx +249 -0
  162. package/src/components/QueueStatusBanner.tsx +86 -0
  163. package/src/components/RealPlayerTags.tsx +82 -0
  164. package/src/components/SceneLighting.tsx +382 -0
  165. package/src/components/SelectionBeam.tsx +59 -0
  166. package/src/components/SwapPanel.tsx +104 -0
  167. package/src/components/SwapParticles.tsx +237 -0
  168. package/src/components/TreasureGate.tsx +505 -0
  169. package/src/components/WalletPanel.tsx +421 -0
  170. package/src/components/WalletSearch.tsx +244 -0
  171. package/src/components/WelcomeOverlay.tsx +135 -0
  172. package/src/components/WindowTooltip.tsx +498 -0
  173. package/src/context/AuthContext.tsx +230 -0
  174. package/src/lib/bot-detection.ts +125 -0
  175. package/src/lib/building-math.ts +136 -0
  176. package/src/lib/building-shader.ts +253 -0
  177. package/src/lib/car-paths.ts +244 -0
  178. package/src/lib/car-system.ts +182 -0
  179. package/src/lib/city-constants.ts +29 -0
  180. package/src/lib/city-slots.ts +35 -0
  181. package/src/lib/city-zoning.ts +64 -0
  182. package/src/lib/collision-map.ts +147 -0
  183. package/src/lib/day-night.ts +252 -0
  184. package/src/lib/export-card.ts +28 -0
  185. package/src/lib/helius-webhook.ts +90 -0
  186. package/src/lib/helius.ts +74 -0
  187. package/src/lib/house-shader.ts +119 -0
  188. package/src/lib/mock-data.ts +56 -0
  189. package/src/lib/multiplayer-manager.ts +329 -0
  190. package/src/lib/plane-physics.ts +66 -0
  191. package/src/lib/player-car.ts +147 -0
  192. package/src/lib/player-plane.ts +200 -0
  193. package/src/lib/projectile-system.ts +272 -0
  194. package/src/lib/skyscraper-types.ts +52 -0
  195. package/src/lib/sound-engine.ts +464 -0
  196. package/src/lib/supabase-admin.ts +9 -0
  197. package/src/lib/supabase.ts +8 -0
  198. package/src/lib/swap-events.ts +70 -0
  199. package/src/middleware.ts +37 -0
  200. package/src/types/phantom.d.ts +16 -0
  201. package/src/types/wallet.ts +20 -0
  202. package/tsconfig.json +34 -0
@@ -0,0 +1,464 @@
1
+ /**
2
+ * Procedural sound engine for Solanapolis.
3
+ *
4
+ * All sounds are synthesized in real-time using the Web Audio API.
5
+ * No external audio files required.
6
+ */
7
+
8
+ // ─── Singleton AudioContext ──────────────────────────────────
9
+
10
+ let _ctx: AudioContext | null = null;
11
+ let _master: GainNode | null = null;
12
+ let _initialized = false;
13
+
14
+ function getCtx(): AudioContext {
15
+ if (!_ctx) {
16
+ _ctx = new AudioContext();
17
+ _master = _ctx.createGain();
18
+ _master.gain.value = 0.3; // global volume
19
+ _master.connect(_ctx.destination);
20
+ }
21
+ return _ctx;
22
+ }
23
+
24
+ function getMaster(): GainNode {
25
+ getCtx();
26
+ return _master!;
27
+ }
28
+
29
+ /**
30
+ * Must be called from a user gesture (click/keydown) to unlock AudioContext.
31
+ */
32
+ export function initAudio(): void {
33
+ if (_initialized) return;
34
+ const ctx = getCtx();
35
+ if (ctx.state === "suspended") {
36
+ ctx.resume();
37
+ }
38
+ _initialized = true;
39
+ }
40
+
41
+ // ─── Utility: White Noise Buffer ─────────────────────────────
42
+
43
+ function createNoiseBuffer(duration = 2): AudioBuffer {
44
+ const ctx = getCtx();
45
+ const sr = ctx.sampleRate;
46
+ const len = sr * duration;
47
+ const buf = ctx.createBuffer(1, len, sr);
48
+ const data = buf.getChannelData(0);
49
+ for (let i = 0; i < len; i++) {
50
+ data[i] = Math.random() * 2 - 1;
51
+ }
52
+ return buf;
53
+ }
54
+
55
+ let _noiseBuf: AudioBuffer | null = null;
56
+ function getNoiseBuffer(): AudioBuffer {
57
+ if (!_noiseBuf) _noiseBuf = createNoiseBuffer();
58
+ return _noiseBuf;
59
+ }
60
+
61
+ // ─── Car Engine ──────────────────────────────────────────────
62
+ // Low-frequency oscillators + distortion for engine rumble
63
+
64
+ export class CarEngine {
65
+ private osc1: OscillatorNode | null = null;
66
+ private osc2: OscillatorNode | null = null;
67
+ private gain: GainNode | null = null;
68
+ private filter: BiquadFilterNode | null = null;
69
+ private running = false;
70
+
71
+ start(): void {
72
+ if (this.running) return;
73
+ const ctx = getCtx();
74
+
75
+ this.gain = ctx.createGain();
76
+ this.gain.gain.value = 0.08;
77
+
78
+ this.filter = ctx.createBiquadFilter();
79
+ this.filter.type = "lowpass";
80
+ this.filter.frequency.value = 200;
81
+ this.filter.Q.value = 3;
82
+
83
+ // Primary engine tone
84
+ this.osc1 = ctx.createOscillator();
85
+ this.osc1.type = "sawtooth";
86
+ this.osc1.frequency.value = 55; // A1 — idle rumble
87
+
88
+ // Harmonic
89
+ this.osc2 = ctx.createOscillator();
90
+ this.osc2.type = "square";
91
+ this.osc2.frequency.value = 110;
92
+
93
+ const osc2Gain = ctx.createGain();
94
+ osc2Gain.gain.value = 0.3;
95
+
96
+ this.osc1.connect(this.filter);
97
+ this.osc2.connect(osc2Gain).connect(this.filter);
98
+ this.filter.connect(this.gain).connect(getMaster());
99
+
100
+ this.osc1.start();
101
+ this.osc2.start();
102
+ this.running = true;
103
+ }
104
+
105
+ /** Set throttle 0–1 to modulate pitch and volume */
106
+ setThrottle(t: number): void {
107
+ if (!this.running) return;
108
+ const throttle = Math.max(0, Math.min(1, t));
109
+ // Pitch: idle 55Hz → max 180Hz
110
+ this.osc1!.frequency.value = 55 + throttle * 125;
111
+ this.osc2!.frequency.value = 110 + throttle * 250;
112
+ // Volume: idle 0.05 → max 0.15
113
+ this.gain!.gain.value = 0.05 + throttle * 0.1;
114
+ // Filter opens with throttle
115
+ this.filter!.frequency.value = 200 + throttle * 600;
116
+ }
117
+
118
+ stop(): void {
119
+ if (!this.running) return;
120
+ try {
121
+ this.osc1?.stop();
122
+ this.osc2?.stop();
123
+ } catch { /* already stopped */ }
124
+ this.gain?.disconnect();
125
+ this.running = false;
126
+ }
127
+ }
128
+
129
+ // ─── Plane Engine ────────────────────────────────────────────
130
+ // Higher pitch buzz + filtered noise for propeller wind
131
+
132
+ export class PlaneEngine {
133
+ private osc: OscillatorNode | null = null;
134
+ private noiseSrc: AudioBufferSourceNode | null = null;
135
+ private gain: GainNode | null = null;
136
+ private noiseGain: GainNode | null = null;
137
+ private filter: BiquadFilterNode | null = null;
138
+ private running = false;
139
+
140
+ start(): void {
141
+ if (this.running) return;
142
+ const ctx = getCtx();
143
+
144
+ this.gain = ctx.createGain();
145
+ this.gain.gain.value = 0.06;
146
+
147
+ this.filter = ctx.createBiquadFilter();
148
+ this.filter.type = "bandpass";
149
+ this.filter.frequency.value = 400;
150
+ this.filter.Q.value = 1.5;
151
+
152
+ // Propeller tone
153
+ this.osc = ctx.createOscillator();
154
+ this.osc.type = "sawtooth";
155
+ this.osc.frequency.value = 120;
156
+
157
+ // Wind noise
158
+ this.noiseSrc = ctx.createBufferSource();
159
+ this.noiseSrc.buffer = getNoiseBuffer();
160
+ this.noiseSrc.loop = true;
161
+
162
+ this.noiseGain = ctx.createGain();
163
+ this.noiseGain.gain.value = 0.03;
164
+
165
+ const noiseFilter = ctx.createBiquadFilter();
166
+ noiseFilter.type = "bandpass";
167
+ noiseFilter.frequency.value = 800;
168
+ noiseFilter.Q.value = 0.5;
169
+
170
+ this.osc.connect(this.filter).connect(this.gain).connect(getMaster());
171
+ this.noiseSrc.connect(noiseFilter).connect(this.noiseGain).connect(getMaster());
172
+
173
+ this.osc.start();
174
+ this.noiseSrc.start();
175
+ this.running = true;
176
+ }
177
+
178
+ /** Set throttle 0–1 */
179
+ setThrottle(t: number): void {
180
+ if (!this.running) return;
181
+ const throttle = Math.max(0, Math.min(1, t));
182
+ this.osc!.frequency.value = 120 + throttle * 200;
183
+ this.gain!.gain.value = 0.04 + throttle * 0.08;
184
+ this.noiseGain!.gain.value = 0.02 + throttle * 0.06;
185
+ this.filter!.frequency.value = 400 + throttle * 800;
186
+ }
187
+
188
+ stop(): void {
189
+ if (!this.running) return;
190
+ try {
191
+ this.osc?.stop();
192
+ this.noiseSrc?.stop();
193
+ } catch { /* already stopped */ }
194
+ this.gain?.disconnect();
195
+ this.noiseGain?.disconnect();
196
+ this.running = false;
197
+ }
198
+ }
199
+
200
+ // ─── Gunshot / Projectile Fire ───────────────────────────────
201
+ // Short burst of noise + pitched-down oscillator
202
+
203
+ export function playGunshot(): void {
204
+ if (!_initialized) return;
205
+ const ctx = getCtx();
206
+ const now = ctx.currentTime;
207
+
208
+ // Noise burst
209
+ const noise = ctx.createBufferSource();
210
+ noise.buffer = getNoiseBuffer();
211
+ const noiseGain = ctx.createGain();
212
+ noiseGain.gain.setValueAtTime(0.12, now);
213
+ noiseGain.gain.exponentialRampToValueAtTime(0.001, now + 0.08);
214
+
215
+ const noiseFilter = ctx.createBiquadFilter();
216
+ noiseFilter.type = "highpass";
217
+ noiseFilter.frequency.value = 1500;
218
+
219
+ noise.connect(noiseFilter).connect(noiseGain).connect(getMaster());
220
+ noise.start(now);
221
+ noise.stop(now + 0.1);
222
+
223
+ // Thud tone
224
+ const osc = ctx.createOscillator();
225
+ osc.type = "sine";
226
+ osc.frequency.setValueAtTime(400, now);
227
+ osc.frequency.exponentialRampToValueAtTime(60, now + 0.06);
228
+
229
+ const oscGain = ctx.createGain();
230
+ oscGain.gain.setValueAtTime(0.15, now);
231
+ oscGain.gain.exponentialRampToValueAtTime(0.001, now + 0.1);
232
+
233
+ osc.connect(oscGain).connect(getMaster());
234
+ osc.start(now);
235
+ osc.stop(now + 0.12);
236
+ }
237
+
238
+ // ─── Hit / Explosion ─────────────────────────────────────────
239
+
240
+ export function playExplosion(): void {
241
+ if (!_initialized) return;
242
+ const ctx = getCtx();
243
+ const now = ctx.currentTime;
244
+
245
+ // Explosion rumble — low freq noise
246
+ const noise = ctx.createBufferSource();
247
+ noise.buffer = getNoiseBuffer();
248
+ const noiseGain = ctx.createGain();
249
+ noiseGain.gain.setValueAtTime(0.2, now);
250
+ noiseGain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
251
+
252
+ const filter = ctx.createBiquadFilter();
253
+ filter.type = "lowpass";
254
+ filter.frequency.setValueAtTime(800, now);
255
+ filter.frequency.exponentialRampToValueAtTime(100, now + 0.3);
256
+
257
+ noise.connect(filter).connect(noiseGain).connect(getMaster());
258
+ noise.start(now);
259
+ noise.stop(now + 0.5);
260
+
261
+ // Impact thud
262
+ const osc = ctx.createOscillator();
263
+ osc.type = "sine";
264
+ osc.frequency.setValueAtTime(200, now);
265
+ osc.frequency.exponentialRampToValueAtTime(30, now + 0.15);
266
+
267
+ const oscGain = ctx.createGain();
268
+ oscGain.gain.setValueAtTime(0.2, now);
269
+ oscGain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
270
+
271
+ osc.connect(oscGain).connect(getMaster());
272
+ osc.start(now);
273
+ osc.stop(now + 0.25);
274
+ }
275
+
276
+ // ─── Ocean Ambient ───────────────────────────────────────────
277
+ // Looping filtered noise that fades in/out
278
+
279
+ export class OceanAmbient {
280
+ private src: AudioBufferSourceNode | null = null;
281
+ private gain: GainNode | null = null;
282
+ private running = false;
283
+
284
+ start(): void {
285
+ if (this.running) return;
286
+ const ctx = getCtx();
287
+
288
+ this.src = ctx.createBufferSource();
289
+ this.src.buffer = getNoiseBuffer();
290
+ this.src.loop = true;
291
+
292
+ const filter = ctx.createBiquadFilter();
293
+ filter.type = "lowpass";
294
+ filter.frequency.value = 600;
295
+ filter.Q.value = 0.5;
296
+
297
+ // Slow LFO to modulate gain (wave surging)
298
+ const lfo = ctx.createOscillator();
299
+ lfo.type = "sine";
300
+ lfo.frequency.value = 0.15;
301
+ const lfoGain = ctx.createGain();
302
+ lfoGain.gain.value = 0.01;
303
+ lfo.connect(lfoGain);
304
+
305
+ this.gain = ctx.createGain();
306
+ this.gain.gain.value = 0.04;
307
+ lfoGain.connect(this.gain.gain);
308
+
309
+ this.src.connect(filter).connect(this.gain).connect(getMaster());
310
+ this.src.start();
311
+ lfo.start();
312
+ this.running = true;
313
+ }
314
+
315
+ stop(): void {
316
+ if (!this.running) return;
317
+ try { this.src?.stop(); } catch { /* */ }
318
+ this.gain?.disconnect();
319
+ this.running = false;
320
+ }
321
+ }
322
+
323
+ // ─── Seagull Call ─────────────────────────────────────────────
324
+ // Random high-pitched oscillator squeaks
325
+
326
+ let _lastSeagullTime = 0;
327
+
328
+ export function maybePlaySeagull(): void {
329
+ if (!_initialized) return;
330
+ const now = performance.now();
331
+ // At most one call every 3–8 seconds
332
+ if (now - _lastSeagullTime < 3000 + Math.random() * 5000) return;
333
+ _lastSeagullTime = now;
334
+
335
+ const ctx = getCtx();
336
+ const t = ctx.currentTime;
337
+
338
+ const duration = 0.15 + Math.random() * 0.2;
339
+ const pitch = 1800 + Math.random() * 1200;
340
+
341
+ const osc = ctx.createOscillator();
342
+ osc.type = "sine";
343
+ osc.frequency.setValueAtTime(pitch, t);
344
+ osc.frequency.linearRampToValueAtTime(pitch * 0.7, t + duration);
345
+
346
+ const gain = ctx.createGain();
347
+ gain.gain.setValueAtTime(0, t);
348
+ gain.gain.linearRampToValueAtTime(0.03, t + 0.02);
349
+ gain.gain.linearRampToValueAtTime(0, t + duration);
350
+
351
+ osc.connect(gain).connect(getMaster());
352
+ osc.start(t);
353
+ osc.stop(t + duration + 0.01);
354
+
355
+ // Second chirp after a short pause
356
+ if (Math.random() > 0.4) {
357
+ const t2 = t + duration + 0.05;
358
+ const dur2 = 0.1 + Math.random() * 0.15;
359
+ const osc2 = ctx.createOscillator();
360
+ osc2.type = "sine";
361
+ osc2.frequency.setValueAtTime(pitch * 1.1, t2);
362
+ osc2.frequency.linearRampToValueAtTime(pitch * 0.6, t2 + dur2);
363
+
364
+ const g2 = ctx.createGain();
365
+ g2.gain.setValueAtTime(0, t2);
366
+ g2.gain.linearRampToValueAtTime(0.025, t2 + 0.015);
367
+ g2.gain.linearRampToValueAtTime(0, t2 + dur2);
368
+
369
+ osc2.connect(g2).connect(getMaster());
370
+ osc2.start(t2);
371
+ osc2.stop(t2 + dur2 + 0.01);
372
+ }
373
+ }
374
+
375
+ // ─── Boat Horn ───────────────────────────────────────────────
376
+ // Deep foghorn-like tone
377
+
378
+ export function playBoatHorn(): void {
379
+ if (!_initialized) return;
380
+ const ctx = getCtx();
381
+ const now = ctx.currentTime;
382
+
383
+ const osc = ctx.createOscillator();
384
+ osc.type = "sawtooth";
385
+ osc.frequency.value = 110;
386
+
387
+ const filter = ctx.createBiquadFilter();
388
+ filter.type = "lowpass";
389
+ filter.frequency.value = 300;
390
+ filter.Q.value = 2;
391
+
392
+ const gain = ctx.createGain();
393
+ gain.gain.setValueAtTime(0, now);
394
+ gain.gain.linearRampToValueAtTime(0.08, now + 0.2);
395
+ gain.gain.setValueAtTime(0.08, now + 0.8);
396
+ gain.gain.linearRampToValueAtTime(0, now + 1.2);
397
+
398
+ osc.connect(filter).connect(gain).connect(getMaster());
399
+ osc.start(now);
400
+ osc.stop(now + 1.3);
401
+ }
402
+
403
+ // ─── UI Click ────────────────────────────────────────────────
404
+
405
+ export function playClick(): void {
406
+ if (!_initialized) return;
407
+ const ctx = getCtx();
408
+ const now = ctx.currentTime;
409
+
410
+ const osc = ctx.createOscillator();
411
+ osc.type = "sine";
412
+ osc.frequency.value = 800;
413
+
414
+ const gain = ctx.createGain();
415
+ gain.gain.setValueAtTime(0.06, now);
416
+ gain.gain.exponentialRampToValueAtTime(0.001, now + 0.04);
417
+
418
+ osc.connect(gain).connect(getMaster());
419
+ osc.start(now);
420
+ osc.stop(now + 0.05);
421
+ }
422
+
423
+ // ─── Score / Hit Confirm ─────────────────────────────────────
424
+
425
+ export function playScoreChime(): void {
426
+ if (!_initialized) return;
427
+ const ctx = getCtx();
428
+ const now = ctx.currentTime;
429
+
430
+ // Rising two-note chime
431
+ const osc1 = ctx.createOscillator();
432
+ osc1.type = "sine";
433
+ osc1.frequency.value = 880; // A5
434
+
435
+ const g1 = ctx.createGain();
436
+ g1.gain.setValueAtTime(0.08, now);
437
+ g1.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
438
+
439
+ osc1.connect(g1).connect(getMaster());
440
+ osc1.start(now);
441
+ osc1.stop(now + 0.2);
442
+
443
+ const osc2 = ctx.createOscillator();
444
+ osc2.type = "sine";
445
+ osc2.frequency.value = 1320; // E6
446
+
447
+ const g2 = ctx.createGain();
448
+ g2.gain.setValueAtTime(0.06, now + 0.08);
449
+ g2.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
450
+
451
+ osc2.connect(g2).connect(getMaster());
452
+ osc2.start(now + 0.08);
453
+ osc2.stop(now + 0.3);
454
+ }
455
+
456
+ // ─── Volume Control ──────────────────────────────────────────
457
+
458
+ export function setMasterVolume(v: number): void {
459
+ getMaster().gain.value = Math.max(0, Math.min(1, v));
460
+ }
461
+
462
+ export function getMasterVolume(): number {
463
+ return getMaster().gain.value;
464
+ }
@@ -0,0 +1,9 @@
1
+ import { createClient } from "@supabase/supabase-js";
2
+
3
+ export function createAdminClient() {
4
+ return createClient(
5
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
6
+ process.env.SUPABASE_SERVICE_ROLE_KEY!,
7
+ { auth: { autoRefreshToken: false, persistSession: false } }
8
+ );
9
+ }
@@ -0,0 +1,8 @@
1
+ import { createBrowserClient } from "@supabase/ssr";
2
+
3
+ export function createClient() {
4
+ return createBrowserClient(
5
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
6
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
7
+ );
8
+ }
@@ -0,0 +1,70 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef } from "react";
4
+ import { PlacedWallet } from "@/types/wallet";
5
+
6
+ export interface SwapEvent {
7
+ walletAddress: string;
8
+ signature: string;
9
+ tokenIn: string | null;
10
+ tokenOut: string | null;
11
+ amountSol: number | null;
12
+ }
13
+
14
+ const MAX_QUEUE = 60;
15
+ const POLL_INTERVAL = 4000; // fetch a batch every 4 seconds
16
+ const BATCH_SIZE = 3; // swaps per batch
17
+
18
+ /**
19
+ * Periodically fetch random existing swap events from the DB
20
+ * to keep cars spawning without needing live webhook data.
21
+ */
22
+ export function useSwapEvents(wallets: PlacedWallet[]): React.MutableRefObject<SwapEvent[]> {
23
+ const queueRef = useRef<SwapEvent[]>([]);
24
+
25
+ useEffect(() => {
26
+ if (wallets.length === 0) return;
27
+
28
+ let cancelled = false;
29
+
30
+ async function fetchRandomSwaps() {
31
+ try {
32
+ const { createClient } = await import("@/lib/supabase");
33
+ const supabase = createClient();
34
+
35
+ // Fetch random existing swap events
36
+ const { data } = await supabase
37
+ .rpc("get_random_swaps", { n: BATCH_SIZE });
38
+
39
+ if (cancelled || !data) return;
40
+
41
+ for (const row of data) {
42
+ const event: SwapEvent = {
43
+ walletAddress: row.wallet_address,
44
+ signature: `${row.signature}-${Date.now()}`, // unique key so car system treats as new
45
+ tokenIn: row.token_in ?? null,
46
+ tokenOut: row.token_out ?? null,
47
+ amountSol: row.amount_sol != null ? Number(row.amount_sol) : null,
48
+ };
49
+ const q = queueRef.current;
50
+ q.push(event);
51
+ if (q.length > MAX_QUEUE) q.splice(0, q.length - MAX_QUEUE);
52
+ }
53
+ } catch {
54
+ // Silently ignore — cars just won't spawn this cycle
55
+ }
56
+ }
57
+
58
+ // Initial fetch after short delay
59
+ const initialTimeout = setTimeout(fetchRandomSwaps, 2000);
60
+ const interval = setInterval(fetchRandomSwaps, POLL_INTERVAL);
61
+
62
+ return () => {
63
+ cancelled = true;
64
+ clearTimeout(initialTimeout);
65
+ clearInterval(interval);
66
+ };
67
+ }, [wallets]);
68
+
69
+ return queueRef;
70
+ }
@@ -0,0 +1,37 @@
1
+ import { createServerClient } from "@supabase/ssr";
2
+ import { NextResponse, type NextRequest } from "next/server";
3
+
4
+ export async function middleware(request: NextRequest) {
5
+ let response = NextResponse.next({ request });
6
+
7
+ const supabase = createServerClient(
8
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
9
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
10
+ {
11
+ cookies: {
12
+ getAll() {
13
+ return request.cookies.getAll();
14
+ },
15
+ setAll(cookiesToSet) {
16
+ cookiesToSet.forEach(({ name, value }) =>
17
+ request.cookies.set(name, value)
18
+ );
19
+ response = NextResponse.next({ request });
20
+ cookiesToSet.forEach(({ name, value, options }) =>
21
+ response.cookies.set(name, value, options)
22
+ );
23
+ },
24
+ },
25
+ }
26
+ );
27
+
28
+ await supabase.auth.getUser();
29
+
30
+ return response;
31
+ }
32
+
33
+ export const config = {
34
+ matcher: [
35
+ "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
36
+ ],
37
+ };
@@ -0,0 +1,16 @@
1
+ interface PhantomSolana {
2
+ isPhantom: boolean;
3
+ connect(): Promise<{ publicKey: { toString(): string; toBytes(): Uint8Array } }>;
4
+ disconnect(): Promise<void>;
5
+ signMessage(
6
+ message: Uint8Array,
7
+ encoding: string
8
+ ): Promise<{ signature: Uint8Array }>;
9
+ on(event: string, callback: () => void): void;
10
+ }
11
+
12
+ interface Window {
13
+ phantom?: {
14
+ solana?: PhantomSolana;
15
+ };
16
+ }
@@ -0,0 +1,20 @@
1
+ export interface WalletBuilding {
2
+ address: string;
3
+ txnCount: number;
4
+ walletAgeDays: number;
5
+ volumeTraded: number; // in SOL
6
+ feesPaid: number; // in SOL
7
+ ingestionStatus?: "queued" | "processing" | "complete" | "failed";
8
+ uniqueTokensSwapped?: number;
9
+ latestBlocktime?: number; // unix seconds
10
+ identityName?: string | null;
11
+ identityType?: string | null;
12
+ identityCategory?: string | null;
13
+ xUsername?: string | null;
14
+ }
15
+
16
+ export interface PlacedWallet extends WalletBuilding {
17
+ blockRow: number;
18
+ blockCol: number;
19
+ localSlot: number;
20
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./src/*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules", "cesium-flight-simulator-main", "persistent-text-streaming-main", "ai-town-main", "convex"]
34
+ }