feat(age-dwarves): Introduce new terrain blend configurations and resource definitions for expanded game content

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-29 13:13:23 -07:00
parent 9c4d635df3
commit 4cfedd96aa
11 changed files with 426 additions and 5 deletions

View file

@ -0,0 +1,21 @@
[
{
"id": "bear_pelt",
"category": "luxury",
"name": "Bear Pelt",
"description": "Thick winter pelts harvested from cave-bear populations. Cold-weather garment, status symbol in clan halls. Supply tracks live cave-bear population — overhunting collapses the resource for decades.",
"happiness_per_unique_copy": 4,
"source_fauna": ["cave_bear"],
"min_population": 50,
"harvest_rate": 0.05,
"tier": 2,
"sprite": "sprites/resources/bear_pelt.png",
"encyclopedia": {
"category": "luxury",
"entry_type": "fauna_product",
"detail_route": "/economy/luxuries",
"tags": ["fauna", "luxury", "cold_weather"]
},
"flavor": "Worn by chieftains; the thicker the pelt, the older the bear, the prouder the hunter."
}
]

View file

@ -0,0 +1,21 @@
[
{
"id": "harpy_feather",
"category": "luxury",
"name": "Harpy Feather",
"description": "Iridescent flight feathers harvested from harpy roosts. Used in fletching, ceremonial cloaks, and clan banners. Population recovers slowly — heavy harvesting collapses the supply for generations.",
"happiness_per_unique_copy": 4,
"source_fauna": ["harpy"],
"min_population": 40,
"harvest_rate": 0.05,
"tier": 3,
"sprite": "sprites/resources/harpy_feather.png",
"encyclopedia": {
"category": "luxury",
"entry_type": "fauna_product",
"detail_route": "/economy/luxuries",
"tags": ["fauna", "luxury", "fletching"]
},
"flavor": "Banners of harpy plume catch the wind and the eye in equal measure."
}
]

View file

@ -0,0 +1,21 @@
[
{
"id": "spider_silk",
"category": "luxury",
"name": "Spider Silk",
"description": "Spun threads harvested from giant cave-spider broods. Stronger than steel by weight; woven into ceremonial robes and bowstrings. Supply tied to spider density in tunnel biomes.",
"happiness_per_unique_copy": 4,
"source_fauna": ["giant_spider"],
"min_population": 80,
"harvest_rate": 0.06,
"tier": 3,
"sprite": "sprites/resources/spider_silk.png",
"encyclopedia": {
"category": "luxury",
"entry_type": "fauna_product",
"detail_route": "/economy/luxuries",
"tags": ["fauna", "luxury", "textile"]
},
"flavor": "A robe of spider silk weighs less than a feather and turns a dirk blade."
}
]

View file

@ -0,0 +1,21 @@
[
{
"id": "troll_bone",
"category": "luxury",
"name": "Troll Bone",
"description": "Dense load-bearing bones recovered from troll kills. Worked into haft cores and ceremonial inlays — only the boldest hunters bring back enough to trade. Supply is gated by local troll population.",
"happiness_per_unique_copy": 4,
"source_fauna": ["mountain_troll"],
"min_population": 30,
"harvest_rate": 0.04,
"tier": 3,
"sprite": "sprites/resources/troll_bone.png",
"encyclopedia": {
"category": "luxury",
"entry_type": "fauna_product",
"detail_route": "/economy/luxuries",
"tags": ["fauna", "luxury", "craft"]
},
"flavor": "Each haft is twice the price of the iron it carries. Trolls do not give up their bones easily."
}
]

View file

@ -0,0 +1,21 @@
[
{
"id": "wyvern_scale",
"category": "luxury",
"name": "Wyvern Scale",
"description": "Iridescent scales shed by living wyverns or harvested from kills. Lightweight, blade-resistant; prized by armoursmiths. Rarity drives the luxury bonus — wyvern populations are slow to recover.",
"happiness_per_unique_copy": 4,
"source_fauna": ["wyvern"],
"min_population": 20,
"harvest_rate": 0.03,
"tier": 4,
"sprite": "sprites/resources/wyvern_scale.png",
"encyclopedia": {
"category": "luxury",
"entry_type": "fauna_product",
"detail_route": "/economy/luxuries",
"tags": ["fauna", "luxury", "armorcraft"]
},
"flavor": "Scaled mail laced with dragonkin shed catches the lamplight like running water."
}
]

View file

@ -32,6 +32,15 @@
"items": { "type": "string", "minLength": 1 },
"minItems": 1
}
},
"defeat_pool": {
"type": "object",
"description": "Per-victory-condition defeat track pool. Mirrors `victory_pool`: when an AI wins by victory_type X, the human hears a defeat track keyed to *how* they were beaten. AudioManager picks at random when multiple track IDs are listed. Falls back to the generic `defeat` track when the victory_type is unmapped or the elimination didn't carry one (e.g. last-unit-destroyed).",
"additionalProperties": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"minItems": 1
}
}
}
}

View file

@ -22,7 +22,18 @@
"era": { "type": ["integer", "null"], "minimum": 1, "maximum": 10 },
"effects": {
"oneOf": [
{ "type": "array", "items": { "type": "object", "required": ["type", "value"], "properties": { "type": { "type": "string" }, "value": { "type": "number" } } } },
{
"type": "array",
"items": {
"type": "object",
"required": ["type"],
"properties": {
"type": { "type": "string" },
"value": { "type": ["number", "boolean", "string"] }
},
"additionalProperties": true
}
},
{ "type": "object" }
]
},

View file

@ -8,9 +8,9 @@
"id": { "type": "string", "minLength": 1 },
"name": { "type": "string", "minLength": 1 },
"description": { "type": "string" },
"unit_type": { "type": "string", "enum": ["military", "civilian", "support", "wild", "npc"] },
"unit_type": { "type": "string", "enum": ["military", "civilian", "support", "siege", "wild", "npc"] },
"school": { "type": ["string", "null"] },
"domain": { "type": "string", "enum": ["land", "sea", "air"] },
"domain": { "type": "string", "enum": ["land", "sea", "naval", "air"] },
"attributes": { "type": "array", "items": { "type": "string" } },
"attack_type": { "type": "string" },
"tier": { "type": ["integer", "null"], "minimum": 1, "maximum": 10 },
@ -33,7 +33,7 @@
"flags": { "type": "array", "items": { "type": "string" } },
"mana_cost": { "type": ["number", "null"] },
"sprite": { "type": "string" },
"gender": { "type": "object" },
"gender": { "type": ["object", "null"] },
"terrain_bonus": { "type": "object" },
"loot_table": { "type": "object" },
"encyclopedia": { "type": "object" }

View file

@ -0,0 +1,275 @@
{
"_doc": "Edge-blend ecotone terrains. These terrain ids appear at hex edges where two distinct centre-tile biomes meet (HEX_GEOMETRY.md §8). They are looked up via terrain_blends.json. Stat values interpolate between the two parent terrains.",
"terrains": [
{
"id": "foothills",
"name": "Foothills",
"movement_cost": 2,
"defense_bonus": 100,
"food": 0,
"production": 1,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.10,
"flags": ["edge_blend"],
"color": [180, 175, 145],
"sprite": "sprites/terrain/foothills.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "temperate",
"quality_bonuses": { "production": 1 },
"terrain_power": {},
"albedo": 0.30,
"evapotranspiration": 0.003,
"description": "Rolling foothills at the base of mountains."
},
{
"id": "shore",
"name": "Shore",
"movement_cost": 1,
"defense_bonus": 0,
"food": 2,
"production": 0,
"trade": 1,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.05,
"flags": ["edge_blend", "coastal"],
"color": [200, 200, 160],
"sprite": "sprites/terrain/shore.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "temperate",
"quality_bonuses": { "food": 1 },
"terrain_power": {},
"albedo": 0.18,
"evapotranspiration": 0.004,
"description": "Where land meets water — sandy beach and tidal flats."
},
{
"id": "grass_fringe",
"name": "Grass Fringe",
"movement_cost": 1,
"defense_bonus": 25,
"food": 1,
"production": 1,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.30,
"flags": ["edge_blend"],
"color": [120, 175, 75],
"sprite": "sprites/terrain/grass_fringe.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "temperate",
"quality_bonuses": {},
"terrain_power": {},
"albedo": 0.20,
"evapotranspiration": 0.005,
"description": "Open grassland yielding to scattered trees."
},
{
"id": "wooded_foothills",
"name": "Wooded Foothills",
"movement_cost": 2,
"defense_bonus": 125,
"food": 1,
"production": 1,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.35,
"flags": ["edge_blend"],
"color": [110, 145, 110],
"sprite": "sprites/terrain/wooded_foothills.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "temperate",
"quality_bonuses": { "production": 1 },
"terrain_power": {},
"albedo": 0.18,
"evapotranspiration": 0.006,
"description": "Steep slopes covered in old forest."
},
{
"id": "riverside_forest",
"name": "Riverside Forest",
"movement_cost": 2,
"defense_bonus": 50,
"food": 2,
"production": 1,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.45,
"flags": ["edge_blend"],
"color": [70, 140, 100],
"sprite": "sprites/terrain/riverside_forest.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "temperate",
"quality_bonuses": { "food": 1 },
"terrain_power": {},
"albedo": 0.12,
"evapotranspiration": 0.010,
"description": "Lush forest along the water's edge."
},
{
"id": "cliff",
"name": "Cliff",
"movement_cost": 4,
"defense_bonus": 250,
"food": 0,
"production": 1,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.0,
"flags": ["edge_blend", "impassable_civilian"],
"color": [120, 130, 165],
"sprite": "sprites/terrain/cliff.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "elevated",
"quality_bonuses": {},
"terrain_power": {},
"albedo": 0.40,
"evapotranspiration": 0.001,
"description": "Sheer rock face dropping to the water."
},
{
"id": "scrub_edge",
"name": "Scrub Edge",
"movement_cost": 1,
"defense_bonus": 25,
"food": 0,
"production": 1,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.15,
"flags": ["edge_blend"],
"color": [175, 165, 95],
"sprite": "sprites/terrain/scrub_edge.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "hot",
"quality_bonuses": {},
"terrain_power": {},
"albedo": 0.30,
"evapotranspiration": 0.002,
"description": "Hardy thicket between forest and desert."
},
{
"id": "arid_plains",
"name": "Arid Plains",
"movement_cost": 1,
"defense_bonus": 0,
"food": 0,
"production": 1,
"trade": 1,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.12,
"flags": ["edge_blend"],
"color": [215, 185, 95],
"sprite": "sprites/terrain/arid_plains.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "hot",
"quality_bonuses": { "trade": 1 },
"terrain_power": {},
"albedo": 0.35,
"evapotranspiration": 0.002,
"description": "Dry plains bordering the desert."
},
{
"id": "badlands",
"name": "Badlands",
"movement_cost": 3,
"defense_bonus": 150,
"food": 0,
"production": 1,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.05,
"flags": ["edge_blend"],
"color": [195, 165, 110],
"sprite": "sprites/terrain/badlands.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "hot",
"quality_bonuses": {},
"terrain_power": {},
"albedo": 0.40,
"evapotranspiration": 0.001,
"description": "Eroded canyons between desert and mountains."
},
{
"id": "bog_edge",
"name": "Bog Edge",
"movement_cost": 2,
"defense_bonus": 50,
"food": 1,
"production": 0,
"trade": 0,
"culture": 0,
"mana_major": null,
"mana_minor": [],
"infiltration": 0.30,
"flags": ["edge_blend"],
"color": [80, 105, 65],
"sprite": "sprites/terrain/bog_edge.png",
"feature_overlay": "",
"feature_type": null,
"variant_count": 1,
"transform_to": null,
"transform_turns": null,
"climate_zone": "temperate",
"quality_bonuses": {},
"terrain_power": {},
"albedo": 0.16,
"evapotranspiration": 0.008,
"description": "Mossy wetland under the forest canopy."
}
]
}

View file

@ -1,4 +1,4 @@
{
"source": "resources/tiles",
"includes": ["frozen", "land_common", "land_forest", "land_special", "water_and_wonders"]
"includes": ["frozen", "land_common", "land_forest", "land_special", "land_blends", "water_and_wonders"]
}

View file

@ -0,0 +1,21 @@
{
"_doc": "Hex edge ecotone blend table (HEX_GEOMETRY.md §8). Each pair maps an unordered (centre, neighbour) terrain pairing to the derived edge terrain that lives on the shared boundary. Pairs are canonical-sorted alphabetically so lookup is order-independent. Same-terrain pairs and undefined pairs default to the centre terrain unchanged (no transition).",
"blends": [
{ "pair": ["forest", "plains"], "edge_terrain": "grass_fringe" },
{ "pair": ["mountains", "plains"], "edge_terrain": "foothills" },
{ "pair": ["coast", "plains"], "edge_terrain": "shore" },
{ "pair": ["lake", "plains"], "edge_terrain": "shore" },
{ "pair": ["ocean", "plains"], "edge_terrain": "shore" },
{ "pair": ["forest", "mountains"], "edge_terrain": "wooded_foothills" },
{ "pair": ["coast", "forest"], "edge_terrain": "riverside_forest" },
{ "pair": ["forest", "lake"], "edge_terrain": "riverside_forest" },
{ "pair": ["forest", "ocean"], "edge_terrain": "riverside_forest" },
{ "pair": ["coast", "mountains"], "edge_terrain": "cliff" },
{ "pair": ["lake", "mountains"], "edge_terrain": "cliff" },
{ "pair": ["mountains", "ocean"], "edge_terrain": "cliff" },
{ "pair": ["desert", "forest"], "edge_terrain": "scrub_edge" },
{ "pair": ["desert", "plains"], "edge_terrain": "arid_plains" },
{ "pair": ["desert", "mountains"], "edge_terrain": "badlands" },
{ "pair": ["forest", "swamp"], "edge_terrain": "bog_edge" }
]
}