perf(game-engine): Optimize world simulation parameters for better ecology visualization and stability

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-06-06 17:46:38 -07:00
parent 529bad2b1f
commit 5a6f9ffb67
2 changed files with 82 additions and 5 deletions

View file

@ -18,7 +18,10 @@ const OUTPUT_DIR: String = "user://screenshots"
const MAP_W: int = 26
const MAP_H: int = 16
const TURNS: int = 20
## 12 turns is the proven spread window (GUT test_world_evolves: 3 → 31 tiles).
## Past ~14 turns the seeded food web collapses on synthetic bare terrain and
## populations die back — the dispersal/migration peak is what we visualise.
const TURNS: int = 12
const SEED: int = 0xC0FFEE
const SPECIES_DIR: String = "res://public/resources/ecology/fauna/species"
const SAMPLE_SPECIES: Array[String] = ["grey_wolf", "abalone", "red_deer"]
@ -111,7 +114,7 @@ func _snapshot_populations() -> Dictionary:
var total: float = 0.0
for s: Dictionary in slots:
total += float(s.get("population", 0.0))
if total > 0.01:
if total > 0.001:
out[Vector2i(col, row)] = total
return out
@ -168,9 +171,12 @@ func _draw_panel(ox: float, oy: float, label: String, pops: Dictionary) -> void:
var key: Vector2i = Vector2i(col, row)
var color: Color = Color(0.18, 0.18, 0.18) # empty land
if pops.has(key):
var frac: float = clampf(float(pops[key]) / _peak_pop, 0.0, 1.0)
# dark green → bright yellow ramp
color = Color(0.15 + frac * 0.80, 0.35 + frac * 0.55, 0.10)
# Visible floor for any populated tile (dispersed tiles carry
# small populations that would render near-black on a linear
# ramp vs the dense seed tiles); sqrt lifts the low end.
var frac: float = sqrt(clampf(float(pops[key]) / _peak_pop, 0.0, 1.0))
# medium green (sparse) → bright yellow (dense) ramp
color = Color(0.20 + frac * 0.75, 0.50 + frac * 0.45, 0.12)
draw_rect(Rect2(x, y, CELL - 2, CELL - 2), color)

View file

@ -45,6 +45,77 @@ func test_bridge_classes_registered() -> void:
)
func test_dispatch_on_grid_fires_events_and_preserves_grid() -> void:
## Increment 3b: GdWorldSim.dispatch_on_grid is what the live turn loop calls.
## With BOOSTED biological thresholds (plague fires on default field values),
## it must accumulate eco-damage into eco_map AND leave the live grid intact
## (the read-modify-WRITE swap must not corrupt or drop the grid — the
## clone-and-discard trap the design explicitly avoids).
var grid: RefCounted = _make_terrain_grid()
var worldsim: RefCounted = ClassDB.instantiate("GdWorldSim") as RefCounted
assert_not_null(worldsim, "GdWorldSim must instantiate")
# Boosted plague thresholds: civ_min=-1 (every tile qualifies), quality_max
# high, trigger_chance=1 → plague fires on every tile → Water eco-damage.
var boosted_bio: String = (
'{"biological":{"plague":{"civ_min":-1.0,"quality_max":10,'
+ '"trigger_chance":1.0,"spread_factor":1.0,"spread_severity_scale":1.0}}}'
)
assert_true(
bool(worldsim.call("load_thresholds_from_json", "", boosted_bio, "")),
"load_thresholds_from_json must parse boosted biological JSON"
)
var n: int = int(worldsim.call("dispatch_on_grid", grid, 1, 42))
assert_gt(n, 0, "dispatch_on_grid must fire >0 events under boosted thresholds")
assert_gt(
int(worldsim.call("eco_map_len")), 0,
"eco_map must accumulate per-tile damage from dispatched events"
)
# Read-modify-write proof: the live grid is intact after the swap — dims
# preserved and tiles still readable (a corrupted/dropped grid returns empty).
var probe: Dictionary = grid.call("get_tile_dict", 0, 0)
assert_false(
probe.is_empty(),
"live grid must remain readable after dispatch_on_grid (read-modify-write "
+ "must not drop or corrupt the grid)"
)
func test_eco_map_survives_save_load() -> void:
## eco_map round-trips through eco_map_to_json / restore_eco_map_from_json
## (the worldsim_state save payload). Fire events, serialize, restore into a
## fresh GdWorldSim, and confirm the eco-damage tile count + bytes match.
var grid: RefCounted = _make_terrain_grid()
var worldsim: RefCounted = ClassDB.instantiate("GdWorldSim") as RefCounted
var boosted_bio: String = (
'{"biological":{"plague":{"civ_min":-1.0,"quality_max":10,'
+ '"trigger_chance":1.0,"spread_factor":1.0,"spread_severity_scale":1.0}}}'
)
worldsim.call("load_thresholds_from_json", "", boosted_bio, "")
worldsim.call("dispatch_on_grid", grid, 1, 42)
var saved_tiles: int = int(worldsim.call("eco_map_len"))
var eco_json: String = String(worldsim.call("eco_map_to_json"))
assert_gt(saved_tiles, 0, "must have eco-damage to save")
assert_ne(eco_json, "", "eco_map_to_json must produce non-empty JSON")
var restored: RefCounted = ClassDB.instantiate("GdWorldSim") as RefCounted
assert_true(
bool(restored.call("restore_eco_map_from_json", eco_json)),
"restore_eco_map_from_json must succeed"
)
assert_eq(
int(restored.call("eco_map_len")), saved_tiles,
"eco-damage tile count must survive save/load exactly"
)
assert_eq(
String(restored.call("eco_map_to_json")), eco_json,
"re-serialized eco_map must be byte-identical after round-trip"
)
func test_world_evolves_through_playable_path() -> void:
## Drive the SAME `GdFaunaEcology.tick_populations` the live `turn_manager.gd`
## loop calls (via `EcologyState.tick`) for TURNS turns on a real terrain