fix(ui): atlas review blockers — moon layout, stale grid dims, overlay schema (#128)
1. Moon placement on the orbital diagram divided by a hard-coded 4 — a gas giant with five or more moons would overlap satellites and make them unclickable. Count moons per parent and distribute them evenly. 2. AtlasViewer._load_markers seeded _grid_w/_grid_h from _tex_w/_tex_h before the terrain_reference null check, so opening a body with no heightmap after one that had a heightmap left the grid dimensions pointing at the previous texture and misplaced markers. Reset texture + grid dims to sentinel defaults at the top of _load_heightmap. 3. AtlasMarkerOverlay's three toggleable overlays (production_zones, shadow_economy, corp_presence) read markers.production_zones / markers.shadow_zones / markers.corp_presence — keys that don't exist in D-191 §8's markers schema, so toggling was a silent no-op. Derive them from cities[] instead: primary_function ∈ PRODUCTION_FUNCTIONS for production, absent Commission presence for shadow bands, Commission presence for corp dots. Follow-up ticket will formalise per-overlay arrays once the server schema lands. Addresses PR #128 review blockers 1-3.
This commit is contained in:
@@ -35,6 +35,11 @@ const COLOR_CORP: Color = Color(0.85, 0.65, 0.20, 0.75)
|
||||
const RAIL_DASH_ON: float = 6.0
|
||||
const RAIL_DASH_OFF: float = 4.0
|
||||
|
||||
const PRODUCTION_FUNCTIONS: Array = [
|
||||
"industrial", "industry", "manufacturing", "extraction", "mining",
|
||||
"refinery", "foundry", "shipyard", "production",
|
||||
]
|
||||
|
||||
var viewer = null # AtlasViewer (untyped to avoid cyclic ref)
|
||||
|
||||
|
||||
@@ -200,24 +205,44 @@ func _draw_population_density(markers: Dictionary) -> void:
|
||||
draw_circle(pos, r, COLOR_POP_HEAT)
|
||||
|
||||
|
||||
# Review #3: derive overlays from existing cities[] fields. The original
|
||||
# code read production_zones / shadow_zones / corp_presence from keys that
|
||||
# are not in the D-191 §8 markers schema, so toggling was a silent no-op.
|
||||
# When the server schema lands with explicit zone polygons we can promote
|
||||
# these back to dedicated arrays; until then the MVP reads what's there.
|
||||
func _draw_production_zones(markers: Dictionary) -> void:
|
||||
for zone: Dictionary in markers.get("production_zones", []):
|
||||
var p: Vector2 = _center_to_canvas(zone.get("center"))
|
||||
var radius: float = float(zone.get("radius", 12.0))
|
||||
draw_circle(p, radius, COLOR_PRODUCTION)
|
||||
for city: Dictionary in markers.get("cities", []):
|
||||
var func_id: String = str(city.get("primary_function", "")).to_lower()
|
||||
if not PRODUCTION_FUNCTIONS.has(func_id):
|
||||
continue
|
||||
var pos: Vector2 = viewer._city_canvas_pos(city)
|
||||
var tier: int = int(city.get("population_tier", 1))
|
||||
var radius: float = 10.0 + float(tier) * 4.0
|
||||
draw_circle(pos, radius, COLOR_PRODUCTION)
|
||||
|
||||
|
||||
func _draw_shadow_economy(markers: Dictionary) -> void:
|
||||
for zone: Dictionary in markers.get("shadow_zones", []):
|
||||
var p: Vector2 = _center_to_canvas(zone.get("center"))
|
||||
var radius: float = float(zone.get("radius", 16.0))
|
||||
draw_circle(p, radius, COLOR_SHADOW)
|
||||
# Broad bands around cities outside the Commission's reach. Intentionally
|
||||
# soft + overlapping — D-181 treats shadow zones as "broad bands", not
|
||||
# precise polygons.
|
||||
for city: Dictionary in markers.get("cities", []):
|
||||
var commission: bool = bool(city.get("commission_presence", false))
|
||||
if commission:
|
||||
continue
|
||||
var pos: Vector2 = viewer._city_canvas_pos(city)
|
||||
var tier: int = int(city.get("population_tier", 1))
|
||||
var radius: float = 18.0 + float(tier) * 6.0
|
||||
draw_circle(pos, radius, COLOR_SHADOW)
|
||||
|
||||
|
||||
func _draw_corp_presence(markers: Dictionary) -> void:
|
||||
for corp: Dictionary in markers.get("corp_presence", []):
|
||||
var p: Vector2 = _center_to_canvas(corp.get("pos"))
|
||||
draw_rect(Rect2(p - Vector2(3, 3), Vector2(6, 6)), COLOR_CORP)
|
||||
# Tier 1 corp presence — keyed off the Commission-presence flag on cities
|
||||
# until D-191 §8 schema explicitly lists per-corp dots.
|
||||
for city: Dictionary in markers.get("cities", []):
|
||||
if not bool(city.get("commission_presence", false)):
|
||||
continue
|
||||
var pos: Vector2 = viewer._city_canvas_pos(city)
|
||||
draw_rect(Rect2(pos - Vector2(3, 3), Vector2(6, 6)), COLOR_CORP)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
|
||||
@@ -230,18 +230,34 @@ func _compute_body_positions() -> void:
|
||||
angle = -PI / 2.0 + TAU * float(i) / float(count)
|
||||
_body_positions[b["body_id"]] = center + Vector2(cos(angle), sin(angle)) * ring_r
|
||||
|
||||
# Place moons near their parent body
|
||||
# Place moons near their parent body. Moons per parent count drives the
|
||||
# angular spacing — a hard-coded divisor made the 5th+ moon overlap moon 1
|
||||
# and become unclickable on gas giants with many satellites (review #1).
|
||||
var moons_by_parent: Dictionary = {}
|
||||
for b: Dictionary in _orbital_bodies:
|
||||
var parent_id: Variant = b.get("parent_body_id")
|
||||
if parent_id == null:
|
||||
continue
|
||||
if not _body_positions.has(str(parent_id)):
|
||||
var key: String = str(parent_id)
|
||||
if not moons_by_parent.has(key):
|
||||
moons_by_parent[key] = []
|
||||
moons_by_parent[key].append(b)
|
||||
for parent_key: String in moons_by_parent:
|
||||
if not _body_positions.has(parent_key):
|
||||
continue
|
||||
var parent_pos: Vector2 = _body_positions[str(parent_id)]
|
||||
var moon_idx: int = int(b.get("orbit_index", 1))
|
||||
# Distribute moons around parent at evenly spaced angles
|
||||
var angle: float = -PI / 2.0 + TAU * float(moon_idx - 1) / 4.0
|
||||
_body_positions[b["body_id"]] = parent_pos + Vector2(cos(angle), sin(angle)) * MOON_ORBIT_RADIUS
|
||||
var siblings: Array = moons_by_parent[parent_key]
|
||||
siblings.sort_custom(
|
||||
func(a: Dictionary, b: Dictionary) -> bool:
|
||||
return int(a.get("orbit_index", 0)) < int(b.get("orbit_index", 0))
|
||||
)
|
||||
var parent_pos: Vector2 = _body_positions[parent_key]
|
||||
var count: int = siblings.size()
|
||||
for i: int in range(count):
|
||||
var moon: Dictionary = siblings[i]
|
||||
var angle: float = -PI / 2.0 + TAU * float(i) / float(count)
|
||||
_body_positions[moon["body_id"]] = (
|
||||
parent_pos + Vector2(cos(angle), sin(angle)) * MOON_ORBIT_RADIUS
|
||||
)
|
||||
|
||||
# Place stations near their parent body (offset right + slightly up)
|
||||
for s: Dictionary in _orbital_stations:
|
||||
|
||||
@@ -177,7 +177,13 @@ func get_markers() -> Dictionary:
|
||||
|
||||
|
||||
func _load_heightmap() -> void:
|
||||
# Reset to sentinel defaults so a body with no heightmap (or that fails to
|
||||
# load) doesn't inherit the previous body's dimensions — otherwise
|
||||
# grid_to_canvas would project markers using the prior texture size
|
||||
# (review #2).
|
||||
_heightmap_texture = null
|
||||
_tex_w = 1024.0
|
||||
_tex_h = 512.0
|
||||
var ref: Variant = _body.get("terrain_reference")
|
||||
if ref == null or str(ref).is_empty():
|
||||
return
|
||||
@@ -196,6 +202,8 @@ func _load_heightmap() -> void:
|
||||
|
||||
func _load_markers() -> void:
|
||||
_markers = {}
|
||||
# Reset grid dims alongside _tex_* in _load_heightmap so we start from a
|
||||
# known baseline regardless of which body ran previously.
|
||||
_grid_w = _tex_w
|
||||
_grid_h = _tex_h
|
||||
|
||||
|
||||
Reference in New Issue
Block a user