init
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# The verification loop in detail
|
||||
|
||||
The core loop, the readiness gate, and the blind-judgment rule are in `SKILL.md`. This file has the
|
||||
tool table (what exists where), the settle details, and the symptom to action map.
|
||||
|
||||
---
|
||||
|
||||
## Two tooling tiers
|
||||
|
||||
### Baseline (any project, no MCP)
|
||||
|
||||
This always works and needs nothing installed beyond the package setup.
|
||||
|
||||
- Launch: `flutter run --enable-flutter-gpu` (native; add `-d chrome` for web). The flag is mandatory.
|
||||
- Console: read the run log. The readiness line, `debugPrint` output, asserts, and the 0.22.0
|
||||
blank-frame diagnostic all land here.
|
||||
- Frame: take a screenshot of the running app after it settles.
|
||||
|
||||
That is the whole loop when there is no editor. Run, settle, screenshot, read the log, correct.
|
||||
|
||||
### Editor MCP (`flutter_scene_mcp`, when connected)
|
||||
|
||||
The editor exposes richer observation. Tool names below are exact. Do not assume they exist unless
|
||||
the MCP is actually connected for the current project.
|
||||
|
||||
| Tool | What it does | Reach for it when |
|
||||
| --- | --- | --- |
|
||||
| `run_project` | Launch the editor-managed Play session (a managed `flutter run`). | Starting a session under the editor. |
|
||||
| `build_project` | Start the selected build config; output streams to the console. | You want a build without launching. |
|
||||
| `stop_project` | Stop the running session. | Ending or restarting cleanly. |
|
||||
| `hot_reload` | Hot reload the running debug session. | A Dart-only change, fastest turnaround. |
|
||||
| `hot_restart` | Hot restart the session. | State or startup changed, or reload did not take. |
|
||||
| `get_console` | The build/run console tail plus building/running flags. | EVERY iteration, paired with a screenshot. |
|
||||
| `screenshot_viewport` | The viewport as a PNG, what the user sees. | EVERY iteration, paired with the console. |
|
||||
| `describe_scene` | The scene-graph tree (ids, paths, names, component types). | Confirming a node/mesh is actually in the scene. |
|
||||
| `scan_for_nans` | Capture a frame and scan every float render target for NaN/Inf in pass order. | A black or garbage frame with no error. Find where non-finite values start. |
|
||||
| `capture_render_graph` | Capture the next frame's graph with thumbnails. | You need to see intermediate buffers. |
|
||||
| `list_render_passes` | The executed passes in order with CPU timings and the buffer keys each read/wrote, plus target formats and sizes. No images. | Learning which pass owns which buffer, and the key names to read. |
|
||||
| `get_pass_output` | Render one captured buffer (a key like `scene_color`, `linear_depth`) as a PNG. NaN paints magenta, Inf yellow, negative blue. | Eyeballing an intermediate buffer to see which stage broke. |
|
||||
| `read_pass_pixel` | One pixel's exact float RGBA from a captured buffer, with NaN/Inf flags. | Confirming an exact value (is this really 0, or NaN, or negative). |
|
||||
| `list_viewport_debug_modes` | The available debug outputs (final, HDR color, linear depth, normals, AO, shadow atlas, ...) and which is active. | Seeing what debug views exist. |
|
||||
| `set_viewport_debug_mode` | Render one debug output full-viewport. Set `final` to restore. | Inspecting depth/normals/AO live, paired with `screenshot_viewport`. |
|
||||
|
||||
Render-graph capture (`capture_render_graph`, `list_render_passes`, `get_pass_output`,
|
||||
`read_pass_pixel`, `scan_for_nans`) is gated on `Scene.debugAllowRenderGraphCapture`. It is a debug
|
||||
opt-in, so a release build or a scene that never armed it returns nothing. The editor arms it for you;
|
||||
outside the editor, set `Scene.debugAllowRenderGraphCapture = true` and call
|
||||
`Scene.captureRenderGraph(...)` directly.
|
||||
|
||||
---
|
||||
|
||||
## Settle, do not seed
|
||||
|
||||
Frames differ from run to run for benign reasons. That is normal, not a bug to eliminate.
|
||||
|
||||
- **Auto-exposure** (`Scene.autoExposure`) ramps toward the target over `speedUp`/`speedDown` seconds,
|
||||
so the first second is darker or brighter than the settled image.
|
||||
- **Particles and trails** carry a random phase, so a `ParticleSystem` looks different every launch.
|
||||
- **Animations** are mid-clip unless you seek them, so a screenshot lands on an arbitrary frame.
|
||||
- **Image-based lighting** re-bakes after the first present on some paths, so reflections dim in for
|
||||
a frame before they are correct.
|
||||
|
||||
So let the scene settle before you trust a capture. Watch until the image stops changing, or advance
|
||||
a fixed few frames, then screenshot. Judge the settled frame, not the first one.
|
||||
|
||||
**Seeding is a different job.** Strict determinism (a fixed random seed, a pinned animation time, a
|
||||
frozen exposure) is what you set up for pixel-exact regression comparison, where two runs must be
|
||||
byte-identical. You do not need it for ordinary observation. For "does this change look right", settle
|
||||
and look. Reserve the seeding work for when you are building a golden or diffing two runs at the pixel
|
||||
level.
|
||||
|
||||
---
|
||||
|
||||
## Symptom to action map
|
||||
|
||||
Localize before editing. Each row says what to capture first and the mistakes it usually points back
|
||||
to. The mistakes are detailed in the `flutter_scene-idioms` skill's `references/traps.md`; this map
|
||||
routes a symptom to the right one.
|
||||
|
||||
### Entirely black frame
|
||||
|
||||
1. Read the console FIRST. If `Flutter Scene is not ready to render. Skipping frame.` is printing, it
|
||||
is the readiness gate, not your scene. Wait for `Scene.initializeStaticResources()`. Stop here.
|
||||
2. In 0.22.0 a frame that issues zero draws prints once in debug naming the likely cause (not ready,
|
||||
empty region, no views, no visible meshes, or a layer mask matching nothing). Read that line.
|
||||
3. If draws are happening but the image is black, `scan_for_nans`. A NaN or Inf anywhere upstream
|
||||
collapses the final image to black, and the scan names the first offending pass. Then
|
||||
`get_pass_output` on that pass's buffer (NaN shows magenta) to confirm.
|
||||
4. Common non-NaN causes: a degenerate camera (target equals position, `up` parallel to the view
|
||||
direction on a top-down camera, FOV passed in degrees not radians), `layerMask: 0`, an oversized
|
||||
environment texture that failed to allocate on the device. See traps #23 and #16.
|
||||
|
||||
### Washed-out, low-contrast, or too-bright color
|
||||
|
||||
1. `screenshot_viewport` after settling, and check whether auto-exposure has finished ramping (a
|
||||
too-bright first second is just the ramp).
|
||||
2. If it persists, suspect a shader-output contract break. A custom `ShaderMaterial`/`PostEffect`/sky
|
||||
shader must output linear HDR premultiplied by alpha. Tone-mapping or gamma-encoding in the shader
|
||||
gets applied a second time by the resolve pass, giving exactly this washed-out look. See traps #37
|
||||
and the root `MATERIALS.md`.
|
||||
3. Also check for a non-color texture bound as color (a normal or metallic-roughness map without the
|
||||
right `TextureContent`), which reads wrong and distance-dependent. Trap #2.
|
||||
4. Hand-packed vertex data at the wrong stride also washes out color (the color attribute lands at the
|
||||
wrong offset). `describe_scene` plus trap #17.
|
||||
|
||||
### See-through or inside-out faces
|
||||
|
||||
1. `set_viewport_debug_mode` to normals (or `get_pass_output` on the normals buffer) and look at the
|
||||
orientation. Inverted normals confirm a winding problem.
|
||||
2. Cause is almost always clockwise hand-built triangles. flutter_scene front faces wind
|
||||
COUNTER-CLOCKWISE (CCW) in model space, matching glTF and standard conventions. Ensure triangle
|
||||
indices wind CCW around the outward face normal, or omit normals and let the constructor derive
|
||||
them. NEVER fix orientation with a per-triangle winding flip on an imported model; that leaves
|
||||
normals and IBL wrong. Traps #13 and #17.
|
||||
3. For an imported model rendered mirrored, check you did not overwrite the runtime importer's
|
||||
`scale(1, 1, -1)` handedness root. Trap #5.
|
||||
|
||||
### Missing or popping geometry
|
||||
|
||||
1. `describe_scene` to confirm the node is actually in the graph. If it is absent, it is a scene-build
|
||||
bug, not a render bug.
|
||||
2. If it is present but invisible, check the layer mask (`Node.layers` is a bitmask, NOT inherited,
|
||||
and must match the view's `layerMask`; `layers = 2` means `1 << 1`, not "layer 2"). Trap #10.
|
||||
3. If it appears and disappears with camera angle, the bounds do not cover the geometry (a
|
||||
caller-supplied `bounds` or `setLocalBounds` that is too small, or a swapped primitive geometry on
|
||||
an older version). Widen or omit the bounds. Traps #8 and #24.
|
||||
4. A moved skinned mesh that will not move is the skinned-node transform being ignored by design; move
|
||||
the skeleton root instead. Trap #4.
|
||||
|
||||
### A value looks numerically wrong (not visually)
|
||||
|
||||
Use `read_pass_pixel` on the relevant buffer to read the exact float RGBA at a coordinate, with NaN/Inf
|
||||
flags. This settles "is this pixel actually 0.5, or is it NaN, or negative" without eyeballing a PNG
|
||||
that the display remap has already clamped.
|
||||
|
||||
---
|
||||
|
||||
## Judgment, restated
|
||||
|
||||
The blind-pairwise rule from `SKILL.md` is the part most likely to be skipped, so it bears repeating
|
||||
here. When you have a before and an after, put them side by side and pick the better one as a binary
|
||||
A-or-B choice against a reference or the previous frame. Do not narrate a score. A solo score climbs
|
||||
on its own because you are grading your own progress; a blind pick between two concrete frames does
|
||||
not. Every visual review runs through a pairwise pick, including the ones that feel too obvious to
|
||||
bother with.
|
||||
Reference in New Issue
Block a user