Performance budgets
Frame, memory, draw-call, triangle, texture, particle, audio, download and network budgets per quality tier, plus the techniques that keep Zoen smooth on browsers and phones.
Optimisation is a design input, not a final pass. Every asset and system has a budget before it is built, and the budget is checked by a logged test. Outcomes are identical on every tier. Tiers only change how things look.
◆Frame targets
| Tier | Typical device | Target | Frame budget | Render scale |
|---|---|---|---|---|
| Low | selected entry-level references below | 30 fps | 33.3 ms | dynamic 0.6–0.85 |
| Mid | mid-range phones and laptops, additional references | 30 fps (60 optional) | 33.3 ms | dynamic 0.75–1.0 |
| High | desktop (owner's M3), high-end phones | 60 fps | 16.7 ms | 1.0 |
| Ultra | desktop dGPU | 60+ fps | 16.7 ms | 1.0 + extras |
Within a frame on High: gameplay/sim ≤ 2 ms, animation ≤ 2.5 ms, culling/LOD ≤ 1 ms, render submit ≤ 4 ms, UI ≤ 1 ms, the rest goes to the GPU. Dynamic resolution holds the frame budget before any visual feature is dropped.
Platform limit (checked 2026-10-06): Unity supports dynamic resolution on Metal, Vulkan and DirectX 12 only (Unity manual). The Web build and any OpenGL ES device need a fixed render scale picked by the capability probe and changed only at safe moments. The GPU Resident Drawer has similar limits (Forward+ and compute shaders; not Web or OpenGL ES; Mesh Renderers only). The per-platform table, five named perf scenes and the extra targets (zero garbage in combat, shader prewarm, frame spread) are proposed in the AAA detail catalog.
◆Reference hardware (owner direction, 2026-10-05)
Use entry-level hardware as the floor, as delegated by the owner. These selected configurations require physical
validation; their specifications do not establish measured game compatibility. Canonical record: reference-devices.json.
| Platform | Selected Low reference | Basis and source |
|---|---|---|
| Android | Samsung Galaxy A07 4G/LTE, Helio G99, 4 GB physical RAM, 128 GB | Current entry-level configuration; distinguish from 5G/higher-RAM variants. Samsung specifications. |
| iOS | iPhone SE 2nd generation (2020), A13 Bionic | Older lower-end hardware supported on current iOS 27; small-screen touch/readability stress. Apple specifications, Apple compatibility. |
| Windows | ASUS Vivobook Go 15 E1504TA-class, Intel N150, integrated Intel UHD, 8 GB RAM, SSD, Windows 11 | Entry-level laptop configuration; verify the exact local SKU rather than substituting a faster variant. ASUS specifications. |
Sources checked 2026-10-05. Record each physical unit's exact SKU, OS build, GPU driver/API, real RAM and storage;
RAM expansion is not physical RAM. No devices have been purchased or benchmarked by this documentation task. The capability probe (M0-05) logs one ZOEN_PROBE line per client run against the contract in reference-devices.json → probe; owner-device results are labelled with their probe and never validate a Low reference, whose probes stay pending until units exist. The
owner's M3 stays a secondary development reference (Chrome; its native app is paused since 2026-10-11,
ADR-032) and the owner's Windows PC (unprobed until WIN-01) is the High test machine;
neither can substitute for the Windows Low probe. The Mac's native app
and Chrome runs are measured (2026-10-08, M0-05C: ownerTestDevices[].probes, merged table
logs/evidence/M0-05C/probe-summary.txt); so is the owner's phone (nubia NX709S) in Chrome (2026-10-08, M0-05D) and as
the native APK (2026-10-08, M0-05E: Vulkan 1.1, 18 GB class memory, ASTC/BC/ETC2, Mid from the cached benchmark). Unity's
Android player cuts a log entry at 1023 bytes, so on Android the probe is logged as ZOEN_PROBE_PART i/n lines of at most
800 bytes (reference-devices.json → probe.partNote).
The starting gate reuses Low 30 fps / 33.3 ms / process or tab ≤600 MB and the existing 30-minute playtest. Profile native and supported browsers separately: both views, camp/crowd/boss telegraphs, boss music, sustained mana-limited 0.3 s dash reuse, jumps, collisions, chunk streaming and background/resume. Log p50/p95/p99 frame time, draw calls, particles, memory, thermal degradation and input response. Keep warnings visible when reducing decoration. Exact acceptance targets remain proposed until the feasibility gate supplies measurements. A failure requires optimization or an explicit baseline revision; do not silently raise minimum hardware.
◆Platform caps (owner, round 4)
| Platform | Quality range | Default |
|---|---|---|
| Browser | Low–Mid only (hard cap) | Mid (Low on weak devices) |
| Windows app | Low–Ultra | High |
| Android / iOS apps | Low–Ultra (Ultra only on devices that pass the thermal/frame gate) | Mid |
The full High/Ultra experience is in the downloaded apps; the browser stays light and smooth. Data: feel-quality.json → platformCaps.
The client's starting-tier rules (low-memory phone, low-end browser, native startup benchmark, the phone Ultra gate) and
the per-tier frame caps above are proposed in feel-quality.json → tierSelection (M0-04C); the client reads them from there.
Engine review: Engine & platforms. Capability probing selects Low on the entry-level references
before heavier content loads; platform defaults do not force Mid/High onto weak hardware.
◆Budgets by tier
| Budget | Low | Mid | High | Ultra |
|---|---|---|---|---|
| Draw calls (after instancing/SRP batching) | 250 | 600 | 1,500 | 2,500 |
| Triangles on screen | 400k | 800k | 2.5M | 4M |
| Visible players (default / max) | 25 / 40 | 40 / 60 | 60 / 100 | 100 / 150 |
| Live particles (own / total) | 300 / 900 | 800 / 2,000 | 2,000 / 5,000 | 3,000 / 8,000 |
| Shadows | blob only | 1 cascade 1024 | 2 cascades 2048 | 3 cascades 4096 |
| Post FX | tonemap only | + bloom (half-res) | + SSAO, colour grading | + volumetric fog |
| View distance | 120 m | 180 m | 260 m | 400 m |
| Vegetation density | 0.3 | 0.6 | 1.0 | 1.3 |
| GPU texture memory | 256 MB | 512 MB | 1 GB | 1.5 GB |
| Memory (process; Web: tab) | ≤ 600 MB | ≤ 900 MB | ≤ 1.5 GB | ≤ 2 GB |
| Audio voices | 16 | 24 | 32 | 48 |
◆Asset budgets
| Asset | LOD0 | LOD1 | LOD2 | Crowd (VAT) | Textures |
|---|---|---|---|---|---|
| Player (body + armour) | 22k tris | 9k | 3.5k | 1.5k | 2K atlas (BC7/ASTC via KTX2) |
| Monster small / medium / large | 4k / 8k / 14k | ½ | ¼ | — | 1K / 1K / 2K |
| Boss | 40k | 18k | 8k | — | 2K × 2 |
| Prop | 0.3–5k | ½ | impostor | — | 512–1K, atlased |
| Tree | 2–6k | 1.5k | billboard impostor | — | 1K atlas |
| Terrain chunk (256 m) | 2× 129² grid + skirts | 65² | 33² | — | 4 splat layers per chunk, 2K detail arrays |
Textures compress per platform on import: BC7 (Windows), ASTC (Android/iOS), ASTC/ETC2 for the Web build. Meshes use Unity mesh compression; streamed content ships as Addressables bundles. Skinned meshes use ≤ 4 bone influences and ≤ 80 bones.
◆Download and startup
| Item | Budget |
|---|---|
| First load to login screen | ≤ 15 MB, ≤ 6 s desktop broadband, ≤ 12 s 4G |
| Town (Amber Gate) playable | + 25 MB streamed |
| Per 256 m chunk | 0.5–2 MB, streamed 2 rings ahead of the player |
| Unity Web build | Low–Mid only; IL2CPP + managed stripping High, Brotli, Addressables per zone (no High/Ultra content in the web catalog) |
◆Network and server
Downstream ≤ 12 KB/s typical, ≤ 32 KB/s cap per client. Upstream ≈ 0.3 KB/s. Server tick p99 ≤ 15 ms at 1,000 players. Details in 1,000 players per map.
◆Techniques we rely on
- Instancing everywhere: GPU Resident Drawer / GPU instancing (
RenderMeshInstanced) for vegetation, rocks, projectiles, damage numbers and baked-animation crowds. - SRP Batcher (all targets) and GPU Resident Drawer (native) for static geometry, plus frustum + distance culling per chunk.
- Pools, not allocations: projectiles, emitters, decals, audio voices and damage numbers come from fixed pools. No
newin the frame loop. - Animation LOD: update rate drops with distance (60 → 30 → 15 Hz), and off-screen characters skip bone evaluation.
- Shader variant budget: ≤ 64 material variants, prewarmed in a loading phase (no hitch on the first cast).
- VFX LOD: every effect has Low/High definitions with the same timing and footprint. Optional tails/sparks drop first.
The per-tier feel layers (debris, decals, lights, screen effects) are listed in
feel-quality.jsonand VFX. - UI: the UI Toolkit HUD updates bound values only when they change; no per-frame allocations.
- Dynamic resolution and frame pacing: keep frame time stable instead of chasing peaks.
◆How budgets are tested
- Perf harness scenes (Combat Lab, town crowd, boss fight) run as PlayMode tests with the Unity Performance Testing package
(Web build: Playwright drives the page) and write
perf.jsonwith p50/p95/p99 frame time, frames over 33 ms, draw calls, triangles, particles, managed heap and GPU memory estimate. - Each milestone repeats them on the reference devices (manual run, logged with
task-log.mjs test). - An asset validator script rejects assets over budget (tris, bones, texture size, missing LODs) before they enter the build.
Source: zoen/docs/tech/PERFORMANCE.md · 1,594 words · edit the Markdown, not this page.
