test(client): the chaining test outlived the chaining (T-1237)

4e503c356 deleted the client's _chain_runs() when the join moved to the
server, but left the test that pinned it. It asserted that three end-to-end
edges chain into one river, which is now precisely what must NOT happen, so
the gdUnit4 suite went red on main. Caught by the push gate; the parse sweep
I did run cannot see a behavioural assertion.

Replaced with the two properties that actually hold now, rather than dropped:

- A river's length is measured WHOLE. One course of four collinear points,
  each 1,500 m segment 5.9 px and under the 15 px floor, total 17.6 px and
  over it. That is the invariant the old test was really protecting — long
  rivers must not vanish because their pieces are individually small — and it
  survives the move to the server.

- Separate courses meeting end-to-end are NOT rejoined. This is the deleted
  chaining's headstone. Water splits a course, and D-261 is explicit that a
  river crossing a lake is two visible strokes, so a client that helpfully
  reconnected them would draw a line across the lake. Three touching
  sub-threshold courses must all be culled; a resurrected chaining pass would
  report one.

1837 tests, 0 failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-07 12:35:54 +02:00
co-authored by Claude Opus 5
parent 6147529fe8
commit 49812e3127
@@ -448,23 +448,53 @@ func _canvas_with_courses(courses: Array) -> Dictionary:
return {"width": 4, "height": 4, "courses": courses}
## A server "course" is an EDGE of the river network — the stretch between two
## confluences — not a river. Culling per course therefore culls per SEGMENT,
## and a long river assembled from many short edges vanishes entirely. Measured
## on Ferrath's Global canvas before chaining: 375 courses, 180 surviving the
## water clip, ZERO surviving the length cull. After chaining: 5 rivers.
func test_edges_chain_into_one_river_before_the_length_cull() -> void:
## The cull measures a WHOLE river, not its segments.
##
## A server course used to be one D8 hop, so culling per course culled per hop
## and a long river assembled from short pieces vanished entirely — measured on
## Ferrath's Global canvas: 375 courses, 180 surviving the water clip, ZERO
## surviving the length cull. The client briefly compensated by chaining runs
## end-to-end; that was the wrong layer and only half-worked (375 hops rejoined
## into 260 pieces, because each hop was warped independently so shared
## confluence points no longer coincided). The join moved to the server, which
## now emits one course per river (`river_course::build_paths`).
##
## So the property to hold here is no longer "chain the pieces" but "measure the
## arriving polyline as a whole": a course whose individual segments are each
## under the cull must still be kept when its total length clears it.
func test_a_rivers_length_is_measured_whole_not_per_segment() -> void:
var layer: StepCanvasAnnotationLayer = auto_free(StepCanvasAnnotationLayer.new())
add_child(layer)
# Three collinear edges, each meeting the next end-to-end. Sized so each
# is individually UNDER the cull and the chain is comfortably over it.
# At District with a 4x4 canvas the pitch is 512 m/gridunit and 2 px per
# gridunit, i.e. 256 m per screen px — so the 15 px floor is 3,840 m.
# 1,500 m per edge: 5.9 px alone (culled), 17.6 px chained (kept).
# ONE course of four collinear points. At District with a 4x4 canvas the
# pitch is 512 m/gridunit at 2 px per gridunit, i.e. 256 m per screen px, so
# the 15 px floor is 3,840 m. Each 1,500 m segment is 5.9 px on its own —
# under the floor — while the whole 4,500 m course is 17.6 px and must be
# kept. A per-segment cull would drop it entirely.
var step: float = 1500.0
var edges: Array = []
var points: Array = []
for i in range(4):
points.append([float(i) * step, 0.0])
var river: Array = [{"class": 2, "points": points}]
layer.set_frame(_canvas_with_courses(river), Vector2.ZERO, "District", Vector2i(4, 4), 0.0)
assert_int(layer.get_drawn_course_count()).override_failure_message(
"a river whose segments are each under the cull must survive on its TOTAL length"
).is_equal(1)
## Separate courses must NOT be rejoined, even when they meet end-to-end.
##
## This is the deleted chaining's headstone. Water splits a course, and D-261 is
## explicit that a river crossing a lake is genuinely two visible strokes — so a
## client that helpfully reconnected them would erase the lake. Three short
## collinear courses that touch are three sub-threshold rivers, and all three
## are culled; a resurrected chaining pass would report 1.
func test_separate_courses_meeting_end_to_end_are_not_rejoined() -> void:
var layer: StepCanvasAnnotationLayer = auto_free(StepCanvasAnnotationLayer.new())
add_child(layer)
var step: float = 1500.0
var courses: Array = []
for i in range(3):
edges.append(
courses.append(
{
"class": 2,
"points": [
@@ -473,10 +503,11 @@ func test_edges_chain_into_one_river_before_the_length_cull() -> void:
],
}
)
layer.set_frame(_canvas_with_courses(edges), Vector2.ZERO, "District", Vector2i(4, 4), 0.0)
layer.set_frame(_canvas_with_courses(courses), Vector2.ZERO, "District", Vector2i(4, 4), 0.0)
assert_int(layer.get_drawn_course_count()).override_failure_message(
"three end-to-end edges must chain into ONE river, not be culled as three segments"
).is_equal(1)
"three touching courses are three rivers, each under the cull — rejoining them "
+ "would draw one stroke straight across the water that separates them"
).is_equal(0)
## The cull itself: a river too short to read as a line is not drawn at all,