feat(client): invert the Atlas rung relation — rung sets extent, not spacing
A rung used to fix the gridunit SPACING, with the canvas extent falling out of spacing x cell count. That is why the top of the ladder was unusable: at REGION_M spacing a viewport-sized canvas spanned ~251,658 km — six times around a rocky body — so the Region rung capped to the body and redrew the Global picture pixel-for-pixel. "Global and region look the same" was not a rendering bug; it was this relation, stated in metres. Inverted: a rung fixes the EXTENT and the spacing falls out of the canvas size. The shorter viewport axis spans exactly one cell of the rung's level, so a widescreen window shows more ground on the long axis rather than less on the short one. Every rung now shows the ground its name promises — Region 262x466 km, District 4.1x7.3 km — and the canvas cell count is viewport-driven and identical at every rung, so derive cost no longer varies with depth and resize is free. Consequences that fell out of the inversion rather than being chosen: - Region leaves the orbital derive set. It was envelope-only because at 251,658 km nothing finer made sense; at 262 km it is a genuine provincial map and takes the full courses-aware derive. Region having no rivers at all was much of why the top of the ladder read flat. It also joins the deep display ratio for the same reason. - The S2 station-spacing floor is deleted, not retuned. It guarded an O(1/spacing) blowup that the inversion makes structurally impossible (the canvas cell count is now constant across rungs, so stations-per-course is bounded however deep you scroll). Kept, it would do active harm in the opposite direction: a 2,048 m pitch across a 3.6 km District canvas places two stations and draws every river as a straight line. Station placement gets its own generator pass. - cap_extent_to_body is superseded and now a documented no-op. A canvas can no longer over-request a body by construction. The residual question — whether a rung's cell exceeds the whole body — is liveness, not capping, and is_rung_live_on_body() answers it by omitting the rung. Empirically it never fires on inhabited content: all six rungs are live on all 271 populated bodies with a radius. - snap_to_gridunit no longer truncates its multiplier to int. Post-inversion the deep rungs run sub-metre (Chunk ~0.12 m at a 1080 px short axis), where int(spacing) floors to zero and would collapse every request centre onto the origin. Both sides derive spacing from the same three inputs (rung, echoed cell extent, body radius) rather than one telling the other, so there is nothing to keep in sync beyond the constant table itself. Body radius already reaches the viewer via enter(); no wire change. Pair session with Jeroen, 2026-07-26. D-243/D-255 amendments to be backfiled. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -162,19 +162,29 @@ var _canvas: Variant = null # decoded StepCanvasResponse.canvas — null until
|
||||
var _world_center: Vector2 = Vector2.ZERO
|
||||
var _rung: String = StepCanvasTransport.RUNG_DISTRICT
|
||||
var _extent_cells: Vector2i = Vector2i.ZERO
|
||||
## Body radius (km) — the fourth frame input since the D-255 extent inversion
|
||||
## (pair session 2026-07-26). Gridunit spacing is no longer a constant per
|
||||
## rung; it is derived from the rung, the canvas cell count AND (at Global,
|
||||
## the elastic seam) the body itself. All four therefore travel together.
|
||||
var _body_radius_km: float = 0.0
|
||||
|
||||
|
||||
## Adopt a new canvas + its request frame (world center, rung, extent) — the
|
||||
## world->screen projection for every drawn feature depends on all three,
|
||||
## so they're set together, matching the terrain layer's own texture-plus-
|
||||
## frame handoff.
|
||||
## Adopt a new canvas + its request frame (world center, rung, extent, body
|
||||
## radius) — the world->screen projection for every drawn feature depends on
|
||||
## all four, so they're set together, matching the terrain layer's own
|
||||
## texture-plus-frame handoff.
|
||||
func set_frame(
|
||||
canvas: Variant, world_center: Vector2, rung: String, extent_cells: Vector2i
|
||||
canvas: Variant,
|
||||
world_center: Vector2,
|
||||
rung: String,
|
||||
extent_cells: Vector2i,
|
||||
body_radius_km: float
|
||||
) -> void:
|
||||
_canvas = canvas
|
||||
_world_center = world_center
|
||||
_rung = rung
|
||||
_extent_cells = extent_cells
|
||||
_body_radius_km = body_radius_km
|
||||
queue_redraw()
|
||||
|
||||
|
||||
@@ -183,6 +193,13 @@ func clear_frame() -> void:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
## This frame's metres-per-gridunit — derived, never a per-rung constant
|
||||
## (D-255 extent inversion). One accessor so every projection in this file
|
||||
## reads the same number by construction.
|
||||
func _spacing_m() -> float:
|
||||
return StepCanvasTransport.spacing_for_rung(_rung, _extent_cells, _body_radius_km)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if not _canvas is Dictionary:
|
||||
return
|
||||
@@ -249,8 +266,8 @@ func _is_true_source_in_canvas(world_m: Variant) -> bool:
|
||||
if not world_m is Vector2:
|
||||
return false
|
||||
var p: Vector2 = world_m
|
||||
var half_w_m: float = float(_extent_cells.x) * 0.5 * StepCanvasTransport.spacing_for_rung(_rung)
|
||||
var half_h_m: float = float(_extent_cells.y) * 0.5 * StepCanvasTransport.spacing_for_rung(_rung)
|
||||
var half_w_m: float = float(_extent_cells.x) * 0.5 * _spacing_m()
|
||||
var half_h_m: float = float(_extent_cells.y) * 0.5 * _spacing_m()
|
||||
var lo_x: float = _world_center.x - half_w_m + CROP_EDGE_EPSILON_M
|
||||
var hi_x: float = _world_center.x + half_w_m - CROP_EDGE_EPSILON_M
|
||||
var lo_y: float = _world_center.y - half_h_m + CROP_EDGE_EPSILON_M
|
||||
@@ -513,7 +530,7 @@ func _draw_settlements(canvas: Dictionary) -> void:
|
||||
## (`center_world_m + (col - half_w) * step_m`), mirrored client-side so a
|
||||
## settlement marker lands on the exact cell its id was read from.
|
||||
func _cell_center_world_m(col: int, row: int) -> Vector2:
|
||||
var spacing: float = StepCanvasTransport.spacing_for_rung(_rung)
|
||||
var spacing: float = _spacing_m()
|
||||
var half_w: float = float(_extent_cells.x) * 0.5
|
||||
var half_h: float = float(_extent_cells.y) * 0.5
|
||||
return Vector2(
|
||||
@@ -523,4 +540,6 @@ func _cell_center_world_m(col: int, row: int) -> Vector2:
|
||||
|
||||
|
||||
func _world_to_local(world_m: Vector2) -> Vector2:
|
||||
return StepCanvasTransport.world_m_to_canvas_local(world_m, _world_center, _rung, _extent_cells)
|
||||
return StepCanvasTransport.world_m_to_canvas_local(
|
||||
world_m, _world_center, _rung, _extent_cells, _body_radius_km
|
||||
)
|
||||
|
||||
@@ -75,7 +75,9 @@ func refresh() -> void:
|
||||
clear()
|
||||
visible = true
|
||||
|
||||
var spacing_km: float = StepCanvasTransport.spacing_for_rung(_viewer.get_held_rung()) / 1000.0
|
||||
var spacing_km: float = StepCanvasTransport.spacing_for_rung(
|
||||
_viewer.get_held_rung(), _viewer.get_held_extent(), _viewer.get_body_radius_km()
|
||||
) / 1000.0
|
||||
var subtitle: String = "%s · %.3f km/gridunit" % [_viewer.get_held_rung().to_lower(), spacing_km]
|
||||
add_component(ImplantHeader.new("ATLAS LEGEND", subtitle))
|
||||
add_component(ImplantSeparator.new())
|
||||
|
||||
@@ -29,18 +29,28 @@ const RUNG_LADDER: Array = [
|
||||
RUNG_GLOBAL, RUNG_REGION, RUNG_DISTRICT, RUNG_QUARTER, RUNG_BLOCK, RUNG_CHUNK
|
||||
]
|
||||
|
||||
## D-243 gridunit spacing in metres for every FIXED rung — mirrors
|
||||
## D-243 world-metre CELL SIZE for every FIXED rung — mirrors
|
||||
## server/src/atlas/scale.rs's own constants exactly (REGION_M/DISTRICT_M/
|
||||
## QUARTER_M/BLOCK_M/CHUNK_M), so the client's rung table can never silently
|
||||
## drift from the wire contract it's choosing between. Global has no single
|
||||
## spacing value in the fixed sense (D-255(a): "a Global gridunit and a
|
||||
## Region gridunit are both 'one region' wide" — step_canvas.rs's own
|
||||
## StepCanvasRung::spacing_m() returns REGION_M for Global too, "a
|
||||
## harmless-but-correct value... so ordering/comparison call sites... get a
|
||||
## sane, documented number rather than 0 or a panic") — mirrored here for the
|
||||
## same reason.
|
||||
const RUNG_SPACING_M: Dictionary = {
|
||||
RUNG_GLOBAL: 204_800.0,
|
||||
## QUARTER_M/BLOCK_M/CHUNK_M) and must equal StepCanvasRung::extent_m()
|
||||
## server-side.
|
||||
##
|
||||
## **These are extents, not spacings** (D-255 amendment, pair session
|
||||
## 2026-07-26). The original ladder had the relation the other way round: a
|
||||
## rung fixed the gridunit SPACING and the canvas extent fell out of
|
||||
## `spacing x cell count`. That is what made the top of the ladder unusable —
|
||||
## at REGION_M spacing a viewport-sized canvas spanned ~251,658 km, six times
|
||||
## around a rocky body, so the Region rung capped to the body and redrew the
|
||||
## Global picture pixel-for-pixel ("global and region look the same"). Now the
|
||||
## rung fixes the EXTENT and the spacing falls out of the canvas size
|
||||
## (spacing_for_rung()), so every rung shows exactly the ground its name
|
||||
## promises.
|
||||
##
|
||||
## Global is deliberately ABSENT: it is D-243's elastic seam, the one rung
|
||||
## whose extent is the body itself and therefore cannot be a constant. A
|
||||
## missing key is the client's equivalent of the server's `Option::None` —
|
||||
## spacing_for_rung() branches on it rather than on a rung name, so the two
|
||||
## sides express the same fact the same way.
|
||||
const RUNG_EXTENT_M: Dictionary = {
|
||||
RUNG_REGION: 204_800.0,
|
||||
RUNG_DISTRICT: 2_048.0,
|
||||
RUNG_QUARTER: 512.0,
|
||||
@@ -75,9 +85,16 @@ const DISPLAY_RATIO_SHALLOW: float = 5.0
|
||||
## (D-255(a): "the deep, ground-level steps where the player is closest to
|
||||
## visible detail"). Chunk's own "1 screen px per 64 m gridunit, no
|
||||
## magnification margin" bottom-out rule (D-255(a)) is exactly DISPLAY_RATIO_DEEP.
|
||||
## D-255 amendment 2026-07-26: **Region moved to the deep ratio.** The shallow
|
||||
## fallback earned its keep only while a Region canvas was an orbital-scale
|
||||
## picture where extent, not per-cell fidelity, was what grew. Post-inversion
|
||||
## Region spans 262x466 km — a provincial map with real terrain in it — so it
|
||||
## takes the same crispness as every rung below it. Jeroen's brief for this
|
||||
## session in one line: "the global and region maps need to look the same as
|
||||
## the smaller level maps, but with obviously a different zoom level."
|
||||
const DISPLAY_RATIO_BY_RUNG: Dictionary = {
|
||||
RUNG_GLOBAL: DISPLAY_RATIO_SHALLOW,
|
||||
RUNG_REGION: DISPLAY_RATIO_SHALLOW,
|
||||
RUNG_REGION: DISPLAY_RATIO_DEEP,
|
||||
RUNG_DISTRICT: DISPLAY_RATIO_DEEP,
|
||||
RUNG_QUARTER: DISPLAY_RATIO_DEEP,
|
||||
RUNG_BLOCK: DISPLAY_RATIO_DEEP,
|
||||
@@ -133,30 +150,60 @@ static func scroll_step(current_index: int, direction: int) -> int:
|
||||
return clampi(current_index + delta, 0, RUNG_LADDER.size() - 1)
|
||||
|
||||
|
||||
static func spacing_for_rung(rung: String) -> float:
|
||||
return float(RUNG_SPACING_M.get(rung, RUNG_SPACING_M[RUNG_DISTRICT]))
|
||||
## Metres per gridunit for a canvas of `extent_cells` cells on a body of
|
||||
## `body_radius_km` — a function of the REQUEST, not a per-rung constant
|
||||
## (D-255 amendment 2026-07-26, see RUNG_EXTENT_M). Must agree exactly with
|
||||
## StepCanvasRung::spacing_m() server-side; both sides compute it from the
|
||||
## same three inputs rather than one telling the other, so there is nothing
|
||||
## to keep in sync beyond the constant table itself.
|
||||
##
|
||||
## Fixed rungs: the SHORTER canvas axis spans exactly one cell of this level,
|
||||
## so a widescreen viewport shows proportionally more ground on the long axis
|
||||
## rather than less on the short one (Jeroen's rule: "the smallest viewport
|
||||
## axis locks the area for calculation, since that is most widescreen
|
||||
## friendly").
|
||||
##
|
||||
## Global: equirectangular whole body — the full 2*PI*R circumference wraps
|
||||
## the canvas WIDTH, the 2:1 cell counts keeping cells square (height spans
|
||||
## PI*R, pole to pole).
|
||||
##
|
||||
## Callers pass the ECHOED extent, never the requested one — the server
|
||||
## clamps independently, and a clamped canvas covers the same ground at a
|
||||
## coarser pitch (the viewer's existing "read the echoed extent" discipline,
|
||||
## now load-bearing for world geometry and not just for drawing).
|
||||
static func spacing_for_rung(rung: String, extent_cells: Vector2i, body_radius_km: float) -> float:
|
||||
var extent_m: float = float(RUNG_EXTENT_M.get(rung, 0.0))
|
||||
if extent_m > 0.0:
|
||||
return extent_m / float(maxi(1, mini(extent_cells.x, extent_cells.y)))
|
||||
return TAU * body_radius_km * 1000.0 / float(maxi(1, extent_cells.x))
|
||||
|
||||
|
||||
static func display_ratio_for_rung(rung: String) -> float:
|
||||
return float(DISPLAY_RATIO_BY_RUNG.get(rung, DISPLAY_RATIO_DEEP))
|
||||
|
||||
|
||||
## True for the two rungs that ride derive_orbital_at_metres server-side
|
||||
## (Global/Region, step_canvas.rs's own StepCanvasRung::uses_orbital_derive())
|
||||
## — mirrored here purely for READABILITY at call sites that branch on it
|
||||
## (e.g. "does this rung's canvas ever carry courses" — Global/Region never
|
||||
## do, matching invent_courses_for_canvas()'s own early return), not because
|
||||
## the client makes any derivation decision itself (D-255(e): derivation
|
||||
## stays server-side, full stop).
|
||||
## True for the rung that rides derive_orbital_at_metres server-side
|
||||
## (step_canvas.rs's own StepCanvasRung::uses_orbital_derive()) — mirrored
|
||||
## here purely for READABILITY at call sites that branch on it (e.g. "does
|
||||
## this rung's canvas ever carry courses"), not because the client makes any
|
||||
## derivation decision itself (D-255(e): derivation stays server-side).
|
||||
##
|
||||
## D-255 amendment 2026-07-26: **Region left this set.** Pre-inversion a
|
||||
## Region canvas spanned ~251,658 km — orbital envelope-only derivation was
|
||||
## the only sane treatment at that scale. Post-inversion it spans 262x466 km,
|
||||
## a genuine provincial map, so it takes the full courses-aware derive like
|
||||
## every other fixed rung. Region having no rivers was a large part of why
|
||||
## the top of the ladder read flat.
|
||||
static func is_orbital_rung(rung: String) -> bool:
|
||||
return rung == RUNG_GLOBAL or rung == RUNG_REGION
|
||||
return rung == RUNG_GLOBAL
|
||||
|
||||
|
||||
## World-metre HALF-EXTENT (radius from center to edge) a fixed-rung canvas
|
||||
## of `extent_cells` x `extent_cells` covers, given the rung's own gridunit
|
||||
## spacing — the request-sizing half of the cursor-anchored step math.
|
||||
static func half_extent_m(rung: String, extent_cells: int) -> float:
|
||||
return float(extent_cells) * 0.5 * spacing_for_rung(rung)
|
||||
static func half_extent_m(rung: String, extent_cells: Vector2i, body_radius_km: float) -> float:
|
||||
var spacing: float = spacing_for_rung(rung, extent_cells, body_radius_km)
|
||||
return float(mini(extent_cells.x, extent_cells.y)) * 0.5 * spacing
|
||||
|
||||
|
||||
## Cursor-anchored step center (D-255(a)/(e), the workshop's own "the center
|
||||
@@ -168,9 +215,13 @@ static func half_extent_m(rung: String, extent_cells: int) -> float:
|
||||
## world-metre point under the cursor. This is the point the NEXT step's
|
||||
## request should center on — computed once per scroll notch, not per frame.
|
||||
static func canvas_local_to_world_m(
|
||||
canvas_local: Vector2, world_center: Vector2, rung: String, extent_cells: Vector2i
|
||||
canvas_local: Vector2,
|
||||
world_center: Vector2,
|
||||
rung: String,
|
||||
extent_cells: Vector2i,
|
||||
body_radius_km: float
|
||||
) -> Vector2:
|
||||
var spacing: float = spacing_for_rung(rung)
|
||||
var spacing: float = spacing_for_rung(rung, extent_cells, body_radius_km)
|
||||
var ratio: float = display_ratio_for_rung(rung)
|
||||
var px_per_gridunit: float = maxf(ratio, 0.0001)
|
||||
var half_w_m: float = float(extent_cells.x) * 0.5 * spacing
|
||||
@@ -187,9 +238,13 @@ static func canvas_local_to_world_m(
|
||||
## display-time scale, this is that same linear map applied to a point
|
||||
## rather than a texture).
|
||||
static func world_m_to_canvas_local(
|
||||
world_m: Vector2, world_center: Vector2, rung: String, extent_cells: Vector2i
|
||||
world_m: Vector2,
|
||||
world_center: Vector2,
|
||||
rung: String,
|
||||
extent_cells: Vector2i,
|
||||
body_radius_km: float
|
||||
) -> Vector2:
|
||||
var spacing: float = spacing_for_rung(rung)
|
||||
var spacing: float = spacing_for_rung(rung, extent_cells, body_radius_km)
|
||||
var ratio: float = display_ratio_for_rung(rung)
|
||||
var px_per_gridunit: float = maxf(ratio, 0.0001)
|
||||
var half_w_m: float = float(extent_cells.x) * 0.5 * spacing
|
||||
@@ -302,14 +357,45 @@ static func viewport_fit_extent(viewport_px: Vector2, rung: String) -> Vector2i:
|
||||
## StepCanvasViewer's own cold-start fallback doc) leaves that axis
|
||||
## uncapped, matching the ticket's "request uncapped and let the server
|
||||
## clamp" fallback choice.
|
||||
## SUPERSEDED by the D-255 extent inversion (pair session 2026-07-26) — now a
|
||||
## documented no-op, pending Jeroen's sign-off to delete outright.
|
||||
##
|
||||
## Its premise was that a rung sharing Global's gridunit spacing would request
|
||||
## more of the body than exists (the "continent repeats sideways / rows smear
|
||||
## past the pole" defect on GJ1c). Post-inversion a canvas's cell count is
|
||||
## purely viewport-driven and its GROUND extent is the rung's own cell size,
|
||||
## so no rung can over-request a body by construction. The residual question —
|
||||
## whether a rung's cell is larger than the whole body — is not a cap but a
|
||||
## LIVENESS question, and it is answered by is_rung_live_on_body() below,
|
||||
## because the right response is to omit the rung from the ladder rather than
|
||||
## to serve a squashed canvas.
|
||||
static func cap_extent_to_body(
|
||||
extent: Vector2i, rung: String, global_extent: Vector2i
|
||||
extent: Vector2i, _rung: String, _global_extent: Vector2i
|
||||
) -> Vector2i:
|
||||
if not is_equal_approx(spacing_for_rung(rung), spacing_for_rung(RUNG_GLOBAL)):
|
||||
return extent
|
||||
var w: int = extent.x if global_extent.x <= 0 else mini(extent.x, global_extent.x)
|
||||
var h: int = extent.y if global_extent.y <= 0 else mini(extent.y, global_extent.y)
|
||||
return Vector2i(w, h)
|
||||
return extent
|
||||
|
||||
|
||||
## Is `rung` a meaningful step on this body? A rung whose cell is larger than
|
||||
## the body itself would zoom OUT rather than in, so it is omitted from the
|
||||
## ladder for that body — Jeroen's rule that the scroll walks the D-243 stair
|
||||
## "only omitting ones, but not inventing new rungs".
|
||||
##
|
||||
## Empirically this never fires on inhabited content: the smallest populated
|
||||
## body is a 734 km-radius moon whose 4,611 km circumference swallows a Region
|
||||
## cell seventeen times over, so all six rungs are live on all 271 populated
|
||||
## bodies with a radius. It fires only on uninhabited rocks (the two sub-region
|
||||
## bodies in systems.db are 6 km and 11 km radius). Kept as a guard precisely
|
||||
## because it is cheap and the failure it prevents is silent.
|
||||
##
|
||||
## A non-positive radius (the elastic seam has no input — e.g. an asteroid
|
||||
## belt, which is not a sphere and has no equirectangular surface at all)
|
||||
## leaves every rung live: the caller has bigger problems than ladder
|
||||
## liveness, and silently collapsing the ladder would hide them.
|
||||
static func is_rung_live_on_body(rung: String, body_radius_km: float) -> bool:
|
||||
var extent_m: float = float(RUNG_EXTENT_M.get(rung, 0.0))
|
||||
if extent_m <= 0.0 or body_radius_km <= 0.0:
|
||||
return true
|
||||
return extent_m < TAU * body_radius_km * 1000.0
|
||||
|
||||
|
||||
## WASD + arrow keys, read via Input.is_key_pressed() on the PHYSICAL keycode
|
||||
@@ -340,10 +426,17 @@ static func held_pan_direction() -> Vector2:
|
||||
## hits are the common case worth protecting). Global ignores center
|
||||
## entirely server-side (step_canvas_protocol.gd's own doc) so snapping is a
|
||||
## harmless no-op there.
|
||||
static func snap_to_gridunit(world_m: Vector2, rung: String) -> Vector2i:
|
||||
var spacing: float = spacing_for_rung(rung)
|
||||
if spacing <= 0.0:
|
||||
static func snap_to_gridunit(
|
||||
world_m: Vector2, rung: String, extent_cells: Vector2i, body_radius_km: float
|
||||
) -> Vector2i:
|
||||
var spacing: float = spacing_for_rung(rung, extent_cells, body_radius_km)
|
||||
# Post-inversion the pitch at the deep rungs is sub-metre (Chunk is ~0.12 m
|
||||
# at a 1080-px short axis), so the old `int(spacing)` multiplier truncated
|
||||
# to ZERO and collapsed every centre onto the origin. Snap in whole metres
|
||||
# instead — the request centre is an integer-metre wire field (i64) and a
|
||||
# metre is already finer than any rung's pitch is meaningful at.
|
||||
if spacing < 1.0:
|
||||
return Vector2i(int(round(world_m.x)), int(round(world_m.y)))
|
||||
return Vector2i(
|
||||
int(round(world_m.x / spacing)) * int(spacing), int(round(world_m.y / spacing)) * int(spacing)
|
||||
int(round(world_m.x / spacing) * spacing), int(round(world_m.y / spacing) * spacing)
|
||||
)
|
||||
|
||||
@@ -331,10 +331,27 @@ func get_body_id() -> String:
|
||||
return _dict_str(_body, "body_id", "")
|
||||
|
||||
|
||||
## Body radius in km — the elastic-seam input Global's gridunit spacing is
|
||||
## derived from (D-255 extent inversion). Already present in the `body`
|
||||
## dictionary `enter()` receives; surfaced here so the transport, the legend
|
||||
## and the annotation layer all read it from one place.
|
||||
func get_body_radius_km() -> float:
|
||||
return float(_body.get("body_radius_km", 0.0))
|
||||
|
||||
|
||||
func get_held_rung() -> String:
|
||||
return _held_rung
|
||||
|
||||
|
||||
## The ECHOED cell extent of the canvas currently on screen — not the
|
||||
## requested one. Since the D-255 extent inversion, gridunit spacing is
|
||||
## derived from this (spacing = rung extent / shorter axis), so any consumer
|
||||
## reporting or projecting in metres must read the extent the server actually
|
||||
## returned, never the one we asked for.
|
||||
func get_held_extent() -> Vector2i:
|
||||
return _held_extent
|
||||
|
||||
|
||||
## T-1183 test seam: exposes the owned StepCanvasRequest (and, through it,
|
||||
## get_disk_cache()) so sweep-trigger wiring is directly testable, matching
|
||||
## the get_cache()/get_disk_cache() accessor pattern StepCanvasRequest
|
||||
@@ -438,7 +455,9 @@ static func _count_distinct_settlements(canvas: Dictionary) -> int:
|
||||
## (ignored server-side, per step_canvas_protocol.gd's own doc).
|
||||
func _fire_request() -> void:
|
||||
var extent: Vector2i = _request_extent()
|
||||
var center: Vector2i = StepCanvasTransport.snap_to_gridunit(_world_center, _held_rung)
|
||||
var center: Vector2i = StepCanvasTransport.snap_to_gridunit(
|
||||
_world_center, _held_rung, extent, get_body_radius_km()
|
||||
)
|
||||
_request.request_now(get_body_id(), _held_rung, center, extent)
|
||||
|
||||
|
||||
@@ -520,7 +539,11 @@ func _pan_drift_fraction() -> float:
|
||||
func _refloat_now() -> void:
|
||||
var screen_center: Vector2 = get_rect().size * 0.5
|
||||
_world_center = StepCanvasTransport.canvas_local_to_world_m(
|
||||
screen_center - _view_offset, _world_center, _held_rung, _held_extent
|
||||
screen_center - _view_offset,
|
||||
_world_center,
|
||||
_held_rung,
|
||||
_held_extent,
|
||||
get_body_radius_km()
|
||||
)
|
||||
_view_offset = Vector2.ZERO
|
||||
_fire_request()
|
||||
@@ -549,7 +572,9 @@ func _on_canvas_ready(canvas: Dictionary) -> void:
|
||||
_global_body_extent = _held_extent
|
||||
_current_canvas_data = canvas
|
||||
_rebuild_terrain_texture(canvas)
|
||||
_annotation_layer.set_frame(canvas, _world_center, _held_rung, _held_extent)
|
||||
_annotation_layer.set_frame(
|
||||
canvas, _world_center, _held_rung, _held_extent, get_body_radius_km()
|
||||
)
|
||||
_recompute_canvas_transform()
|
||||
_refresh_screen_header()
|
||||
if _legend_panel:
|
||||
@@ -655,7 +680,11 @@ func _scroll_rung(direction: int, cursor_local: Vector2) -> void:
|
||||
_reset_to_global()
|
||||
return
|
||||
var cursor_world: Vector2 = StepCanvasTransport.canvas_local_to_world_m(
|
||||
cursor_local - _view_offset, _world_center, _held_rung, _held_extent
|
||||
cursor_local - _view_offset,
|
||||
_world_center,
|
||||
_held_rung,
|
||||
_held_extent,
|
||||
get_body_radius_km()
|
||||
)
|
||||
_rung_index = new_index
|
||||
_held_rung = StepCanvasTransport.rung_at_index(_rung_index)
|
||||
@@ -883,7 +912,9 @@ func _refresh_screen_header() -> void:
|
||||
if _screen_header == null:
|
||||
return
|
||||
var name_label: String = _dict_str(_body, "proper_name", _dict_str(_body, "body_id", "—"))
|
||||
var spacing_km: float = StepCanvasTransport.spacing_for_rung(_held_rung) / 1000.0
|
||||
var spacing_km: float = StepCanvasTransport.spacing_for_rung(
|
||||
_held_rung, _held_extent, get_body_radius_km()
|
||||
) / 1000.0
|
||||
var title := "ATLAS — %s" % name_label.to_upper()
|
||||
var subtitle := "%s · %.3f km/gridunit" % [_held_rung.to_upper(), spacing_km]
|
||||
_screen_header.set_content(title, subtitle)
|
||||
|
||||
Reference in New Issue
Block a user