vchelaru/flatredball2/performance

Claude Code and agent skill by vchelaru/flatredball2

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

FlatRedBallService.Performance — opt-in rolling FPS/timing/collision stats; StartupProfiler for one-time boot/load timing. Triggers: PerformanceMonitor, GenerateReport, FPS, frame time, DeepCollisionCount, StartupProfiler, ProfileStartup, \"why is my game slow\", \"slow to load\", measuring a browser/WASM build, \"click didn't register\".

Install

npx vskill@latest install vchelaru/flatredball2/performance

SKILL.md

Performance Monitoring

FlatRedBallService.Default.Performance (a PerformanceMonitor, src/Diagnostics/PerformanceMonitor.cs) tracks rolling FPS, per-phase frame timing, and a per-collision-relationship severity breakdown over a 120-frame window.

Off by default — set IsEnabled = true before it records anything; until then every stat reads zero/empty.

csharp
FlatRedBallService.Default.Performance.IsEnabled = true;
// later, e.g. every N frames from Screen.CustomActivity:
var perf = FlatRedBallService.Default.Performance;
Console.WriteLine(perf.GenerateReport()); // FPS + phase timing + collision severity, as a string
var fps = perf.Fps; // .Current / .Average / .Min / .Max
var worst = perf.GetCollisionReport(); // most expensive first; each row carries a PartitionStatus

GenerateReport() only builds a string — it does no I/O itself, so printing/logging/writing it is on the caller.

Timer resolution (web)

GenerateReport() opens with a Platform: line and the measured clock step (TimerResolutionMs, probed once via ProfileClock.MeasureResolutionMs).

Landmine: browsers coarsen their clock as a Spectre mitigation — ~1ms on Firefox/Safari, ~0.1ms on Chrome — and Stopwatch.Frequency does not reflect it. Whole-pass totals stay accurate (start/end errors cancel), but any per-phase number smaller than one step reads as 0 or one full step and nothing between. The report warns automatically above 0.5ms. Read FrameTotal first, then the phases.

Set PlatformLabel from the host — the engine targets net10.0 and cannot read navigator.userAgent itself.

Every row in the collision report carries a PartitionStatus. Unpartitioned is the only value worth acting on, and the fix is to set the same Factory<T>.PartitionAxis on both sides of the relationship. NotApplicable means one side is not a factory, such as a TileShapes, a single entity, or a plain List<T>, so no axis setting would change it. See the collision-relationships skill.

See src/Diagnostics/FrameProfile.cs for the underlying per-frame timing struct.

Measuring a browser build

  • `dotnet run` does not measure what ships. Without AOT, Blazor WebAssembly runs on an IL interpreter, and AOT compilation only happens in dotnet publish, so a dev-server run stays interpreted even when the project sets RunAOTCompilation. AOT moves the per-frame baseline by a large factor on CPU-heavy code, so an interpreted number and an AOT number are never comparable.
  • Measure a Release publish served over HTTP. A standalone publish puts the site in the wwwroot folder under the publish output (bin/Release/net10.0/publish/wwwroot); serving publish/ itself returns 404 on /. Delete the publish folder before republishing, because publish leaves files from earlier publishes in place.
  • No `blazor.boot.json` is not a sign of AOT. See the multiplatform-conversion skill's WASM runtime landmines.
  • Debugger support makes the interpreter 2-6x slower, including start-up. The dev server's runtime config has debugLevel: -1. frb-host.js (Blazor.start) sets it to 0 unless the page URL has ?debug, so a plain dev-server run is the fast one, and breakpoints in the browser need ?debug. A page that autostarts Blazor skips frb-host.js's start and keeps -1; use -p:WasmDebugLevel=0 there.
  • Cheapest path for a Release/AOT publish: load the page with `?diag=perf` (optional &frames=N). PerfDiagnosticsProbe (src/Diagnostics/PerfDiagnosticsProbe.cs) collects N frames, then writes one [frb-diag] {json} line with Console.WriteLine and sets window.__frbDiag. It uses Console.WriteLine because Debug.WriteLine is stripped from Release builds. With no diag param nothing is enabled.
  • Console output is part of the measurement. On WebAssembly Console.WriteLine lands in the browser console, which makes it an easy probe and also a real per-frame cost. Remove or #if out per-frame logging before capturing a number.
  • A fixed timestep is required on every platform, browser included; never turn it off. Game.IsFixedTimeStep defaults to true on both MonoGame and KNI, and the engine never changes it. Setting it to false for smoother per-frame updates in the browser turns one long frame into one large physics and collision step, which lets fast objects tunnel through each other.
  • Browsers are not comparable to each other. Large Chromium-vs-Firefox frame-time ratios on the same build are normal, so one browser's number is not evidence about the other. Across browsers, compare counts such as DrawCalls and Sprites, not phase milliseconds.

Measuring headlessly

  • tools/measure-web.ps1 serves a built web head and prints the page console from headless Chrome, for example pwsh tools/measure-web.ps1 -Query '?diag=perf&frames=300' -StopPattern '\[frb-diag\]'. It serves --no-build, so build first (twice after clearing wwwroot/Content). After a package version change, delete the head's bin/ and obj/ before building: an incremental build can keep the old package's .wasm in _framework, so the run measures the old version. Use it instead of hand-rolling a browser run. It enforces one at a time, below-normal priority and a hard time cap.
  • A fresh profile means a cold shader cache on Windows. Pass -ProfileDir to a persistent folder for warm-cache numbers. On macOS use -Angle metal for the real GPU; the Metal shader cache is system-wide, so a fresh profile does not make a run cold there. Use -Angle swiftshader for repeatable cold runs on a Mac.
  • -Angle swiftshader is software GL: it inflates GPU and draw time but not managed CPU time, and frames= must stay small there.

Dropped clicks and first-frame stalls

  • A click that "didn't register" is usually a frame that hadn't drawn yet. Before changing input handling, timestamp the input against the frame: a capture-phase pointerdown listener logging event.timeStamp, next to a Console.WriteLine from game code, shows whether the input arrived during a long frame. Remove the probe before measuring anything else.
  • Keep temporary probes out of `index.html`. The browser can serve a stale copy; see the multiplatform-conversion skill's WASM runtime landmines.
  • A stall on the first frames after a screen or map load that never recurs is allocation, GC, or warm-up, not a per-frame cost. Don't chase it with hot-loop optimization. PerformanceMonitor does not track allocations; compare GC.GetTotalAllocatedBytes() and GC.CollectionCount(n) across the transition frame against steady state.

Startup timing (boot, not per-frame)

FlatRedBallService.Default.StartupTiming (a StartupProfiler, src/Diagnostics/StartupProfiler.cs) times boot and load once — for "why does my game take so long to start," not runtime FPS.

Enable via EngineInitSettings.ProfileStartup = true passed to Initialize, not on the profiler itself — profiling must be on before Initialize opens its first phase, so there's no turning it on afterward.

The report prints itself automatically, once, right after the first screen finishes loading — don't call GenerateReport() yourself, and it won't fire again on later screen transitions.

Game code can wrap its own slow CustomInitialize work in BeginPhase/EndPhase to show up in the same report.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/vchelaru/flatredball2/performance/badge)](https://verified-skill.com/skills/vchelaru/flatredball2/performance)

Something wrong with this skill or its page? Tell us.