Scenarios
A scenario names a setup a tool runs the same way every time: the scene, where the player’s character spawns, who plays, and how the page draws. You declare it once, and two commands read it:
spawnite scenario <name>runs it in Node with no browser and no GPU, as a test, and prints the world’s dump.spawnite play profile --scenario <name>measures it on the game’s build, and judges its frame by the budget your baseline keeps.
So the place a profile measures is the place a test runs. Unreal’s Functional Tests and Unity’s play mode tests name a setup the same way. A scenario holds data alone: no steps and no assertions.
Declare a scenario
Section titled “Declare a scenario”Export each scenario made with defineScenario from a file under your game’s test/scenarios/. Its name is its export’s in kebab case, so groveWalk is grove-walk:
import { defineScenario, QualityLevel, ScenarioDrive, ScenarioWarmth,} from "@spawnite/engine";
/** The player at the forest edge's spawn, with nobody playing. */export const spawn = defineScenario({ scene: "forest-edge", level: QualityLevel.High,});
/** A walk from a grove, as a returning player meets it on a 1440p screen, * with bloom off to price it. */export const groveWalk = defineScenario({ scene: "forest-edge", position: [20, 2.4, 60], facingRadians: 2.25, drive: ScenarioDrive.Walk, level: QualityLevel.High, seed: 7, viewport: { width: 2560, height: 1440 }, warmth: ScenarioWarmth.Warm, overrides: { bloom: false },});Every field is optional:
| Field | What it sets | Left out |
|---|---|---|
scene |
The registered scene the world opens on | The game’s start |
position |
Where the player’s character spawns, in metres, over the scene’s own spawn | The scene’s own spawn |
facingRadians |
The way the character faces as it spawns | The scene’s own spawn’s |
drive |
ScenarioDrive.Still, nobody plays, or ScenarioDrive.Walk, the profile’s walk |
Still |
level |
The quality level the page draws at | QualityLevel.High |
backend |
The GPU interface the page asks for | GraphicsBackend.Auto |
seed |
The seed the world’s random stream starts from | The fixed seed a profile uses |
population |
players, one so far, and bots, room bots in a room game’s room |
One player and no bots |
tickRate |
Steps a second, 30 or 60 | The game’s own |
viewport |
The page’s size in CSS pixels | 1920 by 1080 |
warmth |
ScenarioWarmth.Cold, a first visit, or Warm, a returning player’s shader cache |
Cold |
overrides |
The level’s values over the game’s own, as Game’s quality.levels takes them |
None |
defineScenario throws as your file loads on a number no type can hold, such as a seed of 1.5 or a viewport 0 pixels wide.
Run it as a test
Section titled “Run it as a test”spawnite scenario --list prints each scenario and its file. spawnite scenario grove-walk runs one in Node:
- It makes the world at the scenario’s tick rate and seed.
- It mounts the scene, and the scene’s
<CharacterSpawn>stands the player’s character at the scenario’s position and facing. - It runs the drive for
--seconds, 20 by default. - It prints the world’s dump, its hash, and the session it ran in.
Node draws nothing, so the level, the backend, the viewport, the warmth and the overrides are only a page’s. The walk’s keys run in Node. Its camera drags turn a page’s camera, and Node has none. A scenario with room bots is refused, since Node runs no room.
Measure it against its budget
Section titled “Measure it against its budget”spawnite play profile --scenario grove-walk profiles the game’s build with the page the scenario sets. It runs uncapped, so a frame’s time is its cost. It refuses a flag the scenario sets, such as --quality.
The frame’s budget lives in your baseline, test/profile-baseline.json, never in the scenario. It is a list under budgets, one line for each device, level and backend:
{ "budgets": [ { "device": "ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 SUPER (0x00002783) Direct3D11 vs_5_0 ps_5_0, D3D11)", "level": "high", "backend": "classic", "frameSeconds": 0.00694 } ]}The device is the GPU the profile names as its renderer. The backend is classic while the engine draws with three’s WebGLRenderer. The profile judges the frame’s 99th percentile against the line for its device, level and backend. A device with no line is named and not judged, rather than judged by another device’s line. With --check, the profile also refuses a level with no line before it runs, and exits 1 on an unknown device or a frame past its budget.
Every result names the scenario, the session, the level and the backend.
Limits
Section titled “Limits”- One player plays a scenario so far.
- A room game’s room places its players, so a scenario that sets
positionorfacingRadianson a room game is refused. A room game’s seed and tick rate go to its room. - A room game’s room steps the scene its package.json names, so a scenario on a room game names that scene or none.
- A warm run plays on the game’s kept browser profile, which an empty profile’s first short run fills. Its shaders come from Chrome’s cache; its files download again, since each run is served on a port of its own.
GraphicsBackend.WebGpuandWebGlare refused until the renderer moves toWebGPURenderer, rather than drawn on another backend.- A scenario has no steps, no conditions and no assertions yet.