4e503c35656e9615cc8d51fbcd03dbce2d5ad3d2
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
474ab90663 |
feat(ui): rivers as cartographic strokes (D-261, T-1237)
Rivers now appear on the whole-body map for the first time. Five on Ferrath's
Global canvas, drawn as 5 px strokes that stop at the coastline.
Four rules, all client-side over existing server data, computed once on canvas
adoption rather than per draw:
- fixed 5 px screen-space stroke at every rung
- contiguous geometry through the river's own cells
- never drawn over water — ocean and lake end a run
- culled below 15 px of on-screen length (3x the stroke: below that a line
is a square, not a river)
TWO THINGS THE MEASUREMENT FOUND THAT THE RECORD DID NOT ANTICIPATE.
First, the cull unit was wrong. A server "course" is an EDGE of the river
network — the stretch between two confluences — not a river. Culling per
course culls per segment, so a long river assembled from many short edges
vanishes entirely. Measured on Ferrath Global: 375 courses, 180 surviving the
water clip, and ZERO surviving a per-course cull. Edges are now chained
end-to-end into rivers before the cull is applied, which also delivers the
other half of D-261's "contiguous": per-course contiguity only makes each edge
unbroken; joining is what makes a river read as one line rather than dashes.
After chaining, 5 rivers survive at Global — the "major systems only from
orbit" behaviour the record predicted, arrived at by a different route.
Second, and worse: uses_orbital_derive() still read `Global | Region` while
the client's mirror had said Global-only since 2026-07-26. The D-255 amendment
claims "Region left the orbital derive set... it now takes the full
courses-aware derive". That was implemented against the MIRROR and never
against the authority, so Region kept running envelope-only and carrying no
courses — the exact thing the amendment said it had stopped doing. Both test
suites stayed green for two days because neither compares itself to the other.
Fixed here, with a note on each side pointing at the other, since the two
cannot be cross-checked automatically.
Also removes the two gates that withheld courses from the orbital rung — the
reason the whole-body map had no rivers at all. Whether a course is worth
drawing is measured in screen pixels, which only the client knows, so the
server now supplies geometry at every rung and the client decides.
The capture harness reports "drawn" alongside "courses", because "375 courses
arrived" and "375 rivers are drawn" are different claims and conflating them
is what made an empty map look like a data problem.
Client suite 1836 / 1810 passed / 26 skipped. Server suite green.
Co-Authored-By: Claude <noreply@anthropic.com>
|
||
|
|
b2ef73256a |
fix(config): run-godot reported a suite that never ran as a pass
Found by walking into it. test_step_canvas_annotation_layer.gd had a parse error from an earlier edit in this session, so gdUnit4 could not load it and ran the other suites instead. The harness printed 3610 passed / 0 failed and exit 0. Fifty tests had not run for hours and nothing said so — the full suite reports 3660 with the file repaired, and that difference was invisible. Two states are now hard harness failures rather than test results: load_error — a suite failed to LOAD. Any pass count excludes it, so a green number is a lie. The hint names the offending file. no_tests — zero tests executed. A run that executes nothing can never be a pass; previously a mistyped --filter printed "Tests passed". Both add a "harness_error" field to the summary JSON and exit 2. The exit code cannot inherit gdUnit4's, which returns 0 in both states — that is precisely why they were invisible. Verified by injecting each failure rather than by reasoning about it. The load_error guard was checked in the case that actually matters: one broken file among many, where total stays large and failed stays zero. That run now reports 3610/0 WITH harness_error and exits 2, where before it was indistinguishable from success. Also repairs the file itself: a missed set_frame() argument (the parse error), and a cell-placement test still asserting pre-inversion spacing. Rewritten to assert the invariant that survives the extent inversion, the viewport aspect ratio and panning — half the SHORT axis is half a rung cell — instead of a literal. Two things it deliberately does not assert, both of which the previous version got wrong: "the corner is half a district away" holds only on a square canvas, and the canvas is one district WIDE without sitting ON a district. It is a free-floating window centred wherever the player panned; zoom is stepped, pan is continuous. A rung names a scale, not a cell you are inside. A second test pins that with a deliberately unaligned world centre, so a future change that snaps the canvas to the rung lattice — making pan step instead of slide — fails here. Pair session with Jeroen, 2026-07-27. Co-Authored-By: Claude <noreply@anthropic.com> |
||
|
|
0a0419abcc |
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> |
||
|
|
686021bee8 |
fix(ui): PR #207 review round — crop-gated taper, true mitres, hybrid AA (T-1175)
Five findings from the araminta+hoshe round. Tapering now fires only on a TRUE upstream source: the course's first raw world point is tested against the canvas's own world bounds (conservative 1m epsilon) — exact detection because the server crop keeps one point beyond the window (layer_proxy crop_course_to_window lo = first_in-1, contract documented), so crop passthroughs draw the old flat full-width cut and never a false headwater. The averaged-normal joint is replaced by a real mitre (half_w/cos(theta/2) recovered trig-free via the bisector normal), clamped by a 2x mitre limit AND 0.45x the shorter adjacent segment — restoring true perpendicular width at bends (the 29% pinch at confluences is gone) and preventing the hairpin bowtie; the winding doc now states the actual bounded guarantee. Antialiasing restored via the hybrid: only the varying-width taper head draws as a ribbon; the constant-width ~85% of every course keeps the original antialiased draw_polyline (byte-identical for untapered courses), split at an interpolated arc-length point sharing position and width — junction capture evidence in .cache/screenshots/t1175-fix-round/. Flat-fill single-element color array. Suite 24 -> 48 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
40cf89c3cb |
feat(ui): river source tapering + width-grammar retune — map fluency pass (T-1175)
The single large polish pass Jeroen requested off the T-1170 captures, benchmarked against RimWorld's world-map fluency. Courses now draw as source-tapered ribbons: per-vertex width ramps from a hairline at the upstream source to full class width over 15% of the course's arc length (arc-length parameterized, not vertex-indexed, so point density doesn't change the read), built as one draw_polygon ribbon with mitred joins. Class width table retuned 0.9/1.4/2.2 -> 0.6/1.2/2.4: a clean ~2x per-class ladder so a tributary-joins-trunk confluence reads as a join, trunk held at its visually-proven weight, stream thinner per the benchmark's thin/consistent/restrained grammar. Opacity untouched (Araminta's T-1170 ruling stands). Coast-gradient item resolved as already-correct (the coastal transition zones + elevation lightness render the shoreline band; capture-verified) — no wire change. Stipple assessment filed as T-1194. Eight new taper-geometry tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
d28d24fd26 |
feat(ui): step-canvas map component — RTT terrain + stepped zoom (D-255, T-1182)
The two-layer client rebuild per D-255(a)(b)(e), replacing the _canvas.scale continuous-zoom model with one viewer, one path, all six rungs: - step_canvas_protocol.gd: StepCanvasRequest/Response codec against the T-1181 wire contract — incl. the discovered png_bytes subtlety (rmp_serde without serde_bytes emits a msgpack int-array, not bin; decode repacks via PackedByteArray before load_png_from_buffer) and the extent-echo rule (read the server-clamped extent, never assume the requested one). - step_canvas/ component: transport (six-rung ladder, cursor-anchored scroll steps, edge-scroll/WASD pan with re-request on edge crossing, hard reset-to-Global), RTT terrain layer (Image.set_pixel colorize per the c1 measured ruling, texture.update reuse on step-cross, NEAREST coarse / LINEAR fine per rung), unscaled screen-space annotation sibling (courses + settlement markers at literal px), in-memory LRU cache (Tier 1; T-1183 layers the disk tiers beneath), request lifecycle (pending retry, staleness gate, extent echo). - Full _canvas.scale retirement in the same change: the zoom-scaled canvas model, the _zs compensation family, select_rung / MAX_COVERAGE_M / compute_tile_grid, the orbital-mosaic-vs-window two-path split, _view_zoom/_canonical_fit_zoom — 10 source files deleted; their 14 test suites deleted with them (T-1157 dead-goldens rule; replacement visual-capture coverage is re-scoped T-1157). - Surviving surfaces kept per the ticket: atlas_window_cache.gd's LRU shape (the ticket's named file atlas_window_tile_set.gd was the retiring orchestrator; the real LRU shape lives in atlas_window_cache.gd — cited in step_canvas_cache.gd), overlay colors, legend/overlay-bar chrome, AtlasViewer descend geometry. Determinism boundary per D-255(e): the client interpolates only within the closed server-supplied input set. 7 new gdUnit suites (164 cases) incl. a real extent-echo bug caught by its own test during implementation. Full client suite green (exit 0) with the live-gated suites running against a worktree server build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |