perf: adaptive performance mode for mobile

Auto-detect low-end devices (< 4 cores or touch) and reduce visual
complexity: 60% fewer particles, 6 vs 16 speed lines, 15 vs 40 stars,
halved datacenter buildings/racks, skip LED blinking animations,
fewer GPU graveyard chips, reduced afterimages and exhaust particles.

Toggle in fight viewer (LO/HI button), persisted in localStorage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-03-09 00:00:48 +00:00
co-authored by Claude Opus 4.6
parent 78f83a7520
commit 5af5ee1cba
4 changed files with 97 additions and 31 deletions
+32
View File
@@ -2,6 +2,38 @@
// Gameplay constants — all tunable magic numbers in one place
// ═══════════════════════════════════════════════════════════════
// --- Performance mode ---
const PERF_KEY = 'bf_perf_mode'
function detectLowEnd(): boolean {
if (typeof navigator === 'undefined') return false
const cores = navigator.hardwareConcurrency || 4
const mobile = navigator.maxTouchPoints > 0
return cores < 4 || mobile
}
function loadPerfPref(): boolean | null {
try {
const v = localStorage.getItem(PERF_KEY)
if (v === '1') return true
if (v === '0') return false
} catch {}
return null
}
let _perfMode = loadPerfPref() ?? detectLowEnd()
export function isPerfMode(): boolean { return _perfMode }
export function setPerfMode(on: boolean): void {
_perfMode = on
try { localStorage.setItem(PERF_KEY, on ? '1' : '0') } catch {}
}
/** Scale a particle count down in performance mode (40%) */
export function perfParticles(count: number): number {
return _perfMode ? Math.max(1, Math.round(count * 0.4)) : count
}
// --- Choreography selection probabilities ---
export const CREATOR_ULTIMATE_CHANCE = 0.5 // Creator ultimate chance (non-crit); 1.0 on crits
export const CREATOR_THEMED_MOVE_RATIO = 0.6 // Creator uses creator-themed moves 60% of the time
+7 -3
View File
@@ -1,9 +1,11 @@
import type { FightContext } from './types'
import { perfParticles, isPerfMode } from './config'
export function spawnSparks(ctx: FightContext, x: number, y: number, count: number, color: string): void {
const { k, safeColor } = ctx
const n = perfParticles(count)
const intense = count > 8
for (let i = 0; i < count; i++) {
for (let i = 0; i < n; i++) {
const angle = Math.random() * Math.PI * 2
const speed = (intense ? 150 : 100) + Math.random() * (intense ? 400 : 300)
const size = (intense ? 3 : 2) + Math.random() * (intense ? 6 : 4)
@@ -31,7 +33,8 @@ export function spawnSparks(ctx: FightContext, x: number, y: number, count: numb
export function spawnBulletHoles(ctx: FightContext, x: number, y: number, count: number): void {
const { k, safeColor, trackedTimeout, trackedInterval, clearTracked } = ctx
for (let i = 0; i < count; i++) {
const n = perfParticles(count)
for (let i = 0; i < n; i++) {
const hole = k.add([
k.circle(3 + Math.random() * 3),
k.pos(x + (Math.random() - 0.5) * 40, y + (Math.random() - 0.5) * 60),
@@ -53,7 +56,8 @@ export function spawnExhaust(ctx: FightContext, x: number, y: number, duration:
const endTime = performance.now() + duration * 1000
const interval = trackedInterval(() => {
if (performance.now() > endTime) { clearTracked(interval); return }
for (let i = 0; i < 3; i++) {
const exhaustCount = isPerfMode() ? 1 : 3
for (let i = 0; i < exhaustCount; i++) {
const p = k.add([
k.circle(3 + Math.random() * 5),
k.pos(x + (Math.random() - 0.5) * 15, y),