Scenery Art Layer Prep
Turns one good full-frame scene plate into stackable runtime assets without
coordinate drift. The current repo pipeline lives in tools/scenery_art/ and
feeds lib/features/scenery/.
Rules
- Keep one native coordinate space. Current scenery art is
2560x1440. - Do not crop runtime layers. Every output plate must be full-frame and line up by pixel coordinate alone.
- Treat the master plate as immutable. Generated assets are derived from it.
- Use alpha plates for movable/occluding elements. Avoid separate positioning metadata unless the runtime already has it.
- Visual QA before claiming success. Inspect the generated stack and at least one motion preview.
- Full restart the Flutter app after regenerating PNG assets. Hot reload can keep cached image bytes.
Main Workflow
Identify the art contract. Read
lib/features/scenery/README.md,tools/scenery_art/README.md,lib/features/scenery/model/scenery_assets.dart, andlib/features/scenery/model/backdrop_scene.dart.Preserve or create the base plate. If creating new art with a generative image model, make one high-quality full-frame base first. Do not try to stitch unrelated crops. If editing an existing scene, keep the exact canvas size.
Build structure layers from masks. Use full-frame masks for fixed occluders such as city, bridge, yacht, palms, foreground deck, or props:
python3 tools/scenery_art/layer_from_masks.py \ --master assets/scenery/blue_hour_cloudless.webp \ --out-dir assets/scenery \ --preview-dir tmp/scenery_work \ --layer city_bridge=tools/scenery_art/scenes/blue_hour_waterfront/masks/city_bridge.png \ --layer yacht=tools/scenery_art/scenes/blue_hour_waterfront/masks/yacht.png \ --layer foreground=tools/scenery_art/scenes/blue_hour_waterfront/masks/foreground.pngFor the current blue-hour scene the base plate (
assets/scenery/blue_hour_cloudless.webp) is already cloudless and already has the foreground-duplicated elements (e.g. palms) baked out of it, so this is the only regeneration step needed; run it with:make -C tools/scenery_art blue-hourExtract moving atmosphere — only for a NEW scene. If starting a fresh plate that still has clouds painted in, use OpenCV to split it into a cloudless base plus movable cloud layers:
python3 -m venv /tmp/lotti-scenery-opencv /tmp/lotti-scenery-opencv/bin/python -m pip install -r tools/scenery_art/requirements.txt /tmp/lotti-scenery-opencv/bin/python tools/scenery_art/isolate_clouds.py \ --master <new-scene-master>.png \ --city-bridge assets/scenery/city_bridge.webp \ --yacht assets/scenery/yacht.webp \ --foreground assets/scenery/foreground.webp \ --out-dir assets/scenery \ --preview-dir tmp/scenery_workThis generates
blue_hour_cloudless.webp,clouds_far.webp,clouds_mid.webp, andclouds_near.webp. It is not part of theblue-hourmake target because the current base plate has no clouds left to extract.Validate layer order. Runtime order should usually be:
cloudless base -> moving clouds -> water shader -> fixed city/bridge -> fixed yacht -> additive lights -> fixed foreground -> dancers/vignetteFixed structure must be redrawn above moving cloud/water layers so buildings and yacht edges do not drift.
Run targeted scenery checks.
fvm flutter analyze lib/features/scenery lib/main.dart fvm flutter test test/features/scenery/runtime/scenery_shaders_test.dart \ test/features/scenery/layers/cloud_parallax_layer_test.dart \ test/features/scenery/model/backdrop_scene_test.dart \ test/features/scenery/scenery_assets_test.dart
Visual QA Checklist
Inspect these after regeneration:
tmp/scenery_work/cloud_mask_preview.png: no obvious city, yacht, deck, or palm pixels should be marked as moving cloud.tmp/scenery_work/blue_hour_cloudless.png: no blocky scars in open sky.tmp/scenery_work/clouds_recomposed.png: close to the master before motion.- Runtime or offline preview at two distant times: bright clouds should move clearly; darker cloud bodies should move subtly; skyline chunks should not move.
Common Failures
- Buildings move with clouds: the cloud mask or repaired cloud source still contains structure pixels. Tighten structure exclusions or repair RGB under occluders.
- Clouds have building-shaped holes: alpha was cut out under occluders but not repaired near the cloud mass.
- Dark clouds look static: too much of the body remains baked into the base, or far-layer opacity/speed is too low.
- Open sky has ugly inpaint scars: the erase/stencil mask is too broad. Keep broad dark bodies as low-alpha overlays and inpaint only confident highlights.
- Runtime still shows old assets: restart the app; hot reload can cache PNGs.
See Also
cinematic-render-panelfor scoring final rendered art.flutter-shader-validationfor shader compile/runtime issues in scenery.