feat(client): district morphology generation overlay in the Atlas (T-1046, D-226)
Client half — the cascade's district tier is now visible in the Atlas map.
- protocol.gd: decode the district_grid field from AtlasLayerResponse.
- atlas_viewer.gd: a gen_district ('MRPH') toggle in OVERLAY_DEFS + the
district-grid generation state (set/get) + wire it from the Ready response.
- atlas_marker_overlay.gd: _draw_gen_district() paints the coarse cols×rows grid,
each cell coloured by its MorphologyZone discriminant (D-239 §6, 17-zone
palette), semi-transparent under the Layer-1 line overlays. Planetary map view
(not the 2km on-demand districts — those are Phase 5 in-world).
gdUnit4 test: overlay registered + district grid round-trips through the viewer
and the protocol decode. Visual tuning of the palette can follow once eyeballed
in the running Atlas.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -787,6 +787,7 @@ static func atlas_response_from_raw(raw: Variant) -> Variant:
|
|||||||
"status": status,
|
"status": status,
|
||||||
"error": error,
|
"error": error,
|
||||||
"layer1": raw.get("layer1"),
|
"layer1": raw.get("layer1"),
|
||||||
|
"district_grid": raw.get("district_grid"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,29 @@ func test_generation_overlays_registered() -> void:
|
|||||||
var ids: Array = []
|
var ids: Array = []
|
||||||
for d: Dictionary in AtlasViewer.OVERLAY_DEFS:
|
for d: Dictionary in AtlasViewer.OVERLAY_DEFS:
|
||||||
ids.append(d["id"])
|
ids.append(d["id"])
|
||||||
assert_that(ids).contains(["gen_l1_rivers", "gen_l1_basins", "gen_l1_attractors"])
|
assert_that(ids).contains(
|
||||||
|
["gen_l1_rivers", "gen_l1_basins", "gen_l1_attractors", "gen_district"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
## T-1046: the district/morphology grid round-trips through the viewer and the
|
||||||
|
## protocol decode surfaces it; referencing AtlasMarkerOverlay compiles the draw.
|
||||||
|
func test_district_grid_round_trips() -> void:
|
||||||
|
var v: AtlasViewer = auto_free(AtlasViewer.new())
|
||||||
|
assert_that(v.get_generation_district_grid()).is_null()
|
||||||
|
var grid := {
|
||||||
|
"cols": 2,
|
||||||
|
"rows": 1,
|
||||||
|
"morphology": PackedByteArray([8, 14]),
|
||||||
|
"elev_q": PackedByteArray([10, 90]),
|
||||||
|
}
|
||||||
|
v.set_generation_district_grid(grid)
|
||||||
|
assert_that(v.get_generation_district_grid()).is_equal(grid)
|
||||||
|
# The decoded response dict carries the district_grid key (protocol.gd, T-1046).
|
||||||
|
var decoded: Variant = Protocol.atlas_response_from_raw(
|
||||||
|
{"body_id": "GJ1c", "status": "Ready", "district_grid": grid}
|
||||||
|
)
|
||||||
|
assert_that((decoded as Dictionary).get("district_grid")).is_equal(grid)
|
||||||
|
|
||||||
|
|
||||||
func test_generation_data_round_trips() -> void:
|
func test_generation_data_round_trips() -> void:
|
||||||
|
|||||||
@@ -109,6 +109,13 @@ func _draw() -> void:
|
|||||||
if viewer.is_overlay_visible("corp_presence"):
|
if viewer.is_overlay_visible("corp_presence"):
|
||||||
_draw_corp_presence(markers)
|
_draw_corp_presence(markers)
|
||||||
|
|
||||||
|
# Generation: district morphology grid (T-1046, D-226) — coarse area fill,
|
||||||
|
# drawn under the Layer-1 line/point overlays.
|
||||||
|
if viewer.is_overlay_visible("gen_district"):
|
||||||
|
var grid: Variant = viewer.get_generation_district_grid()
|
||||||
|
if grid is Dictionary:
|
||||||
|
_draw_gen_district(grid, tex_w, tex_h)
|
||||||
|
|
||||||
# Generation-cascade layers (#960, D-225) — from the proxied Layer1Output.
|
# Generation-cascade layers (#960, D-225) — from the proxied Layer1Output.
|
||||||
# Draw order: basins (area) under rivers under attractors (point anchors).
|
# Draw order: basins (area) under rivers under attractors (point anchors).
|
||||||
var layer1: Variant = viewer.get_generation_layer1()
|
var layer1: Variant = viewer.get_generation_layer1()
|
||||||
@@ -385,6 +392,60 @@ func _gen_pos(rc: Variant) -> Vector2:
|
|||||||
return Vector2(float(rc[1]) / _gen_grid_w * _gen_tex_w, float(rc[0]) / _gen_grid_h * _gen_tex_h)
|
return Vector2(float(rc[1]) / _gen_grid_w * _gen_tex_w, float(rc[0]) / _gen_grid_h * _gen_tex_h)
|
||||||
|
|
||||||
|
|
||||||
|
## District morphology grid (T-1046, D-226). A coarse `cols × rows` area fill —
|
||||||
|
## each cell coloured by its MorphologyZone discriminant (D-239 §6), semi-
|
||||||
|
## transparent so the heightmap reads through. Planetary-scale map view (NOT the
|
||||||
|
## 2 km on-demand districts, which are Phase 5 in-world).
|
||||||
|
func _draw_gen_district(grid: Dictionary, tex_w: float, tex_h: float) -> void:
|
||||||
|
var cols: int = int(grid.get("cols", 0))
|
||||||
|
var rows: int = int(grid.get("rows", 0))
|
||||||
|
if cols <= 0 or rows <= 0:
|
||||||
|
return
|
||||||
|
var morphology: Variant = grid.get("morphology")
|
||||||
|
if not morphology is PackedByteArray:
|
||||||
|
return
|
||||||
|
var cw: float = tex_w / float(cols)
|
||||||
|
var ch: float = tex_h / float(rows)
|
||||||
|
var n: int = morphology.size()
|
||||||
|
for ry in range(rows):
|
||||||
|
for rx in range(cols):
|
||||||
|
var i: int = ry * cols + rx
|
||||||
|
if i >= n:
|
||||||
|
continue
|
||||||
|
# +0.5 overdraw avoids hairline seams between adjacent cells.
|
||||||
|
draw_rect(Rect2(rx * cw, ry * ch, cw + 0.5, ch + 0.5), _morphology_color(int(morphology[i])))
|
||||||
|
|
||||||
|
|
||||||
|
## MorphologyZone discriminant → overlay colour (D-239 §6 order). ~0.55 alpha so
|
||||||
|
## the heightmap shows through: water blues, plains greens, uplands greys/browns,
|
||||||
|
## volcanic dark red.
|
||||||
|
const MORPHOLOGY_COLORS: Array = [
|
||||||
|
Color(0.10, 0.20, 0.45, 0.55), # 0 OpenOcean
|
||||||
|
Color(0.20, 0.40, 0.65, 0.55), # 1 Lake
|
||||||
|
Color(0.45, 0.55, 0.50, 0.55), # 2 TidalFlat
|
||||||
|
Color(0.85, 0.78, 0.45, 0.55), # 3 DuneStrand
|
||||||
|
Color(0.50, 0.50, 0.55, 0.55), # 4 CliffCoast
|
||||||
|
Color(0.30, 0.40, 0.50, 0.55), # 5 Fjord
|
||||||
|
Color(0.40, 0.65, 0.60, 0.55), # 6 Delta
|
||||||
|
Color(0.30, 0.55, 0.55, 0.55), # 7 Estuarine
|
||||||
|
Color(0.30, 0.60, 0.30, 0.55), # 8 AlluvialPlain
|
||||||
|
Color(0.45, 0.70, 0.40, 0.55), # 9 RiverBank
|
||||||
|
Color(0.35, 0.60, 0.50, 0.55), # 10 MeanderReach
|
||||||
|
Color(0.55, 0.60, 0.45, 0.55), # 11 BraidedPlain
|
||||||
|
Color(0.50, 0.55, 0.30, 0.55), # 12 ValleyFloor
|
||||||
|
Color(0.55, 0.45, 0.30, 0.55), # 13 MountainPass
|
||||||
|
Color(0.80, 0.82, 0.85, 0.55), # 14 Alpine
|
||||||
|
Color(0.45, 0.15, 0.12, 0.55), # 15 Volcanic
|
||||||
|
Color(0.25, 0.45, 0.40, 0.55), # 16 Wetland
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
func _morphology_color(zone: int) -> Color:
|
||||||
|
if zone >= 0 and zone < MORPHOLOGY_COLORS.size():
|
||||||
|
return MORPHOLOGY_COLORS[zone]
|
||||||
|
return Color(0.5, 0.5, 0.5, 0.4)
|
||||||
|
|
||||||
|
|
||||||
func _draw_gen_rivers(layer1: Dictionary) -> void:
|
func _draw_gen_rivers(layer1: Dictionary) -> void:
|
||||||
var rn: Variant = layer1.get("river_network")
|
var rn: Variant = layer1.get("river_network")
|
||||||
if not rn is Dictionary:
|
if not rn is Dictionary:
|
||||||
|
|||||||
@@ -139,6 +139,12 @@ const OVERLAY_DEFS: Array = [
|
|||||||
"group": "toggle",
|
"group": "toggle",
|
||||||
"tooltip": "Layer 1 — geographic attractors (generation overlay, #960)."
|
"tooltip": "Layer 1 — geographic attractors (generation overlay, #960)."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "gen_district",
|
||||||
|
"label": "MRPH",
|
||||||
|
"group": "toggle",
|
||||||
|
"tooltip": "District morphology zones (generation overlay, T-1046/D-226)."
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# ── Context (set by show_body) ─────────────────────────────────────────────────
|
# ── Context (set by show_body) ─────────────────────────────────────────────────
|
||||||
@@ -150,6 +156,7 @@ var _implant_theme = null
|
|||||||
var _heightmap_texture: Texture2D = null
|
var _heightmap_texture: Texture2D = null
|
||||||
var _markers: Dictionary = {}
|
var _markers: Dictionary = {}
|
||||||
var _generation_layer1: Variant = null # #960: Layer1Output from the proxy (D-225)
|
var _generation_layer1: Variant = null # #960: Layer1Output from the proxy (D-225)
|
||||||
|
var _generation_district_grid: Variant = null # T-1046: coarse DistrictGridLayer (D-226)
|
||||||
var _gen_pending: bool = false # #960: awaiting a Layer1 response (re-polls on Pending)
|
var _gen_pending: bool = false # #960: awaiting a Layer1 response (re-polls on Pending)
|
||||||
var _gen_retries: int = 0
|
var _gen_retries: int = 0
|
||||||
var _grid_w: float = 512.0
|
var _grid_w: float = 512.0
|
||||||
@@ -298,6 +305,18 @@ func get_generation_layer1() -> Variant:
|
|||||||
return _generation_layer1
|
return _generation_layer1
|
||||||
|
|
||||||
|
|
||||||
|
## T-1046: store the coarse district/morphology grid (DistrictGridLayer) from the
|
||||||
|
## proxy and redraw. The marker overlay reads it via get_generation_district_grid().
|
||||||
|
func set_generation_district_grid(grid: Variant) -> void:
|
||||||
|
_generation_district_grid = grid
|
||||||
|
if _overlay_node:
|
||||||
|
_overlay_node.queue_redraw()
|
||||||
|
|
||||||
|
|
||||||
|
func get_generation_district_grid() -> Variant:
|
||||||
|
return _generation_district_grid
|
||||||
|
|
||||||
|
|
||||||
## #960: request the body's Layer-1 cascade output from the server proxy.
|
## #960: request the body's Layer-1 cascade output from the server proxy.
|
||||||
## No-op in test mode (SimBridge has no server connection) — the overlays
|
## No-op in test mode (SimBridge has no server connection) — the overlays
|
||||||
## simply stay empty, which is the correct serverless behavior.
|
## simply stay empty, which is the correct serverless behavior.
|
||||||
@@ -322,6 +341,7 @@ func _on_atlas_layers_received(response: Dictionary) -> void:
|
|||||||
_gen_retries = 0
|
_gen_retries = 0
|
||||||
_set_gen_indicator(false)
|
_set_gen_indicator(false)
|
||||||
set_generation_layer1(response.get("layer1"))
|
set_generation_layer1(response.get("layer1"))
|
||||||
|
set_generation_district_grid(response.get("district_grid"))
|
||||||
"Pending":
|
"Pending":
|
||||||
if _gen_retries < GEN_MAX_RETRIES:
|
if _gen_retries < GEN_MAX_RETRIES:
|
||||||
_gen_retries += 1
|
_gen_retries += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user