production · 28 minute study
Optimize the frame you can measure
A prettier screenshot can hide a slower, less playable game. Use repeatable budgets to decide whether to reduce simulation cost, draw calls, pixels, memory or network transfer.
Useful preparation · 1 prerequisite
Use this as a map, not a lock. If these ideas are familiar, continue.
- A save is untrusted, versioned input — Local persistence can be malformed; two devices can write concurrently; one browser can be shared by different people. A successful JSON parse is only the start of deciding whether a save is valid.
First, build a mental model
Performance is a set of budgets. Frame time governs responsiveness, texture allocation governs memory pressure, and transfer plus decode governs loading. A change can improve one budget while leaving another unchanged.
- Convert a target frame rate to time: 60 FPS allows about 16.67 ms per frame. CPU and GPU work can overlap, but synchronization or a slow stage can make one side wait. Measure the limiting stage rather than adding arbitrary component estimates and calling them a benchmark.
- Tail latency matters. A low average can hide recurring long frames. Keep a stable scene and examine a distribution, such as a percentile and worst representative stalls, together with the conditions that produced them.
- More pixels can increase GPU work; a device pixel ratio of 2 produces four times the pixels of DPR 1 at the same CSS dimensions. More draw calls can increase CPU submission cost. Which reduction helps depends on the observed bottleneck.
- Texture transfer size differs from decoded allocation. A 1024×1024 RGBA8 base image needs 4 MiB of texel data before mip levels and other overhead. Compression on the network alone does not make that uncompressed allocation smaller.
- Separate boot measurements from the test harness. Record when interaction is actually available, and distinguish cold cache, warm cache, asset decode and first rendering. Faster page-load events do not prove controls are ready.
function percentile(samples, fraction) {
if (samples.length === 0) return null;
const sorted = [...samples].sort((a, b) => a - b);
const index = Math.max(0, Math.ceil(sorted.length * fraction) - 1);
return sorted[Math.min(sorted.length - 1, index)];
}
const frameTimes = [7, 8, 9, 10, 42]; // illustrative milliseconds
console.log(percentile(frameTimes, 0.95)); // 42 with this tiny sample
console.log(1000 / 60); // frame budget: about 16.67 ms
// Collect many representative frames before making production claims.
// Measure CPU/GPU/network separately; this function only summarizes numbers.- Observe
Most listed frames fit the target, but one 42 ms frame visibly interrupts the cadence.
- Locate
Correlate the spike with CPU work, garbage collection, shader compilation, asset decode or another measured event.
- Change and compare
Modify the demonstrated bottleneck, replay the same scene, and compare the same metric under the same conditions.
Production thinking
Take this beyond the example.
Budget for representative mobile devices and sustained play, not only a powerful desktop's first ten seconds. Lazy-load heavy scenes, share resources with clear ownership and dispose them when no longer used. Stop optimizing once the target is met and player evidence identifies a more important problem.
Sources and further reading
Your evidence
unseen is a local learning signal, not a credential or proof of production competence.
Explain the mental model, then explicitly acknowledge it. Opening a page is not evidence.
Supported retries can complete this lesson. Independent evidence and delayed review determine the skill stage.