From 5afa2f13e378bca20689d9d318ca164a11843186 Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Sat, 17 Jan 2026 11:36:50 +0100 Subject: [PATCH 1/9] fix trip value change calc for boosted trips --- packages/client/src/lib/components/Admin/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/lib/components/Admin/helpers.ts b/packages/client/src/lib/components/Admin/helpers.ts index 6e690312..e5deae7b 100644 --- a/packages/client/src/lib/components/Admin/helpers.ts +++ b/packages/client/src/lib/components/Admin/helpers.ts @@ -59,7 +59,7 @@ export function calculateProfitLossForTrip( const previousOutcome = tripOutcomes[i - 1] const outcomeTime = new Date(outcome._createdAt).getTime() - const previousOutcomeValue = previousOutcome?.tripValue || Number(trip.tripCreationCost) // Value from the previous trip, and if it's the first outcome, creation cost of the trip + const previousOutcomeValue = previousOutcome?.tripValue ?? outcome.oldTripValue ?? Number(trip.tripCreationCost) // Value from the previous trip, or oldTripValue (accounts for boosts), or creation cost const currentTripValue = outcome.tripValue || 0 const valueChange = currentTripValue - previousOutcomeValue From f776bd0e2d564e0c249e0702fdc4bd845764e53e Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Sat, 17 Jan 2026 11:49:51 +0100 Subject: [PATCH 2/9] add typescript check workflow for PRs --- .github/workflows/typecheck.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/typecheck.yml diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml new file mode 100644 index 00000000..7d121c81 --- /dev/null +++ b/.github/workflows/typecheck.yml @@ -0,0 +1,33 @@ +name: TypeScript Check + +on: + pull_request: + branches: [main] + +jobs: + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'pnpm' + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build contracts + run: pnpm --filter 'contracts' run build + + - name: Build libs + run: pnpm build:libs + + - name: TypeScript check + run: pnpm check From 1bb1bd83424a0e00fcb5ea3b0ea3e22f72e8e0dc Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Sat, 17 Jan 2026 12:05:35 +0100 Subject: [PATCH 3/9] skip broken packages in typecheck until env vars configured --- .github/workflows/typecheck.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 7d121c81..7d8ac646 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -30,4 +30,5 @@ jobs: run: pnpm build:libs - name: TypeScript check - run: pnpm check + # TODO: re-enable claim-frontend and exchange-frontend once env vars are configured for CI + run: pnpm --filter '@ratfun/shared-ui' --filter 'client' --filter 'auction-frontend' --filter 'server' --filter 'websocket-server' --filter 'query-server' run check From 34cc7d0ec6f191368cc6487a01556a89619e9918 Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Sat, 17 Jan 2026 12:08:06 +0100 Subject: [PATCH 4/9] copy .env.example files for CI typecheck --- .github/workflows/typecheck.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 7d8ac646..dfd93e29 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -20,6 +20,14 @@ jobs: - name: Install Foundry uses: foundry-rs/foundry-toolchain@v1 + - name: Setup env files + run: | + for pkg in packages/*/; do + if [ -f "$pkg.env.example" ]; then + cp "$pkg.env.example" "$pkg.env" + fi + done + - name: Install dependencies run: pnpm install --frozen-lockfile @@ -30,5 +38,4 @@ jobs: run: pnpm build:libs - name: TypeScript check - # TODO: re-enable claim-frontend and exchange-frontend once env vars are configured for CI - run: pnpm --filter '@ratfun/shared-ui' --filter 'client' --filter 'auction-frontend' --filter 'server' --filter 'websocket-server' --filter 'query-server' run check + run: pnpm check From 9c39551a8f53111f896c9cc7d378bfe250b8560c Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Sat, 17 Jan 2026 12:11:55 +0100 Subject: [PATCH 5/9] exclude deprecated claim-frontend from typecheck --- .github/workflows/typecheck.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index dfd93e29..49180476 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -38,4 +38,5 @@ jobs: run: pnpm build:libs - name: TypeScript check - run: pnpm check + # claim-frontend excluded - depends on merkle-tree-airdrop which is deprecated post-airdrop + run: pnpm --filter '!claim-frontend' check From 79db76e5b86b90e2b466924fcc61172a070093f5 Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Sat, 17 Jan 2026 12:15:30 +0100 Subject: [PATCH 6/9] exclude deprecated auction-frontend from typecheck --- .github/workflows/typecheck.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index 49180476..b70b2d38 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -38,5 +38,5 @@ jobs: run: pnpm build:libs - name: TypeScript check - # claim-frontend excluded - depends on merkle-tree-airdrop which is deprecated post-airdrop - run: pnpm --filter '!claim-frontend' check + # claim-frontend, auction-frontend: deprecated, events over + run: pnpm --filter '!claim-frontend' --filter '!auction-frontend' check From 1a1632714520408c92a236f65caf717670464140 Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Sat, 17 Jan 2026 12:21:39 +0100 Subject: [PATCH 7/9] build query-server before typecheck --- .github/workflows/typecheck.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typecheck.yml b/.github/workflows/typecheck.yml index b70b2d38..e96aef68 100644 --- a/.github/workflows/typecheck.yml +++ b/.github/workflows/typecheck.yml @@ -35,7 +35,7 @@ jobs: run: pnpm --filter 'contracts' run build - name: Build libs - run: pnpm build:libs + run: pnpm build:libs && pnpm --filter 'query-server' run build - name: TypeScript check # claim-frontend, auction-frontend: deprecated, events over From 4728ecd0b21f0f4c5982a9d6aacb3eb2675819a1 Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Mon, 19 Jan 2026 20:47:23 +0100 Subject: [PATCH 8/9] replace console.* with conditional logger in webgl package --- packages/webgl/src/ShaderManager.ts | 7 +++--- packages/webgl/src/WebGLGeneralRenderer.ts | 9 ++++--- packages/webgl/src/index.ts | 1 + packages/webgl/src/logger.ts | 29 ++++++++++++++++++++++ 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 packages/webgl/src/logger.ts diff --git a/packages/webgl/src/ShaderManager.ts b/packages/webgl/src/ShaderManager.ts index 72cd45fe..a3ed05c4 100644 --- a/packages/webgl/src/ShaderManager.ts +++ b/packages/webgl/src/ShaderManager.ts @@ -2,6 +2,7 @@ import { WebGLGeneralRenderer } from "./WebGLGeneralRenderer" import { shaders } from "./shaders" import { ShaderInitializationError } from "@ratfun/common/error-handling" import type { UniformType, UniformDefinition } from "./types" +import { logger } from "./logger" export type ErrorHandler = (error: Error, context?: string) => void @@ -239,7 +240,7 @@ export class ShaderManager { } } catch (error) { // Error already logged to Sentry in initializeRenderer - console.error(`Failed to initialize shader "${pendingKey}":`, error) + logger.error(`shader init failed: ${pendingKey}`) // Mark context as exhausted and hide canvas to show CSS fallback this._contextExhausted = true @@ -321,7 +322,7 @@ export class ShaderManager { window.removeEventListener("resize", this.handleResize) window.addEventListener("resize", this.handleResize) } catch (error) { - console.error("Failed to initialize WebGL renderer:", error) + logger.error("renderer init failed") this._renderer = null // Report to Sentry @@ -398,7 +399,7 @@ export class ShaderManager { } } catch (error) { // Still failing, try again - console.warn(`Recovery attempt ${this.recoveryAttempts} failed, will retry...`) + logger.warn(`recovery attempt ${this.recoveryAttempts} failed, retrying`) this.scheduleContextRecovery(shaderKey) } } diff --git a/packages/webgl/src/WebGLGeneralRenderer.ts b/packages/webgl/src/WebGLGeneralRenderer.ts index f8920772..67bad801 100644 --- a/packages/webgl/src/WebGLGeneralRenderer.ts +++ b/packages/webgl/src/WebGLGeneralRenderer.ts @@ -6,6 +6,7 @@ import { WebGLError, UniformLocationError } from "@ratfun/common/error-handling" +import { logger } from "./logger" /** * A general-purpose WebGL renderer that can render shaders to a canvas. @@ -239,14 +240,14 @@ export class WebGLGeneralRenderer implements WebGLRenderer { this.animationId = undefined } - console.warn("WebGL context lost") + logger.warn("context lost") } /** * Handles WebGL context restore event. */ private handleContextRestored = (): void => { - console.log("WebGL context restored, reinitializing...") + logger.log("context restored, reinitializing") this.contextLost = false try { @@ -255,7 +256,7 @@ export class WebGLGeneralRenderer implements WebGLRenderer { this.render() } } catch (error) { - console.error("Failed to restore WebGL context:", error) + logger.error("failed to restore context") // Error already reported in initWebGL } } @@ -520,7 +521,7 @@ export class WebGLGeneralRenderer implements WebGLRenderer { } } catch (error) { // WebGL context may be lost or invalid, ignore cleanup errors - console.warn("WebGL cleanup failed (context may be lost):", error) + logger.warn("cleanup failed, context may be lost") } finally { this.uniformLocations.clear() // Clear references to allow garbage collection diff --git a/packages/webgl/src/index.ts b/packages/webgl/src/index.ts index cf9c3a90..9c273714 100644 --- a/packages/webgl/src/index.ts +++ b/packages/webgl/src/index.ts @@ -1,6 +1,7 @@ // Main exports export { WebGLGeneralRenderer, createWebGLRenderer } from "./WebGLGeneralRenderer" export { ShaderManager, createShaderManager } from "./ShaderManager" +export { setWebGLLoggingEnabled, isWebGLLoggingEnabled } from "./logger" // Type exports export type { diff --git a/packages/webgl/src/logger.ts b/packages/webgl/src/logger.ts new file mode 100644 index 00000000..a3f2ccfe --- /dev/null +++ b/packages/webgl/src/logger.ts @@ -0,0 +1,29 @@ +// webgl package logger - disabled by default to prevent memory leaks +// enable via setWebGLLoggingEnabled(true) during development + +let loggingEnabled = false + +export function setWebGLLoggingEnabled(enabled: boolean): void { + loggingEnabled = enabled +} + +export function isWebGLLoggingEnabled(): boolean { + return loggingEnabled +} + +export const logger = { + log: (...args: unknown[]): void => { + if (loggingEnabled) { + console.log("[WebGL]", ...args) + } + }, + warn: (...args: unknown[]): void => { + if (loggingEnabled) { + console.warn("[WebGL]", ...args) + } + }, + error: (...args: unknown[]): void => { + // errors always logged regardless of loggingEnabled + console.error("[WebGL]", ...args) + } +} From 237c4d603f4f4d72ac78e502ae039a7934d0cf44 Mon Sep 17 00:00:00 2001 From: Arkadiy Kukarkin Date: Mon, 19 Jan 2026 21:02:31 +0100 Subject: [PATCH 9/9] add LOD time gates, quality tier, and 30fps mobile for perf --- .../GameRun/TripProcessing/TripProcessing.svelte | 5 ++++- packages/webgl/src/ShaderManager.ts | 8 +++++++- packages/webgl/src/WebGLGeneralRenderer.ts | 2 +- .../src/shaders/trip-processing/fragment.glsl | 16 +++++++++++++--- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/client/src/lib/components/GameRun/TripProcessing/TripProcessing.svelte b/packages/client/src/lib/components/GameRun/TripProcessing/TripProcessing.svelte index 25852c45..c3427408 100644 --- a/packages/client/src/lib/components/GameRun/TripProcessing/TripProcessing.svelte +++ b/packages/client/src/lib/components/GameRun/TripProcessing/TripProcessing.svelte @@ -4,6 +4,7 @@ import { playSound } from "$lib/modules/sound" import { backgroundMusic } from "$lib/modules/sound/stores" import { shaderManager } from "$lib/modules/webgl/shaders/index.svelte" + import { singleFrameRender } from "$lib/modules/ui/state.svelte" import TripProcessingPopUp from "./TripProcessingPopUp.svelte" const { @@ -45,9 +46,11 @@ shaderManager.enableContinuousRendering() // Set the trip processing shader with custom uniforms + // u_quality: 0.0 on mobile (skip madness layer), 1.0 on desktop (full quality) shaderManager.setShader("tripProcessing", false, { u_seed1: { type: "float", value: seed1 }, - u_seed2: { type: "float", value: seed2 } + u_seed2: { type: "float", value: seed2 }, + u_quality: { type: "float", value: $singleFrameRender ? 0.0 : 1.0 } }) // Start timer diff --git a/packages/webgl/src/ShaderManager.ts b/packages/webgl/src/ShaderManager.ts index a3ed05c4..48482be8 100644 --- a/packages/webgl/src/ShaderManager.ts +++ b/packages/webgl/src/ShaderManager.ts @@ -12,6 +12,8 @@ export interface ShaderManagerOptions { singleFrameRender: () => boolean /** Preserve drawing buffer for canvas capture (enables toDataURL) - impacts performance */ preserveDrawingBuffer?: boolean + /** Target FPS (default: 60 desktop, 30 mobile) */ + targetFPS?: number } export class ShaderManager { @@ -32,6 +34,7 @@ export class ShaderManager { private singleFrameRender: () => boolean private singleFramePauseRafId: number | null = null private preserveDrawingBuffer: boolean + private targetFPS: number currentShaderKey: string | null = null private shaderChangeListeners: Array<(shaderKey: string | null) => void> = [] private shaderChangeTimeout: ReturnType | null = null @@ -45,6 +48,8 @@ export class ShaderManager { this.errorHandler = options.errorHandler this.singleFrameRender = options.singleFrameRender this.preserveDrawingBuffer = options.preserveDrawingBuffer ?? false + // 30fps on mobile, 60fps on desktop + this.targetFPS = options.targetFPS ?? (options.singleFrameRender() ? 30 : 60) } /** @@ -313,7 +318,8 @@ export class ShaderManager { shader: shaderSource, uniforms: initialUniforms, onError: this.errorHandler, - preserveDrawingBuffer: this.preserveDrawingBuffer + preserveDrawingBuffer: this.preserveDrawingBuffer, + targetFPS: this.targetFPS }) this._renderer.render() diff --git a/packages/webgl/src/WebGLGeneralRenderer.ts b/packages/webgl/src/WebGLGeneralRenderer.ts index 67bad801..d2f38c52 100644 --- a/packages/webgl/src/WebGLGeneralRenderer.ts +++ b/packages/webgl/src/WebGLGeneralRenderer.ts @@ -54,7 +54,7 @@ export class WebGLGeneralRenderer implements WebGLRenderer { this.startTime = performance.now() this.uniforms = options.uniforms || {} this.autoRender = options.autoRender ?? true - this.frameInterval = 1000 / 60 + this.frameInterval = 1000 / (options.targetFPS ?? 60) this.shaderSource = options.shader this.onError = options.onError this.preserveDrawingBuffer = options.preserveDrawingBuffer ?? false diff --git a/packages/webgl/src/shaders/trip-processing/fragment.glsl b/packages/webgl/src/shaders/trip-processing/fragment.glsl index 1dfe16a7..c0e9797a 100644 --- a/packages/webgl/src/shaders/trip-processing/fragment.glsl +++ b/packages/webgl/src/shaders/trip-processing/fragment.glsl @@ -45,6 +45,12 @@ precision highp float; #define WARP3_MULTIPLIER 3.33 // Warp3 intensity multiplier #define WARP3_STRENGTH 1.5 // Warp3 displacement strength +// LOD time thresholds (must match complexity thresholds to avoid negative amplitude) +// complexityFactor = u_time / 10.0, so threshold 0.6 = 6 seconds +#define LOD_TIME_TURB 6.0 // Enable turbulence at t=6s (complexity=0.6) +#define LOD_TIME_WARP3 7.0 // Enable warp3 at t=7s (complexity=0.7) +#define LOD_TIME_MADNESS 8.0 // Enable madness at t=8s (complexity=0.8) + // Turbulence parameters #define TURB_THRESHOLD 0.6 // Complexity threshold for turbulence #define TURB_MULTIPLIER 2.5 // Turbulence pattern multiplier @@ -126,6 +132,7 @@ uniform vec2 u_resolution; // Screen resolution (width, height) uniform bool u_invert; // Color inversion toggle uniform float u_seed1; // First seed value (0-1) uniform float u_seed2; // Second seed value (0-1) +uniform float u_quality; // Quality tier (0.0=mobile, 1.0=desktop) // ============================================================================ // HASH FUNCTIONS @@ -315,7 +322,8 @@ void main() { warpedUV += vec2(sin(warp2 * TWO_PI), cos(warp2 * TWO_PI)) * (WARP2_STRENGTH_BASE + chaosIntensity * WARP2_STRENGTH_CHAOS); // Coordinate warping - layer 3 (extreme chaos) - if (complexityFactor > WARP3_THRESHOLD) { + // LOD: uniform-based branch allows GPU to skip entirely before 7s + if (u_time > LOD_TIME_WARP3) { float warp3a = rotatedNoise(warpedUV * (10.0 + seedOffset1 * 0.1), noiseRotation2, smoothness1); float warp3b = ridgedNoise(warpedUV * (8.0 + seedOffset2 * 0.1), smoothness2); float warp3 = mix(warp3a, warp3b, combinedSeed) * (complexityFactor - WARP3_THRESHOLD) * WARP3_MULTIPLIER; @@ -359,7 +367,8 @@ void main() { pattern += noisePattern2 * chaosIntensity * LAYER5_CHAOS_WEIGHT; // Layer 6: Extreme turbulence (conditional) - if (complexityFactor > TURB_THRESHOLD) { + // LOD: uniform-based branch allows GPU to skip entirely before 6s + if (u_time > LOD_TIME_TURB) { vec2 turbCoord = warpedUV * (12.0 + combinedSeed * 20.0); vec2 spiralVec = vec2(spiralTime); @@ -374,7 +383,8 @@ void main() { } // Layer 7: Complete madness (conditional) - if (complexityFactor > MAD_THRESHOLD) { + // LOD: skip on mobile (u_quality < 1.0) and before 5s + if (u_time > LOD_TIME_MADNESS && u_quality > 0.5) { float madness = (complexityFactor - MAD_THRESHOLD) * MAD_MULTIPLIER; vec2 madCoord = warpedUV * (20.0 + seedOffset1 * HALF) + vec2(spiralTime * TWO, -spiralTime * 1.5);