feat(@projects/@magic-civilization): 🎨 single-source biome_colors.json + DataLoader.get_biome_color (p2-87 phase 1a)

Establish ONE data source for biome render colour, lifting hex_renderer.gd's
authoritative 69-entry palette (value-preserving, biome_id -> [r,g,b] 0-255).

- public/games/age-of-dwarves/data/biome_colors.json — the single source.
- DataLoader: _load_biome_colors() at theme load + get_biome_color(biome_id)
  with '_default' fallback then magenta sentinel.

Additive only — no consumer rerouted yet (next phases: hex_renderer, minimap,
proof scenes all read this + delete their hardcoded TERRAIN_COLORS dicts).
Verified: headless load clean, biome_colors.json parses, 0 script errors.
(data_loader.gd max-file-lines is pre-existing, tracked by p2-10k.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Natalie 2026-06-18 23:44:50 -05:00
parent 8fd3ef4ee3
commit 16bfb2ad31
2 changed files with 381 additions and 0 deletions

View file

@ -0,0 +1,350 @@
{
"_doc": "SINGLE SOURCE for biome render colour (biome_id -> [r,g,b] 0-255). Lifted value-preserving from hex_renderer.gd (p2-87). Consumed by hex_renderer + minimap + terrain proof scenes via DataLoader.get_biome_color(). '_default' is the fallback for unmapped biomes.",
"colors": {
"grassland": [
115,
173,
76
],
"temperate_grassland": [
140,
184,
76
],
"plains": [
184,
166,
97
],
"chaparral": [
158,
143,
89
],
"savanna": [
199,
184,
107
],
"steppe": [
166,
158,
115
],
"land": [
128,
153,
97
],
"forest": [
56,
128,
56
],
"temperate_forest": [
46,
122,
56
],
"taiga": [
76,
115,
89
],
"boreal_forest": [
38,
89,
71
],
"jungle": [
38,
107,
38
],
"tropical_rainforest": [
15,
97,
38
],
"tropical_dry_forest": [
107,
128,
56
],
"temperate_rainforest": [
43,
97,
64
],
"enchanted_forest": [
112,
64,
171
],
"montane_forest": [
41,
92,
61
],
"cloud_forest": [
51,
112,
87
],
"mangrove": [
31,
89,
107
],
"desert": [
224,
209,
140
],
"badlands": [
184,
107,
71
],
"dust_plain": [
184,
120,
74
],
"dune_field": [
204,
140,
89
],
"canyon": [
158,
76,
41
],
"ancient_lakebed": [
140,
115,
82
],
"hills": [
148,
133,
97
],
"mountain": [
140,
128,
122
],
"mountains": [
115,
107,
122
],
"highland": [
158,
143,
112
],
"alpine_meadow": [
128,
153,
122
],
"alpine_tundra": [
158,
166,
143
],
"peak": [
199,
199,
209
],
"basalt_highland": [
76,
64,
56
],
"volcanic": [
89,
51,
46
],
"volcano": [
122,
51,
41
],
"volcanic_plains": [
64,
46,
36
],
"lava_field": [
204,
41,
10
],
"caldera": [
115,
26,
20
],
"marsh": [
102,
133,
89
],
"swamp": [
51,
71,
31
],
"bog": [
97,
82,
46
],
"wetland": [
61,
82,
41
],
"oasis": [
89,
158,
122
],
"tundra": [
191,
199,
204
],
"snow": [
235,
240,
245
],
"ice": [
217,
230,
242
],
"permanent_ice": [
199,
219,
232
],
"glacial": [
230,
237,
247
],
"sea_ice": [
191,
217,
242
],
"polar_desert": [
184,
186,
173
],
"subterranean": [
82,
61,
46
],
"cave": [
46,
41,
33
],
"ocean": [
38,
76,
140
],
"deep_ocean": [
20,
46,
102
],
"coast": [
64,
122,
166
],
"shallow_ocean": [
46,
97,
184
],
"lake": [
56,
107,
158
],
"inland_sea": [
46,
94,
143
],
"river": [
64,
122,
204
],
"pond": [
97,
158,
209
],
"estuary": [
64,
115,
133
],
"coral_reef": [
38,
158,
148
],
"deep_water": [
15,
26,
64
],
"shallow_water": [
56,
122,
204
],
"lake_bed": [
61,
97,
117
],
"lowland": [
140,
184,
84
],
"midland": [
122,
158,
82
],
"mana_node": [
138,
89,
173
],
"_default": [
122,
122,
122
]
}
}

View file

@ -58,6 +58,8 @@ var _data: Dictionary = {}
var _raw: Dictionary = {}
var _ecology: DataLoaderEcologyScript = DataLoaderEcologyScript.new()
var _worlds: DataLoaderWorldsScript = DataLoaderWorldsScript.new()
## Single-source biome render colours (p2-87): biome_id -> [r,g,b] 0-255.
var _biome_colors: Dictionary = {}
func _ready() -> void:
for category: String in DATA_CATEGORIES:
@ -74,6 +76,7 @@ func load_theme(theme_id: String) -> void:
_load_from_base("res://public/resources", _RESOURCES_DIR_MAP)
_load_from_base("res://public/games/%s/data" % theme_id, _WORLD_DIR_MAP)
_apply_subscription_manifest(theme_id)
_load_biome_colors(theme_id)
_ecology.deserialize(_raw)
BiomeRegistry.rebuild_from_data()
_validate_unit_actions()
@ -310,6 +313,34 @@ func get_data(category: String) -> Dictionary:
func get_terrain(id: String) -> Dictionary:
return _get_entry("terrain", id)
## Single-source biome render colour (p2-87). Reads biome_colors.json
## (biome_id -> [r,g,b] 0-255), falling back to the '_default' entry, then to
## opaque magenta if even that is missing (visible error, never silent black).
func get_biome_color(biome_id: String) -> Color:
var rgb: Array = _biome_colors.get(biome_id, _biome_colors.get("_default", [])) as Array
if rgb != null and rgb.size() >= 3:
return Color(float(rgb[0]) / 255.0, float(rgb[1]) / 255.0, float(rgb[2]) / 255.0)
return Color(1.0, 0.0, 1.0)
func _load_biome_colors(theme_id: String) -> void:
_biome_colors = {}
var path: String = "res://public/games/%s/data/biome_colors.json" % theme_id
if not FileAccess.file_exists(path):
push_warning("DataLoader: biome_colors.json not found at %s" % path)
return
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
if file == null:
return
var json: JSON = JSON.new()
var err: Error = json.parse(file.get_as_text())
file.close()
if err != OK:
push_error("DataLoader: biome_colors.json parse error: %s" % json.get_error_message())
return
if json.data is Dictionary and (json.data as Dictionary).has("colors"):
_biome_colors = (json.data as Dictionary)["colors"] as Dictionary
func get_unit(id: String) -> Dictionary:
return _get_entry("units", id)