Visual: sprinkle grass+flower decoration overlay (ElvGames bundle)

First visual pass: replaces nothing, adds a decoration TileMapLayer above
Terrain that paints ~8% of cells with random overlay sprites pulled from
FG_Grasslands_Spring.png. Mix of 3 grass-sprout variants, 3 white-with-
colored-center flowers, and 2 scattered-grass-dot patches. ~540 cells
populated on the 80x80 map; deterministic seed so layout is stable.

The bundle's design assumes flat-color terrain + overlay sprites for
visual richness — so this is the cheapest possible win that uses the
art we own. Terrain base, walls, trees, pawns, UI all unchanged.

Implementation lives in world.gd as _build_decoration_tileset() +
_paint_decorations(); the Decoration TileMapLayer is added to
world.tscn at z_index=0 (siblings render in tree order so it draws
between Terrain and Floor). Tileset is built at runtime pointing at
res://art/tiles/FG_Grasslands_Spring.png, mirroring the existing
_build_placeholder_tileset pattern.

Verified MCP runtime: world feels like a meadow now, no perf hit.
Headless boot logs '[world] decoration: painted 541 overlay cells'.

License: ElvGames Humble bundle — commercial use OK with credit (see
docs/art.md). Credit string compilation is still an open audit item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
megaproxy 2026-05-12 14:07:36 +01:00
parent f435c3c467
commit 314c7a70b4
4 changed files with 111 additions and 0 deletions

View file

@ -85,6 +85,7 @@ const SEASON_TINTS: Dictionary = {
@onready var dark_overlay: CanvasModulate = $DarkOverlay
@onready var terrain_layer: TileMapLayer = $Terrain
@onready var decoration_layer: TileMapLayer = $Decoration
@onready var floor_layer: TileMapLayer = $Floor
@onready var wall_layer: TileMapLayer = $Wall
@onready var designation_layer: TileMapLayer = $Designation
@ -112,6 +113,11 @@ func _ready() -> void:
for layer in [terrain_layer, floor_layer, wall_layer, designation_layer, roof_layer, fog_layer]:
layer.tile_set = tileset
_paint_terrain()
# Decoration overlay — sparse grass tufts + flowers from the ElvGames bundle.
# Lives on its own TileMapLayer above Terrain so a future season swap is one
# tileset reassignment.
decoration_layer.tile_set = _build_decoration_tileset()
_paint_decorations()
# Phase 5: removed _paint_sample_walls() — the old 8×8 stone ring rendered
# only on the (now-hidden) Wall TileMap layer. With the rendering pivot
# to entity-based walls, that ring was invisible to the player but still
@ -285,6 +291,68 @@ func _paint_terrain() -> void:
terrain_layer.set_cell(Vector2i(x, y), PLACEHOLDER_SOURCE_ID, TILE_GRASS)
# ── decoration overlay (ElvGames Grasslands sprinkle) ────────────────────────
## Source id for the decoration tileset (separate atlas from PLACEHOLDER_SOURCE_ID).
const DECORATION_SOURCE_ID: int = 10
## Atlas-tile coords inside FG_Grasslands_Spring.png for overlay variants.
## Each is a 16×16 sprite on transparent background, picked by visual eyeball
## (see /tmp/deco_review.png in the 2026-05-12 visual-pass session). Mix of
## small-grass sprouts, flowers, and scattered-tuft sprites — all sit cleanly
## on top of the base grass tile without occluding pawns.
const DECORATION_TILES: Array[Vector2i] = [
Vector2i(12, 0), # grass sprout A
Vector2i(13, 0), # grass sprout B
Vector2i(14, 0), # scattered grass dots — small
Vector2i(16, 0), # white flower / red center
Vector2i(17, 0), # white flower / yellow center
Vector2i(18, 0), # white flower / purple center
Vector2i(16, 2), # scattered grass dots — medium
Vector2i(17, 2), # scattered grass dots — large
]
## How often a tile gets a decoration. 0.08 = ~8% density — visually rich but
## not so dense that pawns/items get hidden under it. Tunable; tested at
## ~500 decorations on the 80×80 map.
const DECORATION_DENSITY: float = 0.08
func _build_decoration_tileset() -> TileSet:
var ts := TileSet.new()
ts.tile_size = Vector2i(TILE_SIZE_PX, TILE_SIZE_PX)
var tex: Texture2D = load("res://art/tiles/FG_Grasslands_Spring.png")
if tex == null:
Audit.log("world", "decoration: FG_Grasslands_Spring.png not loadable; skipping overlay")
return ts
var src := TileSetAtlasSource.new()
src.texture = tex
src.texture_region_size = Vector2i(TILE_SIZE_PX, TILE_SIZE_PX)
for coord in DECORATION_TILES:
src.create_tile(coord)
ts.add_source(src, DECORATION_SOURCE_ID)
return ts
func _paint_decorations() -> void:
# Deterministic-ish seed so the layout is stable across runs (cosmetic only;
# not save-round-tripped). Mix world size into seed for "feels random but
# reproducible." Use a fresh RNG so we don't perturb the global state.
var rng := RandomNumberGenerator.new()
rng.seed = MAP_SIZE_TILES.x * 31 + MAP_SIZE_TILES.y * 17
var placed: int = 0
for x in MAP_SIZE_TILES.x:
for y in MAP_SIZE_TILES.y:
if rng.randf() >= DECORATION_DENSITY:
continue
var coord: Vector2i = DECORATION_TILES[rng.randi() % DECORATION_TILES.size()]
decoration_layer.set_cell(Vector2i(x, y), DECORATION_SOURCE_ID, coord)
placed += 1
Audit.log("world", "decoration: painted %d overlay cells" % placed)
func _paint_sample_walls() -> void:
var origin := Vector2i(36, 36)
var size: int = 8

View file

@ -29,6 +29,9 @@ color = Color(1, 1, 1, 1)
[node name="Terrain" type="TileMapLayer" parent="."]
z_index = 0
[node name="Decoration" type="TileMapLayer" parent="."]
z_index = 0
[node name="Floor" type="TileMapLayer" parent="."]
z_index = 1
visible = false