# Scenery Art Layer Prep

> Prepare generated or hand-painted scenery art for layered Flutter backdrops: full-frame PNG plates, alpha masks, cloud/wave isolation, OpenCV cleanup, runtime stacking, parallax validation, and visual QA. Use when Codex needs to split a base backdrop into aligned layers, refine masks, make clouds/water independently movable, debug drifting building pixels, or document the asset-prep side of a scenery change.

- Skill: `matthiasn/scenery-art-layer-prep` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add matthiasn/scenery-art-layer-prep`
- Raw SKILL.md: https://api.skillmd.com/api/skills/matthiasn/scenery-art-layer-prep/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: matthiasn (https://skillmd.com/u/matthiasn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/matthiasn/scenery-art-layer-prep

---


# 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

1. **Identify the art contract.** Read `lib/features/scenery/README.md`,
   `tools/scenery_art/README.md`, `lib/features/scenery/model/scenery_assets.dart`,
   and `lib/features/scenery/model/backdrop_scene.dart`.

2. **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.

3. **Build structure layers from masks.** Use full-frame masks for fixed
   occluders such as city, bridge, yacht, palms, foreground deck, or props:

   ```bash
   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.png
   ```

   For 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:

   ```bash
   make -C tools/scenery_art blue-hour
   ```

4. **Extract 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:

   ```bash
   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_work
   ```

   This generates `blue_hour_cloudless.webp`, `clouds_far.webp`,
   `clouds_mid.webp`, and `clouds_near.webp`. It is not part of the `blue-hour`
   make target because the current base plate has no clouds left to extract.

5. **Validate layer order.** Runtime order should usually be:

   ```text
   cloudless base -> moving clouds -> water shader -> fixed city/bridge
   -> fixed yacht -> additive lights -> fixed foreground -> dancers/vignette
   ```

   Fixed structure must be redrawn above moving cloud/water layers so buildings
   and yacht edges do not drift.

6. **Run targeted scenery checks.**

   ```bash
   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-panel` for scoring final rendered art.
- `flutter-shader-validation` for shader compile/runtime issues in scenery.

