diff --git a/art/stages/rusty-creek/README.md b/art/stages/rusty-creek/README.md index 8c624b1..792d569 100644 --- a/art/stages/rusty-creek/README.md +++ b/art/stages/rusty-creek/README.md @@ -28,7 +28,7 @@ The same place before the anomaly (clean campus architecture, nature layers iden | 06 | Near rails | [png](images/06-near-rails-left.png) | [png](images/06-near-rails-center.png) | = left | Foreground 1.1, never collides | | 07 | Near foliage | [png](images/07-foliage-left.png) | [png](images/07-foliage-center.png) | [png](images/07-foliage-right.png) | Foreground 1.15 | -"= left": shared scenery repeats on the right chunk and is stored once (alias in [media-sources.json](media-sources.json)). The clean architecture plates are [left](images/clean-architecture-left.png) and [centre](images/clean-architecture-center.png); right = left. +"= left": shared scenery repeats on the right chunk and is stored once (alias in [media-sources.json](media-sources.json)). In the game, layers 01, 06 and 07 come from one region-wide plate each ([layers/](layers/), `python scripts/depth_layers.py region art/stages/rusty-creek`, #150): per chunk they overlapped at 0.2 and left gaps at 1.1 and 1.15. The creek as the game draws it: [parallax.png](previews/parallax.png). The clean architecture plates are [left](images/clean-architecture-left.png) and [centre](images/clean-architecture-center.png); right = left. ## Route diff --git a/art/stages/rusty-creek/export.json b/art/stages/rusty-creek/export.json index 50f1313..4507970 100644 --- a/art/stages/rusty-creek/export.json +++ b/art/stages/rusty-creek/export.json @@ -1,24 +1,92 @@ { - "note": "Draw order follows creek_layers.py compose(): the plates are soft and blend, so the water is drawn last. z names the docs/04 role; loaders draw in list order.", + "note": "Draw order follows creek_layers.py compose(): the plates are soft and blend, so the water is drawn last. z names the docs/04 role; loaders draw in list order. 01-distance (0.2), 06-near-rails (1.1) and 07-foliage (1.15) are region-wide plates in chunk-wide tiles (depth_layers.py region, #150): per chunk they overlapped or left gaps.", "chunks": { - "rc-0-0": {"x": 0, "y": 0, "name": "left"}, - "rc-1-0": {"x": 1, "y": 0, "name": "center"}, - "rc-2-0": {"x": 2, "y": 0, "name": "right"} + "rc-0-0": { + "x": 0, + "y": 0, + "name": "left" + }, + "rc-1-0": { + "x": 1, + "y": 0, + "name": "center" + }, + "rc-2-0": { + "x": 2, + "y": 0, + "name": "right" + } }, "layers": { - "01-distance": {"file": "images/01-distance-{name}.png", "z": 1, "scroll": 0.2}, - "03-bank": {"file": "images/03-bank-{name}.png", "z": 2, "scroll": 1.0}, - "02-supports": {"file": "images/02-supports-{name}.png", "z": 3, "scroll": 1.0}, - "clean-architecture": {"file": "images/clean-architecture-{name}.png", "z": 3, "scroll": 1.0}, - "05-traversal": {"file": "images/05-traversal-{name}.png", "z": 5, "scroll": 1.0}, - "06-near-rails": {"file": "images/06-near-rails-{name}.png", "z": 6, "scroll": 1.1}, - "07-foliage": {"file": "images/07-foliage-{name}.png", "z": 7, "scroll": 1.15}, - "04-water": {"file": "images/04-water-{name}.png", "z": 4, "scroll": 1.0} + "01-distance": { + "file": "layers/rusty-creek.01-distance.{tile}.png", + "z": 1, + "scroll": 0.2, + "region": true, + "rest": "art/stages/rusty-creek/images/01-distance-{name}.png" + }, + "03-bank": { + "file": "images/03-bank-{name}.png", + "z": 2, + "scroll": 1.0 + }, + "02-supports": { + "file": "images/02-supports-{name}.png", + "z": 3, + "scroll": 1.0 + }, + "clean-architecture": { + "file": "images/clean-architecture-{name}.png", + "z": 3, + "scroll": 1.0 + }, + "05-traversal": { + "file": "images/05-traversal-{name}.png", + "z": 5, + "scroll": 1.0 + }, + "06-near-rails": { + "file": "layers/rusty-creek.06-near-rails.{tile}.png", + "z": 6, + "scroll": 1.1, + "region": true, + "rest": "art/stages/rusty-creek/images/06-near-rails-{name}.png" + }, + "07-foliage": { + "file": "layers/rusty-creek.07-foliage.{tile}.png", + "z": 7, + "scroll": 1.15, + "region": true, + "rest": "art/stages/rusty-creek/images/07-foliage-{name}.png" + }, + "04-water": { + "file": "images/04-water-{name}.png", + "z": 4, + "scroll": 1.0 + } }, "states": { - "anomaly": ["01-distance", "03-bank", "02-supports", "05-traversal", "06-near-rails", "07-foliage", "04-water"], - "clean": ["01-distance", "03-bank", "clean-architecture", "06-near-rails", "07-foliage", "04-water"] + "anomaly": [ + "01-distance", + "03-bank", + "02-supports", + "05-traversal", + "06-near-rails", + "07-foliage", + "04-water" + ], + "clean": [ + "01-distance", + "03-bank", + "clean-architecture", + "06-near-rails", + "07-foliage", + "04-water" + ] + }, + "composed": { + "anomaly": "images/after-{name}.png", + "clean": "images/before-{name}.png" }, - "composed": {"anomaly": "images/after-{name}.png", "clean": "images/before-{name}.png"}, "aliases": "media-sources.json" } diff --git a/art/stages/rusty-creek/layers/rusty-creek.01-distance.0.png b/art/stages/rusty-creek/layers/rusty-creek.01-distance.0.png new file mode 100644 index 0000000..663a26b Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.01-distance.0.png differ diff --git a/art/stages/rusty-creek/layers/rusty-creek.01-distance.1.png b/art/stages/rusty-creek/layers/rusty-creek.01-distance.1.png new file mode 100644 index 0000000..7871916 Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.01-distance.1.png differ diff --git a/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.0.png b/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.0.png new file mode 100644 index 0000000..702fea7 Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.0.png differ diff --git a/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.1.png b/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.1.png new file mode 100644 index 0000000..e268fff Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.1.png differ diff --git a/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.2.png b/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.2.png new file mode 100644 index 0000000..5cee465 Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.06-near-rails.2.png differ diff --git a/art/stages/rusty-creek/layers/rusty-creek.07-foliage.0.png b/art/stages/rusty-creek/layers/rusty-creek.07-foliage.0.png new file mode 100644 index 0000000..964b13f Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.07-foliage.0.png differ diff --git a/art/stages/rusty-creek/layers/rusty-creek.07-foliage.1.png b/art/stages/rusty-creek/layers/rusty-creek.07-foliage.1.png new file mode 100644 index 0000000..eff63b8 Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.07-foliage.1.png differ diff --git a/art/stages/rusty-creek/layers/rusty-creek.07-foliage.2.png b/art/stages/rusty-creek/layers/rusty-creek.07-foliage.2.png new file mode 100644 index 0000000..28fd529 Binary files /dev/null and b/art/stages/rusty-creek/layers/rusty-creek.07-foliage.2.png differ diff --git a/art/stages/rusty-creek/previews/parallax.png b/art/stages/rusty-creek/previews/parallax.png new file mode 100644 index 0000000..abee86e Binary files /dev/null and b/art/stages/rusty-creek/previews/parallax.png differ diff --git a/docs/09-ART-PIPELINE.md b/docs/09-ART-PIPELINE.md index 5a5ea47..47fe32e 100644 --- a/docs/09-ART-PIPELINE.md +++ b/docs/09-ART-PIPELINE.md @@ -85,7 +85,7 @@ flowchart LR - **The key only says which band, never which pixel.** It drifts a few pixels at fine edges, so near the sky the real pixel decides: open sky is ≈ (40–60, 120–140, 240) and hazy far treetops ≈ (80, 120, 180), so "sky" means blue above 200. Path, water and foreground keep their exact rectangles and colour rules, which run after the masks and win. - **Fills never change the picture at rest.** A fill is kept only under the nearer layers, and the layer's own pixels are copied from the art, so `--verify` still rebuilds the chunk exactly. The camera only ever reveals painted scenery. -- **Several chunks: one plate per slow layer for the whole region.** Per chunk, plates at scroll *s* < 1 overlap by 2160 × (1 − *s*) and cut each other off with hard vertical edges; at *s* > 1 (the foreground) they spread apart and leave gaps. `depth_layers.py region` quilts each layer marked `"region": true` in `export.json` from the chunks' own plates (`"rest"`): chunk *i* at *i* × 2160 × *s*, each next plate taking over along a min-error path in a 192 px band and only where it has pixels; a foreground gap is filled with the earlier plate's edge mirrored. The first chunk carries the plate, so it lands at x = 0 under any placement rule, and `--verify` still rebuilds every chunk at rest from its own plates. The Hidden Garden's mid plate is 6992 px wide (8192 is the common WebGL limit). +- **Several chunks: one plate per slow layer for the whole region.** Per chunk, plates at scroll *s* < 1 overlap by 2160 × (1 − *s*) and cut each other off with hard vertical edges; at *s* > 1 (the foreground) they spread apart and leave gaps. `depth_layers.py region` quilts each layer marked `"region": true` in `export.json` from the chunks' own plates (`"rest"`): chunk *i* at *i* × 2160 × *s*, each next plate taking over along a min-error path in a 192 px band and only where it has pixels; a foreground gap is filled with the earlier plate's edge mirrored. The first chunk carries the plate, so it lands at x = 0 under any placement rule, and `--verify` still rebuilds every chunk at rest from its own plates. The Hidden Garden's mid plate is 6992 px wide, in four 2160 px tiles. Rusty Creek's distance (0.2), rails (1.1) and foliage (1.15) are quilted the same way ([#150](https://github.com/THD-AI/WinCS/issues/150)); shared plates ("= left") resolve through its aliases. `python scripts/compare.py parallax art/stages/` shows any region as the game draws it. ## Prompting lessons (23 Oct, all runs) diff --git a/scripts/depth_layers.py b/scripts/depth_layers.py index 0ea73a0..d3d1ca2 100644 --- a/scripts/depth_layers.py +++ b/scripts/depth_layers.py @@ -406,11 +406,17 @@ def region_layers(region: Path) -> list[Path]: """Quilt every layer marked "region" in export.json from its chunks' "rest" plates into one plate each.""" export = json.loads((region / "export.json").read_text(encoding="utf-8")) chunks = sorted(export["chunks"].items(), key=lambda kv: kv[1]["x"]) + aliases = json.loads((region / export["aliases"]).read_text(encoding="utf-8")).get("aliases", {}) if export.get("aliases") else {} + + def rest(layer: dict, chunk_id: str, chunk: dict) -> Path: + """A chunk's own plate for a region layer; a shared plate (an alias, "= left") resolves to its file.""" + path = ROOT / layer["rest"].format(name=chunk.get("name", chunk_id)) + return path if path.exists() or path.name not in aliases else path.parent / aliases[path.name] written = [] for layer_id, layer in export["layers"].items(): if not layer.get("region"): continue - plates = [Image.open(ROOT / layer["rest"].format(name=c)).convert("RGBA") for c, _ in chunks] + plates = [Image.open(rest(layer, c, chunk)).convert("RGBA") for c, chunk in chunks] plate = region_plate(plates, layer["scroll"], carry=layer.get("carry", False)) # A preview-free game plate: 256 colours keep it small; --verify checks the chunks' own plates ("rest"). alpha = plate.getchannel("A") @@ -420,6 +426,7 @@ def region_layers(region: Path) -> list[Path]: # factor then shows exactly the plate's column the camera is over, with any loader that places # layers at their chunk's origin - and no file is wider than a chunk. w, n = plates[0].width, len(plates) + (region / "layers").mkdir(exist_ok=True) for old in (region / "layers").glob(f"{region.name}.{layer_id}.*.png"): old.unlink() for j in range(min(n, -(-plate.width // w))): diff --git a/scripts/export_stage.py b/scripts/export_stage.py index 259c08a..98c6891 100644 --- a/scripts/export_stage.py +++ b/scripts/export_stage.py @@ -116,6 +116,12 @@ def verify(region: Path, out: Path) -> list[str]: config = json.loads((region / "export.json").read_text(encoding="utf-8")) stage = json.loads((out / region.name / "stage.json").read_text(encoding="utf-8")) + aliases = json.loads((region / config["aliases"]).read_text(encoding="utf-8")).get("aliases", {}) if config.get("aliases") else {} + + def rest(pattern: str, name: str) -> Path: + path = ROOT / pattern.format(name=name) + return path if path.exists() or path.name not in aliases else path.parent / aliases[path.name] + report = [] for chunk in stage["chunks"]: name = config["chunks"][chunk["id"]].get("name", chunk["id"]) @@ -124,7 +130,7 @@ def verify(region: Path, out: Path) -> list[str]: if not pattern: continue exported = {layer["id"]: out / region.name / layer["file"] for layer in content["layers"]} - rests = {lid: ROOT / L["rest"].format(name=name) for lid, L in config["layers"].items() if L.get("region")} + rests = {lid: rest(L["rest"], name) for lid, L in config["layers"].items() if L.get("region")} if any(not path.exists() for path in rests.values()): report.append(f"{chunk['id']}.{state} (not checked: its region plates' sources are not in tmp/)") continue