diff --git a/art/tiles/FG_Grasslands_Spring.png b/art/tiles/FG_Grasslands_Spring.png new file mode 100644 index 0000000..238769a Binary files /dev/null and b/art/tiles/FG_Grasslands_Spring.png differ diff --git a/art/tiles/FG_Grasslands_Spring.png.import b/art/tiles/FG_Grasslands_Spring.png.import new file mode 100644 index 0000000..de31416 --- /dev/null +++ b/art/tiles/FG_Grasslands_Spring.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://25dl87qxv8ks" +path="res://.godot/imported/FG_Grasslands_Spring.png-e9676b030c6767775ad374c0db805fa9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/tiles/FG_Grasslands_Spring.png" +dest_files=["res://.godot/imported/FG_Grasslands_Spring.png-e9676b030c6767775ad374c0db805fa9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/scenes/world/world.gd b/scenes/world/world.gd index a9ed397..80589b5 100644 --- a/scenes/world/world.gd +++ b/scenes/world/world.gd @@ -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 diff --git a/scenes/world/world.tscn b/scenes/world/world.tscn index e3f4e70..3dac07c 100644 --- a/scenes/world/world.tscn +++ b/scenes/world/world.tscn @@ -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