The land and its shore lie on the land tiles #135

Owner

Tracker: #3620

Step 4c of plan #3558. Where known land meets known water or lake, the land's waterline, its flat cap and its bank now lie inside the land tiles, as D4 asks, and the drawn shore follows the waterline. The woods cap and the highway still draw as on main; #3621 holds them to the flat cap.

What changes

  • Waterline (src/shared/contour.ts): landLoops' point on each land–water edge keeps the curve point's place along the middle half of the edge, and maps its offset across the edge into the WATERLINE band, 0.01–0.05 inside the land tile. The outline also turns through a vertex at each grid corner where it turns:
    • at a convex corner, 0.04 off both of the land tile's edges;
    • where the land wraps a water tile's corner, 0.03 off both of the water tile's edges, inside the diagonal land tile.
    • At a pinch, trace takes the same tile's next edge, so the outlines part there, each through its own convex corner vertex. On main it took the first unused edge and could cross over the pinch. This rule is in trace, so a woods outline at a woods pinch parts too. Otherwise forestLoops is main's.
  • Lip: landLoops returns LandLoops, each with lip, the flat cap's edge, vertex for vertex. On an edge it lies 0.05 farther inland than the waterline, 0.06–0.10 off the edge. At a convex corner it lies 0.1 off both edges, and at a wrapped corner 0.09 off both of the water tile's edges. So it is never more than 0.15/√2 off the water's edges. A point with no bank on either side keeps its lip on the waterline.
  • Bank (src/client/render/island.ts): the flat cap at TILE_TOP runs to the lip. BANK is a strip of 2 rings, where main's had 4, from the lip down to y = 0 on the waterline. BANK_REACH is gone. bankRuns returns index runs. A run that ends at a tile edge facing unexplored ground or the map edge runs on to the point past its end, so its bank closes the gap between the lip and the waterline there.
  • Shore (shore.ts, water.ts, ground.ts): the shore field is the signed distance to the waterline, positive inland up to SHORE_INLAND = 2 and negative over the water down to −SHORE_REACH. Its zero lies at the middle of the WATERLINE band, inside the coast tiles' edges.
    • Every texel lies wholly on one side of the waterline, so the field's raster is still exact.
    • The write and read margins shrink from 11 and 10 tiles to 10 and 9.
    • The water's foam and depth start at the waterline. The ground shader's beach is sand on the flat cap, wet within about 0.4 of the waterline and down the bank, and Toybox's ink runs along the bank's foot.

Tests

  • src/shared/contour.test.ts, over generated islands (seeds 1–8 that generate, at 64² and 128², Island and Land, Lakes 0, 0.25 and 1):
    • every waterline segment, sampled every 0.05 with its ends included, lies no more than 1e-3 inside a water or lake tile and within 0.15 of one;
    • every lip vertex inland of its waterline vertex lies no more than 0.15/√2 off the edges of the nearest water or lake tile: the larger of its two axis gaps to that tile;
    • hand-built maps pin a water corner the land wraps, a one-tile peninsula's two tip corners, and a pinch whose outlines part, each through its own convex corner vertex.
    • All four fail on the base's contour.ts and pass on the head. The lip check failed 343 edge lips at 0.109 when the band ran to 0.07, before the band narrowed to 0.05.
  • src/mock/island.test.ts, over the same maps:
    • every bank triangle is sampled every 0.05 along its edges and on a 0.05 grid over its area, and lies within the known land tiles to 1e-3;
    • every point of a 0.05 grid over each coast land tile, farther than 0.15 from every water or lake tile, lies inside the lip's outline.
    • The cap-area test now measures the land cap against the lip's area, both at the float32 positions the mesh holds. In float64, Earcut's triangles match the loops to 1e-11, but the mesh's float32 positions add up to 2e-3 on 128² maps with Lakes 1.
    • It counts only triangles that face down by more than float32 rounding. Earcut leaves zero-area triangles where three lip vertices lie on a straight diagonal staircase.
    • The task names src/client/render/island.test.ts, but src/client/boundary.test.ts refuses any /server/ import under src/client, generateIsland included. So the test sits beside the existing generated-island surface test.
    • The generated islands come from src/mock/islands.ts, which both tests import.
  • src/client/render/shore.test.ts: the field's zero crosses the known coast within 0.15 inside the land tiles' edge, at x 10.03, not 1.15 tiles out. The reveal-by-reveal test still matches the field built at once.
  • src/client/map.phone.ts: at 1×, in every finish and coast view, the published water points and wrapped-corner points beside bare land draw as water or foam. The land and convex-corner points draw as neither. The test's water-or-foam reading takes blue over red by more than 30, or the pale shallows and foam: green over red, blue no more than 15 under red, and every channel over 120.

changelog.md records it, and the "Shores and ground" bullet is rewritten in place.

Evidence

  • Base checks: the four new contour tests fail on the base's contour.ts. With the head's map.phone.ts, the base tree's coast-view test finds every water and wrapped-corner point beside bare land drawn as land, in every finish and view.

  • Mutation checks (a git archive copy of the head):

    • Pushing the bank's waterline ring out by twice the bank's width fails the bank test: 173,465 samples fall in the water.
    • Insetting the wrapped corner's lip by 0.13, past 0.15/√2, fails the cap test: 240 grid points are left off the cap.
  • Renders: the coast views at 1× and 0.5× in every finish with &grid, base b9556de against the head, from builds of each tree, at 653902c and again at 4c96d2e.

    • Every page drew without errors, with the same draw calls and 2,320 fewer triangles on both heads.
    • The coast now follows the tile grid. The 1-tile lake beside the street view's Factory, drawn as grass on the base, shows as water.
    • Narrow water reads mostly as foam, because the foam runs about 0.3 of a tile out from each shore.
    • Where the woods or the highway meet the water, they still reach past the new waterline. #3621 holds them.
  • Rebuild cost (Shore yard, Playwright WebKit, iPhone 15 landscape, on m5): 5 sessions × 7 interleaved runs per arm, built from git archive trees. Each figure is the median of the runs' rebuild means, in ms, at the final head 4c96d2e:

    arm rebuild landLoops buildSurface shore render
    main 672fc28, with the head's shore.ts 17.13 2.20 14.20 0.20 0.53
    base b9556de 10.33 2.13 7.60 0.20 0.53
    head 4c96d2e 11.67 2.47 8.53 0.20 0.53
    • The head is 5.47 ms under main's rebuild (phone estimate −41.0 ms) and 1.33 ms over the base's (phone estimate +10.0 ms). Every session's median lay within 0.3 ms of these figures.
    • The same bench at 653902c gave 17.07, 10.40 and 11.80.
    • main's arm differs from the head's shore.ts only by a local WOODS_SHADE = 0.9, because main's ground.ts does not export it.
    • The shore field's first build (18–19 ms) and reveal (0.4 ms) are the same on all three arms. map.phone.ts's 40 ms rebuild bound holds.
  • Map Yard bench (src/bench/map.ts, WebKit iPhone 15 landscape, on m5): 5 sessions × 7 interleaved runs, base b9556de against head 653902c, at rest and with ?doom=battle, ?doom=battle&spare, ?doom=nuke and ?doom=nuke&spare on each arm. Medians of 35 runs, ms a frame:

    view base head head − base (phone) battle peak Δ nuke peak Δ
    1× 1.020 1.020 +0.000 (+0.00) +0.000 −0.017
    0.5× 1.593 1.560 −0.033 (−0.25) +0.037 −0.003
    0.25× 1.957 1.990 +0.033 (+0.25) +0.023 −0.040
    • Each peak Δ is the head's doom − spare less the base's.
    • The head draws the same calls in every arm and view, with 3,049 fewer triangles.
    • The per-session head − base deltas ran from −0.060 to +0.040 ms.
    • 4c96d2e changes only the WATERLINE band's far bound, from 0.07 to 0.05, so it draws the same vertices and triangles as 653902c.
  • The step changes no play, so the AI matches and the net bench were not run.

  • Gates at 4c96d2e: sak-gate full run 5 passed: npm test 888/888 in 22.5 s, test:phone 180/180.

    • Runs 3 and 4 failed only on "Test timed out in 5000ms", in hud-css (text contrast), craft (two gunship landings) and collapses (no scale). That suite took 36–40 s, while my renders ran beside run 3 and another run started on the runner.
    • The three files run alone passed 30/30 in 1.9 s.
    • At 653902c, fast run 1 and full run 2 passed.
Tracker: [#3620](https://sak.swipelab.com/tasks/3620) Step 4c of plan [#3558](https://sak.swipelab.com/tasks/3558). Where known land meets known water or lake, the land's waterline, its flat cap and its bank now lie inside the land tiles, as D4 asks, and the drawn shore follows the waterline. The woods cap and the highway still draw as on main; [#3621](https://sak.swipelab.com/tasks/3621) holds them to the flat cap. ## What changes - **Waterline (src/shared/contour.ts):** `landLoops`' point on each land–water edge keeps the curve point's place along the middle half of the edge, and maps its offset across the edge into the `WATERLINE` band, 0.01–0.05 inside the land tile. The outline also turns through a vertex at each grid corner where it turns: - at a convex corner, 0.04 off both of the land tile's edges; - where the land wraps a water tile's corner, 0.03 off both of the water tile's edges, inside the diagonal land tile. - At a pinch, `trace` takes the same tile's next edge, so the outlines part there, each through its own convex corner vertex. On main it took the first unused edge and could cross over the pinch. This rule is in `trace`, so a woods outline at a woods pinch parts too. Otherwise `forestLoops` is main's. - **Lip:** `landLoops` returns `LandLoop`s, each with `lip`, the flat cap's edge, vertex for vertex. On an edge it lies 0.05 farther inland than the waterline, 0.06–0.10 off the edge. At a convex corner it lies 0.1 off both edges, and at a wrapped corner 0.09 off both of the water tile's edges. So it is never more than 0.15/√2 off the water's edges. A point with no bank on either side keeps its lip on the waterline. - **Bank (src/client/render/island.ts):** the flat cap at TILE_TOP runs to the lip. `BANK` is a strip of 2 rings, where main's had 4, from the lip down to y = 0 on the waterline. `BANK_REACH` is gone. `bankRuns` returns index runs. A run that ends at a tile edge facing unexplored ground or the map edge runs on to the point past its end, so its bank closes the gap between the lip and the waterline there. - **Shore (shore.ts, water.ts, ground.ts):** the shore field is the signed distance to the waterline, positive inland up to `SHORE_INLAND` = 2 and negative over the water down to −`SHORE_REACH`. Its zero lies at the middle of the `WATERLINE` band, inside the coast tiles' edges. - Every texel lies wholly on one side of the waterline, so the field's raster is still exact. - The write and read margins shrink from 11 and 10 tiles to 10 and 9. - The water's foam and depth start at the waterline. The ground shader's beach is sand on the flat cap, wet within about 0.4 of the waterline and down the bank, and Toybox's ink runs along the bank's foot. ## Tests - **src/shared/contour.test.ts**, over generated islands (seeds 1–8 that generate, at 64² and 128², Island and Land, Lakes 0, 0.25 and 1): - every waterline segment, sampled every 0.05 with its ends included, lies no more than 1e-3 inside a water or lake tile and within 0.15 of one; - every lip vertex inland of its waterline vertex lies no more than 0.15/√2 off the edges of the nearest water or lake tile: the larger of its two axis gaps to that tile; - hand-built maps pin a water corner the land wraps, a one-tile peninsula's two tip corners, and a pinch whose outlines part, each through its own convex corner vertex. - All four fail on the base's contour.ts and pass on the head. The lip check failed 343 edge lips at 0.109 when the band ran to 0.07, before the band narrowed to 0.05. - **src/mock/island.test.ts**, over the same maps: - every bank triangle is sampled every 0.05 along its edges and on a 0.05 grid over its area, and lies within the known land tiles to 1e-3; - every point of a 0.05 grid over each coast land tile, farther than 0.15 from every water or lake tile, lies inside the lip's outline. - The cap-area test now measures the land cap against the lip's area, both at the float32 positions the mesh holds. In float64, Earcut's triangles match the loops to 1e-11, but the mesh's float32 positions add up to 2e-3 on 128² maps with Lakes 1. - It counts only triangles that face down by more than float32 rounding. Earcut leaves zero-area triangles where three lip vertices lie on a straight diagonal staircase. - The task names src/client/render/island.test.ts, but src/client/boundary.test.ts refuses any `/server/` import under src/client, `generateIsland` included. So the test sits beside the existing generated-island surface test. - The generated islands come from src/mock/islands.ts, which both tests import. - **src/client/render/shore.test.ts:** the field's zero crosses the known coast within 0.15 inside the land tiles' edge, at x 10.03, not 1.15 tiles out. The reveal-by-reveal test still matches the field built at once. - **src/client/map.phone.ts:** at 1×, in every finish and coast view, the published water points and wrapped-corner points beside bare land draw as water or foam. The land and convex-corner points draw as neither. The test's water-or-foam reading takes blue over red by more than 30, or the pale shallows and foam: green over red, blue no more than 15 under red, and every channel over 120. changelog.md records it, and the "Shores and ground" bullet is rewritten in place. ## Evidence - **Base checks:** the four new contour tests fail on the base's contour.ts. With the head's map.phone.ts, the base tree's coast-view test finds every water and wrapped-corner point beside bare land drawn as land, in every finish and view. - **Mutation checks** (a `git archive` copy of the head): - Pushing the bank's waterline ring out by twice the bank's width fails the bank test: 173,465 samples fall in the water. - Insetting the wrapped corner's lip by 0.13, past 0.15/√2, fails the cap test: 240 grid points are left off the cap. - **Renders:** the coast views at 1× and 0.5× in every finish with `&grid`, base b9556de against the head, from builds of each tree, at 653902c and again at 4c96d2e. - Every page drew without errors, with the same draw calls and 2,320 fewer triangles on both heads. - The coast now follows the tile grid. The 1-tile lake beside the street view's Factory, drawn as grass on the base, shows as water. - Narrow water reads mostly as foam, because the foam runs about 0.3 of a tile out from each shore. - Where the woods or the highway meet the water, they still reach past the new waterline. #3621 holds them. - **Rebuild cost (Shore yard, Playwright WebKit, iPhone 15 landscape, on m5):** 5 sessions × 7 interleaved runs per arm, built from `git archive` trees. Each figure is the median of the runs' rebuild means, in ms, at the final head 4c96d2e: | arm | rebuild | landLoops | buildSurface | shore | render | |---|---|---|---|---|---| | main 672fc28, with the head's shore.ts | 17.13 | 2.20 | 14.20 | 0.20 | 0.53 | | base b9556de | 10.33 | 2.13 | 7.60 | 0.20 | 0.53 | | head 4c96d2e | 11.67 | 2.47 | 8.53 | 0.20 | 0.53 | - The head is 5.47 ms under main's rebuild (phone estimate −41.0 ms) and 1.33 ms over the base's (phone estimate +10.0 ms). Every session's median lay within 0.3 ms of these figures. - The same bench at 653902c gave 17.07, 10.40 and 11.80. - main's arm differs from the head's shore.ts only by a local `WOODS_SHADE = 0.9`, because main's ground.ts does not export it. - The shore field's first build (18–19 ms) and reveal (0.4 ms) are the same on all three arms. map.phone.ts's 40 ms rebuild bound holds. - **Map Yard bench (src/bench/map.ts, WebKit iPhone 15 landscape, on m5):** 5 sessions × 7 interleaved runs, base b9556de against head 653902c, at rest and with `?doom=battle`, `?doom=battle&spare`, `?doom=nuke` and `?doom=nuke&spare` on each arm. Medians of 35 runs, ms a frame: | view | base | head | head − base (phone) | battle peak Δ | nuke peak Δ | |---|---|---|---|---|---| | 1× | 1.020 | 1.020 | +0.000 (+0.00) | +0.000 | −0.017 | | 0.5× | 1.593 | 1.560 | −0.033 (−0.25) | +0.037 | −0.003 | | 0.25× | 1.957 | 1.990 | +0.033 (+0.25) | +0.023 | −0.040 | - Each peak Δ is the head's doom − spare less the base's. - The head draws the same calls in every arm and view, with 3,049 fewer triangles. - The per-session head − base deltas ran from −0.060 to +0.040 ms. - 4c96d2e changes only the `WATERLINE` band's far bound, from 0.07 to 0.05, so it draws the same vertices and triangles as 653902c. - The step changes no play, so the AI matches and the net bench were not run. - **Gates at 4c96d2e:** `sak-gate full` run 5 passed: `npm test` 888/888 in 22.5 s, `test:phone` 180/180. - Runs 3 and 4 failed only on "Test timed out in 5000ms", in hud-css (text contrast), craft (two gunship landings) and collapses (no scale). That suite took 36–40 s, while my renders ran beside run 3 and another run started on the runner. - The three files run alone passed 30/30 in 1.9 s. - At 653902c, `fast` run 1 and `full` run 2 passed.
agra merged commit b76502dc5c into task/3558-build-inside-the-city-and-a-flat-map 2026-10-23 18:22:56 +03:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
agra/mini-alert!135
No description provided.