From 2face3a43d3cb841501ac6004446162fde447449 Mon Sep 17 00:00:00 2001 From: Jeremy Collins Date: Mon, 13 Jul 2026 01:45:45 +0000 Subject: [PATCH] =?UTF-8?q?fix(bake):=20store=20complex=20linestyle=20runs?= =?UTF-8?q?=20=E2=80=94=20the=20baker=20never=20wrote=20them?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bake surface's vtable CLAIMED, in a comment, to store complex runs un-tessellated so replay could re-walk the period at the display's scale: // Bake path: store complex runs un-tessellated (no size_scale set — bake is // native; replay re-walks the period display-scaled). ...but .store_complex_run was never set on the vtable. So canStoreComplexRun() returned false, drawComplexLine tessellated the period at bake time at size_scale = 1, and no tile ever carried an ls_style — the replay path that re-walks them has been dead code. The bricks then scale with the display at render time while their SPACING stays frozen at 1x, so on a HiDPI display (size_scale ~3) the symbols swell into each other and a caution/restricted boundary reads as a smashed-up sawtooth instead of spaced marks. Wire storeComplexRun on the bake surface, writing the clipped run with its style id, phase (arc0), colour and width — exactly what replay.zig already reads. A rebaked Boston cell now carries CTNARE51/ENTRES51/NAVARE51 runs, and the brick count falls as size_scale rises (74 -> 40 -> 32), i.e. the spacing finally scales with the bricks. REQUIRES A REBAKE: the period lives in the tiles. --- src/scene/scene.zig | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/scene/scene.zig b/src/scene/scene.zig index 07c1f91..047d416 100644 --- a/src/scene/scene.zig +++ b/src/scene/scene.zig @@ -471,6 +471,7 @@ pub const TileSurface = struct { .endScene = endScene, // Bake path: store complex runs un-tessellated (no size_scale set — bake is // native; replay re-walks the period display-scaled). + .store_complex_run = storeComplexRun, }; pub fn init(a: Allocator, format: TileFormat) TileSurface { @@ -599,6 +600,29 @@ pub const TileSurface = struct { try s.pointsL().append(s.a, .{ .geom_type = .point, .parts = parts, .properties = props.items }); } + /// BAKE: store one clipped complex-linestyle run UN-TESSELLATED, tagged with its style id + /// and its phase (arc0), so replay can re-walk the period at the DISPLAY's size_scale. + /// + /// Without this the baker falls through to drawComplexRun and freezes the period at + /// size_scale = 1. The bricks then scale with the display at render time while their + /// SPACING does not, so on a HiDPI display (size_scale ~3) the symbols swell into each + /// other and the linestyle reads as a smashed-up sawtooth. The whole point of storing the + /// run is that spacing and brick size scale TOGETHER, and the baked tile stays + /// display-independent — replay.zig has always known how to read this; only the baker + /// never wrote it. + fn storeComplexRun(ctx: *anyopaque, ls_id: []const u8, color: rs.ColorToken, width_px: f64, arc0: f64, run: []const rs.TilePoint) anyerror!void { + const s = sp(ctx); + var props = std.ArrayList(mvt.Prop).empty; + try props.append(s.a, .{ .key = "ls_style", .value = .{ .string = ls_id } }); + try props.append(s.a, .{ .key = "ls_arc0", .value = .{ .double = arc0 } }); + try props.append(s.a, .{ .key = "color_token", .value = .{ .string = color } }); + try props.append(s.a, .{ .key = "width_px", .value = .{ .double = width_px } }); + try appendMeta(s.a, &props, s.cur); + const parts = try s.a.alloc([]const mvt.Point, 1); + parts[0] = run; + try s.linesL().append(s.a, .{ .geom_type = .linestring, .parts = parts, .properties = props.items }); + } + fn drawSounding(ctx: *anyopaque, depth_m: f64, swept: bool, low_acc: bool, at: rs.TilePoint) anyerror!void { const s = sp(ctx); var props = std.ArrayList(mvt.Prop).empty;