fix shoreline slits beside claimed tiles and dropped edge-row facades

Two voxel-mode holes, both in ChunkMesher's keep/emit rules:

1. A tile claimed by a standing object (building footprint, sign
   standee, pond bushes) painted only its flat synthesized ground and
   never emitted side faces, so beside recessed water the 2px step was
   an open slit into the sky. The skip branch now emits the same
   below-ground side bands ordinary ground does, cut from the
   synthesized ground's art.

2. A profiled building whose front row is the map's last tile row has
   its facade exactly on the shared boundary plane (Saffron's row
   houses seen from Route 6), where the strict overBody test and the
   closed neighbour mask dropped it. Outward-facing quads on the body's
   boundary planes are now kept, told apart from ring scraps -- the
   thing the mask exists to kill -- by their winding.
This commit is contained in:
DramaticShape
2026-07-27 00:00:48 -04:00
parent fdcd5f1fce
commit cd3c4300ae
2 changed files with 84 additions and 2 deletions
+28
View File
@@ -1,5 +1,33 @@
# Changelog
## Unreleased
### Fixed
- **The shoreline no longer opens into the sky beside buildings and
signs.** Water recesses 2px below the ground, and the ground tile beside
it closes the step with a small below-ground side band -- but a tile
CLAIMED by a standing object (a building footprint, a sign standee, the
bushes ringing Fuchsia's ponds) only painted its synthesized flat ground
and never emitted sides. Along every stretch where such a tile met
water, the two-pixel step was an open slit straight through to the sky
behind the mesh. The skip branch now emits the same below-ground bands
ordinary ground does, cut from the synthesized ground's own art, so the
shoreline lip is continuous whatever stands on the bank.
- **Edge-row buildings keep their facades.** The south wall of Saffron's
row houses -- profiled buildings whose front row is the map's last tile
row -- lies exactly on the boundary plane shared with Route 6, and the
prebuilt-quad keep rules dropped it: the strict body test excludes the
plane, and the closed neighbour mask (which exists to kill ring scraps
whose rects sit exactly on that line) swallowed what was left, so from
Route 6 the houses stood hollow. The two cases are geometrically
identical degenerate rects, but they FACE opposite ways: a face pointing
away from the body is this map's own facade and nothing in the
neighbour will ever draw that plane, while a face pointing into the
body is the scrap the mask is for. The mesher now reads the winding and
keeps outward faces on the body's boundary planes, on all four edges.
## 1.0.3
### Added
+56 -2
View File
@@ -430,7 +430,36 @@ local function runGeometry(map, bodyOnly, masks, sink)
if s and S.skip[k] then
-- an object stands here; paint its synthesized ground and let the
-- prebuilt prism quads (appended below) carry the art
if S.ground[k] then topQuad(tx * 8, ty * 8, 0, S.ground[k], 1) end
local g = S.ground[k]
if g then
topQuad(tx * 8, ty * 8, 0, g, 1)
-- the claimed tile is still ground at height 0, and water next
-- door still recesses below it: without the same below-ground
-- side bands ordinary ground emits, the two-pixel shoreline
-- face is a slit into the sky behind the mesh -- which is
-- exactly what a building plot or a sign standing at the
-- waterline showed. Same bands, cut from the synthesized
-- ground's own art
for _, side in ipairs(SIDES) do
local nh = heightAt(tx + side[1], ty + side[2])
if nh < 0 then
local d = side[3]
local lat = LATERAL[d]
local hl = lat and heightAt(tx + lat[1], ty + lat[2]) or 0
local hr = lat and heightAt(tx + lat[3], ty + lat[4]) or 0
for band = math.floor(nh / 8), -1 do
local y0 = math.max(nh, band * 8)
local y1 = math.min(0, band * 8 + 8)
if y1 > y0 then
sideQuad(d, tx * 8, ty * 8, y0, y1, g,
(band * 8 + 8) - y1, (band * 8 + 8) - y0,
sideShades(hl, hr, y0, y1, y0 <= nh,
Voxel3D.FACE_SHADE[d]))
end
end
end
end
end
elseif s then
local run = S.runs[k]
local h = run and run.h or s.h
@@ -610,6 +639,31 @@ local function runGeometry(map, bodyOnly, masks, sink)
return overBody or not maskedClosed(x0, z0, x1, z1)
end
-- A face lying EXACTLY on a body boundary plane is ambiguous to the
-- rect tests above: a body structure's outward facade (a Saffron row
-- house whose front row is the map's last row, its south wall on the
-- shared plane with Route 6) and the inward face of a ring scrap
-- occupy the same degenerate rect, and the strict overBody plus the
-- closed mask dropped BOTH -- which is why those facades were missing.
-- The winding tells them apart: a face pointing AWAY from the body
-- belongs to this map's own edge-row structure and nothing in the
-- neighbour will ever draw that plane, so it stays; a face pointing
-- INTO the body is the scrap the mask rules exist to kill, and falls
-- through to them.
local function outwardOnEdge(q, x0, z0, x1, z1)
if z0 == z1 and (z0 == 0 or z0 == bh) and x1 > 0 and x0 < bw then
local nz = (q[2][1] - q[1][1]) * (q[3][2] - q[1][2])
- (q[2][2] - q[1][2]) * (q[3][1] - q[1][1])
return (z0 == bh and nz > 0) or (z0 == 0 and nz < 0)
end
if x0 == x1 and (x0 == 0 or x0 == bw) and z1 > 0 and z0 < bh then
local nx = (q[2][2] - q[1][2]) * (q[3][3] - q[1][3])
- (q[2][3] - q[1][3]) * (q[3][2] - q[1][2])
return (x0 == bw and nx > 0) or (x0 == 0 and nx < 0)
end
return false
end
local scUV = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }
local function quadUV(q)
if q.uv then return q.uv end
@@ -625,7 +679,7 @@ local function runGeometry(map, bodyOnly, masks, sink)
local x1 = math.max(q[1][1], q[2][1], q[3][1], q[4][1])
local z0 = math.min(q[1][3], q[2][3], q[3][3], q[4][3])
local z1 = math.max(q[1][3], q[2][3], q[3][3], q[4][3])
if keepQuad(x0, z0, x1, z1) then
if outwardOnEdge(q, x0, z0, x1, z1) or keepQuad(x0, z0, x1, z1) then
push({ q[1], q[2], q[3], q[4] }, quadUV(q), groundShades(q, q.shade))
end
end