makepad/skills/makepad-qa-release/references/test-harness.md
Arena Agent fe21c07d84 skills: add Makepad game skills pack ported from threejs-game-skills
Nine agent skills for building Makepad/Rust games, ported from
majidmanzarpour/threejs-game-skills. Same director-routed workflow and
premium bar; runtime rewritten for this fork's game crates.

- makepad-game-director        entrypoint, routing, continuity, asset probe
- makepad-gameplay-systems     loop, movers vs rigid bodies, input, camera, netplay
- makepad-aaa-graphics-builder lighting, shaders, budget, visual scorecard
- makepad-game-ui-designer     HUD, menus, touch and XR UI
- makepad-debug-profiler       defect bisection and profiling
- makepad-qa-release           verification ladder, evidence, packaging
- makepad-3d-generator         CC0 model search, casts, procedural geometry
- makepad-image-generator      texgen textures, palettes, sky, icons
- makepad-audio-generator      sample bank, mixer, material impacts, 3D audio

Written against the real APIs in libs/game/*, libs/sim and apps/arcade:
the game.* verb table, GameRenderer adaptive quality, the packed 6-float
GameMeshVertex layout, script_mod! splash styling, makepad-test driving,
and the BUDGETS.md numbers. No paid generation API is required - the CC0
library plus seeded makepad-game-gen replaces them.

Includes install.sh (Codex/Claude), validate-skills.sh and a repo-aware
probe_assets.sh; both scripts verified against this checkout.
2026-09-05 21:18:58 +00:00

127 lines
4.5 KiB
Markdown

# Test harness
## Which kind of test
| Claim | Test |
| --- | --- |
| A rule holds (physics, scoring, generation, netcode) | unit test in the Cx-free crate |
| Two systems compose correctly | integration test with both crates as dev-deps |
| The app responds to input | `makepad-test` driven test |
| It looks right | driven test + `screenshot()` + your inspection |
| It is fast enough | probe example in `--release` + `frame_p90_ms()` |
**Prefer the lowest rung that can express the claim.** Driven tests are slow and flaky relative to unit tests; use them for what only they can prove.
## Unit tests
```rust
#[test]
fn checkpoint_counts_once_per_crossing() {
let mut kit = RaceKit::new(/* .. */);
cross(&mut kit, 0);
cross(&mut kit, 0); // same checkpoint again
assert_eq!(kit.standings()[0].lap, 0, "re-crossing must not advance the lap");
}
#[test]
fn generator_is_deterministic() {
let a = generate_level(1234);
let b = generate_level(1234);
assert_eq!(a, b, "same seed must give byte-identical output");
}
```
Determinism tests matter more here than in most codebases: netplay and `(preset, seed, position)` replication both depend on them, and a non-deterministic generator fails silently until a second player joins.
## Integration tests
When the claim spans crates, add the other crate as a dev-dependency and test the composition. The house precedent: `makepad-game-blocks` dev-depends on `gen` and `render` because "a walker can enter a REAL generated room" and "the car's body does not float above the road" are claims about real geometry meeting real physics. Split across two crates they become two half-tests that both pass while the car hovers — which is exactly what happened.
```rust
#[test]
fn car_body_rests_on_the_road() {
let road = makepad_game_gen::/* generate real road */;
let car = Car::new(CarConfig::default());
let settled = settle(car, &road, 120);
let lowest = lowest_vertex_y(&real_glb_body(), settled.transform());
assert!((lowest - road_surface_y).abs() < 0.05, "car floats or sinks: {lowest}");
}
```
## Driven tests
```rust
use makepad_test::{makepad_test, TestApp, KeyCode, Selector};
#[makepad_test]
fn pause_and_resume(app: TestApp) {
app.wait_for_log_contains("game: ready");
app.press_key(KeyCode::Escape);
app.wait_for_log_contains("state: paused");
let paused = app.screenshot();
app.press_key(KeyCode::Escape);
app.wait_for_log_contains("state: playing");
println!("paused shot: {}", paused.display());
}
```
Available on `TestApp`: `type_text`, `press_return`, `press_key`, `press_key_with_modifiers`, `touch_down`, `screenshot`, `widget_dump`, `widget_snapshot`, `wait_for_log_contains`, `locator(Selector)`, `forward(Vec<StudioToApp>)`. Each has a `try_` variant returning `TestResult` for when a failure is expected.
`widget_dump()` prints the tree — use it to discover ids rather than guessing selectors.
## Synchronisation
**Never sleep.** Emit one log line per state transition and wait on it:
```rust
log!("game: ready");
log!("state: paused");
log!("score: {}", score);
log!("race: won");
```
This costs one line per transition and converts the entire game flow into something machine-checkable. It is the highest-value habit in this document.
## Asset-dependent tests
They skip without the library, which is correct for CI and dangerous for reporting.
```rust
fn require_assets() -> Option<AssetIndex> {
let idx = load_index().ok()?;
if idx.is_empty() {
eprintln!("SKIP: run ./apps/arcade/download_assets.sh for real-asset coverage");
return None;
}
Some(idx)
}
```
Check test output for skips before reporting a pass. And assert loading, since a bad id degrades silently:
```rust
assert!(renderer.model_is_loaded("kenney/racing/vehicle-truck-yellow"));
```
## Bot playtest
```rust
#[makepad_test]
fn five_minute_soak(app: TestApp) {
app.wait_for_log_contains("game: ready");
for _ in 0..300 {
drive_one_second(&app);
assert!(!app.widget_dump().contains("panic"));
}
app.wait_for_log_contains("soak: complete");
}
```
Catches what unit tests structurally cannot: softlocks, memory growth, frame-time drift, and incomplete `reset()` (win twice in a row).
## Flakiness
A flaky test is worse than no test — it trains everyone to ignore failures. Causes here: sleeping instead of waiting on a log; asserting exact floats (use an epsilon); depending on iteration order of an unordered container; assuming assets exist; and racing the first frame. Fix the cause; never add a retry.