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.
- package/README.md +518 -0
- package/bin/solanapolis.js +197 -0
- package/convex/_generated/api.d.ts +175 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +60 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/agent/conversation.ts +352 -0
- package/convex/agent/embeddingsCache.ts +110 -0
- package/convex/agent/memory.ts +450 -0
- package/convex/agent/schema.ts +53 -0
- package/convex/aiChat.ts +54 -0
- package/convex/aiTown/agent.ts +382 -0
- package/convex/aiTown/agentDescription.ts +27 -0
- package/convex/aiTown/agentInputs.ts +155 -0
- package/convex/aiTown/agentOperations.ts +178 -0
- package/convex/aiTown/conversation.ts +395 -0
- package/convex/aiTown/conversationMembership.ts +38 -0
- package/convex/aiTown/game.ts +371 -0
- package/convex/aiTown/ids.ts +32 -0
- package/convex/aiTown/inputHandler.ts +9 -0
- package/convex/aiTown/inputs.ts +25 -0
- package/convex/aiTown/insertInput.ts +20 -0
- package/convex/aiTown/location.ts +32 -0
- package/convex/aiTown/main.ts +154 -0
- package/convex/aiTown/movement.ts +189 -0
- package/convex/aiTown/player.ts +310 -0
- package/convex/aiTown/playerDescription.ts +35 -0
- package/convex/aiTown/schema.ts +79 -0
- package/convex/aiTown/world.ts +65 -0
- package/convex/aiTown/worldMap.ts +74 -0
- package/convex/chat.ts +79 -0
- package/convex/constants.ts +78 -0
- package/convex/convex.config.ts +6 -0
- package/convex/crons.ts +89 -0
- package/convex/engine/abstractGame.ts +199 -0
- package/convex/engine/historicalObject.ts +355 -0
- package/convex/engine/schema.ts +56 -0
- package/convex/http.ts +36 -0
- package/convex/init.ts +110 -0
- package/convex/messages.ts +53 -0
- package/convex/npcCarAgents.ts +415 -0
- package/convex/schema.ts +61 -0
- package/convex/streaming.ts +23 -0
- package/convex/testing.ts +202 -0
- package/convex/tsconfig.json +18 -0
- package/convex/util/FastIntegerCompression.ts +221 -0
- package/convex/util/assertNever.ts +4 -0
- package/convex/util/asyncMap.ts +20 -0
- package/convex/util/compression.ts +71 -0
- package/convex/util/geometry.ts +132 -0
- package/convex/util/isSimpleObject.ts +11 -0
- package/convex/util/llm.ts +724 -0
- package/convex/util/minheap.ts +38 -0
- package/convex/util/object.ts +22 -0
- package/convex/util/sleep.ts +3 -0
- package/convex/util/types.ts +33 -0
- package/convex/util/xxhash.ts +228 -0
- package/convex/world.ts +257 -0
- package/data/animations/campfire.json +45 -0
- package/data/animations/gentlesparkle.json +37 -0
- package/data/animations/gentlesplash.json +61 -0
- package/data/animations/gentlewaterfall.json +61 -0
- package/data/animations/windmill.json +78 -0
- package/data/characters.ts +121 -0
- package/data/convertMap.js +74 -0
- package/data/gentle.js +330 -0
- package/data/spritesheets/f1.ts +75 -0
- package/data/spritesheets/f2.ts +75 -0
- package/data/spritesheets/f3.ts +75 -0
- package/data/spritesheets/f4.ts +75 -0
- package/data/spritesheets/f5.ts +75 -0
- package/data/spritesheets/f6.ts +75 -0
- package/data/spritesheets/f7.ts +75 -0
- package/data/spritesheets/f8.ts +75 -0
- package/data/spritesheets/p1.ts +59 -0
- package/data/spritesheets/p2.ts +59 -0
- package/data/spritesheets/p3.ts +59 -0
- package/data/spritesheets/player.ts +59 -0
- package/data/spritesheets/types.ts +26 -0
- package/eslint.config.mjs +37 -0
- package/next.config.ts +7 -0
- package/package.json +85 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/helius-icon.svg +84 -0
- package/public/helius-logo.svg +85 -0
- package/public/next.svg +1 -0
- package/public/plane.glb +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/clear-city.ts +74 -0
- package/scripts/seed-wallets.ts +185 -0
- package/scripts/setup-webhook.ts +73 -0
- package/src/app/api/auth/callback/route.ts +6 -0
- package/src/app/api/auth/link-wallet/route.ts +6 -0
- package/src/app/api/auth/phantom/route.ts +6 -0
- package/src/app/api/broadcast-position/route.ts +59 -0
- package/src/app/api/leaderboard/route.ts +85 -0
- package/src/app/api/network-stats/route.ts +86 -0
- package/src/app/api/parcel-reward/route.ts +181 -0
- package/src/app/api/queue-status/route.ts +30 -0
- package/src/app/api/snapshots/route.ts +37 -0
- package/src/app/api/transactions/enhanced/route.ts +57 -0
- package/src/app/api/treasury/route.ts +83 -0
- package/src/app/api/wallet/[address]/balances/route.ts +124 -0
- package/src/app/api/wallet/[address]/identity/route.ts +32 -0
- package/src/app/api/wallet/[address]/route.ts +216 -0
- package/src/app/api/wallet/[address]/traded-tokens/route.ts +41 -0
- package/src/app/api/wallets/route.ts +68 -0
- package/src/app/api/webhooks/helius/route.ts +76 -0
- package/src/app/auth/callback/page.tsx +29 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +39 -0
- package/src/app/layout.tsx +43 -0
- package/src/app/page.tsx +16 -0
- package/src/components/AITownNPCs.tsx +206 -0
- package/src/components/ActivityFeed.tsx +189 -0
- package/src/components/AuthPanel.tsx +163 -0
- package/src/components/BeachScene.tsx +280 -0
- package/src/components/Building.tsx +138 -0
- package/src/components/CesiumFlight.tsx +1768 -0
- package/src/components/CesiumGlobe.tsx +616 -0
- package/src/components/CitizenCard.tsx +442 -0
- package/src/components/CitizenCardModal.tsx +153 -0
- package/src/components/CityGrid.tsx +313 -0
- package/src/components/CityLandmarks.tsx +427 -0
- package/src/components/CityScene.tsx +1289 -0
- package/src/components/CitySlotsBadge.tsx +68 -0
- package/src/components/CockpitHUD.tsx +460 -0
- package/src/components/ConvexWrapper.tsx +19 -0
- package/src/components/DubaiDistrict.tsx +630 -0
- package/src/components/FlightMiniMap.tsx +133 -0
- package/src/components/GameChat.tsx +383 -0
- package/src/components/GameHUD.tsx +393 -0
- package/src/components/Ground.tsx +14 -0
- package/src/components/HowItWorksModal.tsx +251 -0
- package/src/components/IngestionBanner.tsx +123 -0
- package/src/components/InstancedBuildings.tsx +316 -0
- package/src/components/InstancedCars.tsx +504 -0
- package/src/components/InstancedCityPlanes.tsx +259 -0
- package/src/components/InstancedHouses.tsx +246 -0
- package/src/components/InstancedLampPosts.tsx +201 -0
- package/src/components/InstancedResidentCars.tsx +357 -0
- package/src/components/InstancedRoadDashes.tsx +42 -0
- package/src/components/InstancedSkyscrapers.tsx +434 -0
- package/src/components/InstancedTrees.tsx +67 -0
- package/src/components/LeaderboardPanel.tsx +136 -0
- package/src/components/MultiplayerPlanes.tsx +128 -0
- package/src/components/NetworkStats.tsx +83 -0
- package/src/components/NewBuildingSpotlight.tsx +93 -0
- package/src/components/ParcelChallengeBanner.tsx +242 -0
- package/src/components/ParcelReward.tsx +191 -0
- package/src/components/Park.tsx +42 -0
- package/src/components/PhantomWrapper.tsx +22 -0
- package/src/components/PixelStreamViewer.tsx +335 -0
- package/src/components/PlaneMode.tsx +190 -0
- package/src/components/PlayerCar.tsx +211 -0
- package/src/components/PlayerPlane.tsx +255 -0
- package/src/components/ProjectileRenderer.tsx +249 -0
- package/src/components/QueueStatusBanner.tsx +86 -0
- package/src/components/RealPlayerTags.tsx +82 -0
- package/src/components/SceneLighting.tsx +382 -0
- package/src/components/SelectionBeam.tsx +59 -0
- package/src/components/SwapPanel.tsx +104 -0
- package/src/components/SwapParticles.tsx +237 -0
- package/src/components/TreasureGate.tsx +505 -0
- package/src/components/WalletPanel.tsx +421 -0
- package/src/components/WalletSearch.tsx +244 -0
- package/src/components/WelcomeOverlay.tsx +135 -0
- package/src/components/WindowTooltip.tsx +498 -0
- package/src/context/AuthContext.tsx +230 -0
- package/src/lib/bot-detection.ts +125 -0
- package/src/lib/building-math.ts +136 -0
- package/src/lib/building-shader.ts +253 -0
- package/src/lib/car-paths.ts +244 -0
- package/src/lib/car-system.ts +182 -0
- package/src/lib/city-constants.ts +29 -0
- package/src/lib/city-slots.ts +35 -0
- package/src/lib/city-zoning.ts +64 -0
- package/src/lib/collision-map.ts +147 -0
- package/src/lib/day-night.ts +252 -0
- package/src/lib/export-card.ts +28 -0
- package/src/lib/helius-webhook.ts +90 -0
- package/src/lib/helius.ts +74 -0
- package/src/lib/house-shader.ts +119 -0
- package/src/lib/mock-data.ts +56 -0
- package/src/lib/multiplayer-manager.ts +329 -0
- package/src/lib/plane-physics.ts +66 -0
- package/src/lib/player-car.ts +147 -0
- package/src/lib/player-plane.ts +200 -0
- package/src/lib/projectile-system.ts +272 -0
- package/src/lib/skyscraper-types.ts +52 -0
- package/src/lib/sound-engine.ts +464 -0
- package/src/lib/supabase-admin.ts +9 -0
- package/src/lib/supabase.ts +8 -0
- package/src/lib/swap-events.ts +70 -0
- package/src/middleware.ts +37 -0
- package/src/types/phantom.d.ts +16 -0
- package/src/types/wallet.ts +20 -0
- 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,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
|
+
}
|