MORE RE-ORGANIZING
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
---
|
||||
name: flutter_scene-verification-loop
|
||||
version: 2
|
||||
description: Close the visual-iteration loop when building or debugging a flutter_scene 3D app so you see your own output and self-correct. Use whenever a change affects what renders (geometry, materials, lighting, shaders, post-processing) or a frame looks wrong (black, washed-out, see-through, missing geometry).
|
||||
---
|
||||
|
||||
# Verifying flutter_scene visually
|
||||
|
||||
flutter_scene renders 3D. A rendering change you cannot see is a guess, and guessing at pixels is the single biggest waste of iterations. The highest-value habit is a closed visual loop plus judgment that does not drift. This skill is that loop.
|
||||
|
||||
**The one thing to internalize: run it, let it settle, look at the frame AND the console, localize before you edit, repeat.** Do not change code off a hypothesis you did not confirm from the actual output.
|
||||
|
||||
## The loop
|
||||
|
||||
1. **Run.** Launch the app with `flutter run --enable-flutter-gpu` (native; the flag is mandatory and it is the only run flag, see the idioms skill). Where the editor MCP is connected, `run_project` launches the managed session instead.
|
||||
2. **Settle.** A live frame is a moving target. Auto-exposure is still ramping, particles have a random phase, an animation is mid-clip, IBL re-bakes after the first present. Let the scene reach steady state (a few frames, or until the image stops changing) before you trust a capture. Do not screenshot the first frame and reason from it.
|
||||
3. **Capture the frame AND read the console.** A screenshot alone hides errors that print; the console alone hides wrong pixels. Take both every time. Baseline path is a screenshot plus the run log; with the editor MCP it is `screenshot_viewport` plus `get_console`.
|
||||
4. **Localize before editing.** When something is wrong, find where it goes wrong before you touch code. Read an intermediate buffer, read a single pixel's exact value, or scan for non-finite values. A NaN or Inf propagates silently into black or garbage downstream, so the first pass that produced it is the culprit, not the pass where you see the black. `references/loop.md` has the tool table and a symptom to action map.
|
||||
5. **Correct, repeat.** Make one change, run the loop again. One change per iteration keeps cause and effect legible.
|
||||
|
||||
## The readiness gate (do not debug through it)
|
||||
|
||||
Rendering is gated on `Scene.initializeStaticResources()`. Until that Future completes, every frame is skipped and the engine prints exactly:
|
||||
|
||||
```
|
||||
Flutter Scene is not ready to render. Skipping frame.
|
||||
```
|
||||
|
||||
If you see that line, the scene is not broken, it is not ready. Wait for readiness (build geometry and materials inside `initializeStaticResources().then(...)`, gate the widget on `Scene.isReadyToRender`) before you diagnose anything else. A black frame while that line prints is the gate, not your code.
|
||||
|
||||
## Judge blind, never self-score (the load-bearing rule)
|
||||
|
||||
When deciding whether a change improved the look, **do not assign the frame a quality score.** Self-assigned scores drift upward, because the model is grading its own trajectory and wants to have made progress. That drift is how a session convinces itself a regression is an improvement.
|
||||
|
||||
Instead, **compare two frames and return a binary pick.** Put the new frame next to a reference (a known-good target) or the previous frame, and answer only "which of these two is better", A or B. No number, no "8/10", no "looks pretty good now". A blind pairwise pick does not inflate the way a solo score does. This applies to every visual review, including the ones that feel obvious.
|
||||
|
||||
If you have no reference at all, say so and describe the concrete difference between the two frames (this one is brighter here, that one has an artifact there) rather than inventing a score.
|
||||
|
||||
## Empirical verification rules
|
||||
|
||||
These rules prevent false diagnoses, hollow passes, and measurement traps during visual iteration:
|
||||
|
||||
1. **Liveness before ablation.** A negative result is evidence only if the removed or modified term was actually live in the draw pass. Diff pixels for liveness before trusting an ablation.
|
||||
2. **Tools must fail loud on empty measurements.** A tool that measures an empty population, zero pixels, or non-finite data must fail loud rather than returning a default passing number.
|
||||
3. **Population discipline.** Always quote the population window, crop rectangle, brightness threshold, and rendering resolution beside any color or lighting figure.
|
||||
4. **Baselines expire quickly.** Two captures taken hours apart in a changing tree reflect multiple edits; isolate paired A/B captures with temporary snapshots or git worktrees.
|
||||
5. **Look at the raw frame before quoting numbers.** Inspect the actual captured frame before taking numbers off it; metrics can yield valid-looking numbers on corrupt frames.
|
||||
6. **Negative control requirement.** A metric that returns the same score for positive and negative control populations cannot serve as evidence for either.
|
||||
7. **Explanations are hypotheses, not evidence.** Before adopting an explanatory mechanism, identify the specific numerical observation that would differ if the mechanism were false.
|
||||
8. **Attribution must reach the triangle.** When diagnosing geometry defects, trace down to the specific triangle indices and edge lengths rather than stopping at the mesh component.
|
||||
9. **Symmetric domain clamping.** Clamping parametric lookup domains must be verified at both boundaries, and derived slope or heading accessors must clamp both sample points.
|
||||
10. **Multi-band octave tables for tiling.** Two-band ratios like `hf/lf` are blind to regular mid-frequency patterns; inspect multi-octave energy tables to detect periodic tiling.
|
||||
11. **Physical discrimination over naming.** A class-discriminating physical observation (such as missing shadow terminators) outranks code comments or variable names.
|
||||
12. **Grazing light terminator crossing.** Under low-angle grazing lighting, prioritize terminator-crossing fractions over slope RMS to detect normal map over-amplification.
|
||||
13. **Resolution scaling.** High-frequency energy (`hf/lf`) and relative contrast scale with resolution; compare them only at equal pixel resolutions.
|
||||
14. **Paired capture isolation.** Snapshot source files or freeze environment state when capturing before/after pairs so background modifications cannot corrupt the comparison.
|
||||
|
||||
## Be honest about tooling
|
||||
|
||||
The richest observation tooling lives behind the editor MCP (`flutter_scene_mcp`): screenshots, console, NaN scans, render-graph capture, per-pass and per-pixel readback, viewport debug modes. A general-purpose observation server for an arbitrary running game is still being built, so do not assume those tools exist for every project. When the MCP is not connected, the baseline loop is still fully usable: `flutter run --enable-flutter-gpu`, read the console, take a screenshot. Do not claim a capability you cannot reach in the current project.
|
||||
|
||||
## More depth
|
||||
|
||||
- `references/loop.md` for the full editor-MCP tool table, the symptom to action map (black frame, washed-out, see-through, missing geometry), and the settle details.
|
||||
- The `flutter_scene-idioms` skill (`references/traps.md`) for the underlying mistakes each symptom points back to (wrong vertex layout, hand-rolled winding flip, transform-in-place, the blank-frame causes).
|
||||
@@ -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