From 3092ff20a3e89e58df01e5971b662dd745ac79ef Mon Sep 17 00:00:00 2001 From: OpenAI Codex Date: Sat, 19 Sep 2026 20:32:25 +0800 Subject: [PATCH] feat: publish Kyushu multi-light S-52 release --- .../CM95_S52_灯弧与多LIGHTS修复阶段报告_v2.md | 266 ++++++++++++++++++ web/kyushu_compare.html | 16 +- web/kyushu_compare_server.py | 6 +- 3 files changed, 278 insertions(+), 10 deletions(-) create mode 100644 docs/报告/CM95_S52_灯弧与多LIGHTS修复阶段报告_v2.md diff --git a/docs/报告/CM95_S52_灯弧与多LIGHTS修复阶段报告_v2.md b/docs/报告/CM95_S52_灯弧与多LIGHTS修复阶段报告_v2.md new file mode 100644 index 0000000..cf8ac41 --- /dev/null +++ b/docs/报告/CM95_S52_灯弧与多LIGHTS修复阶段报告_v2.md @@ -0,0 +1,266 @@ +# CM95 S-52 灯弧与多 LIGHTS 修复阶段报告 v2 + +日期:2026-09-19 + +## 1. 最终结论 + +本阶段已完成 LIGHTS05 灯弧缺失问题的两层修复: + +1. Web/MapLibre 侧补齐 S-52 CA screen-space renderer; +2. Builder 保留同一物理航标关联的全部 LIGHTS presentation,不再只保留一条“最丰富”的 LIGHTS。 + +新版九州 Production Release 已完成全量生产、独立 Source→PBF presentation conservation audit、finalize、release-scoped style 编译、CA runtime capability audit 和真实浏览器 multi-sector 验证。 + +Data Release 最终状态:**PASS**。 + +Target Web debug 的完整 runtime portrayal capability 仍为 **FAIL**,原因是部分 runtime kind 仍为 PREVIEW_ONLY / NOT_IMPLEMENTED;该结果是正确的严格审计结论,不影响本次 Data Release。 +## 2. 新版基线 + +Builder Contract: + +`cm95pbf-builder/0.4.11-s52-multi-light-presentations` + +关键提交: + +- `8ac774dff12bf2f267a9b926551c960528e16be9` — preserve S-52 multi-light arcs +- `06dc10abd9b46d2a69c3a75cc2f8a74478ce632e` — enforce S-52 semantic conservation audits +- `068efb18141c0a1117925f3bab66c5e16f7cdd76` — scope S-52 conservation audit to production footprint + +Production Plan ID: + +`1c27f0a66f6bacf4484925a4cc3f24d94d50dfd3a9c608fc34f817421b13e94b` + +Release ID: + +`cm95-d1-62a85bd40d3a-1c27f0a66f6b` +## 3. Production 最终结果 + +- planned tile jobs:5065 +- PASS:5065 +- FAILED:0 +- PBF files:5064 +- explicit empty tile:1 +- reports:5065 +- planned output features:476121 +- decoded PBF features:476121 +- tile footprint contract:PASS +- S-52 resource contract:PASS +- defined resources:1107 +- active resources:977 +- S-52 runtime data contract:PASS +- release failures:[] + +`semantic_completeness` 仍为 `PENDING_REVIEW`: + +- pending semantic variants:112 +- pending semantic feature rows:52828 + +该 backlog 与本次 LIGHTS / CA 数据守恒修复是不同维度。 +## 4. Multi-LIGHTS 数据守恒 + +Final release 内部的 tile-occurrence 对账: + +- report multi-light groups:357 +- PBF multi-light groups:357 +- report light presentation records:745 +- PBF light presentation records:745 +- failure count:0 + +这里的 357 / 745 是“跨 tile occurrence”计数,同一个稳定 Feature 可在多个 tile 中出现。 + +独立 Source→PBF 审计按稳定 Feature ID 去重后的结果: + +- expected multi-light groups:187 +- actual multi-light groups:187 +- expected presentation records:390 +- actual presentation records:390 +- missing groups:0 +- unexpected groups:0 +- presentation signature mismatch:0 +- tile occurrence presentation drift:0 +- tile occurrence scope mismatch:0 +- Source resolution failures:0 +- PBF parse failures:0 + +Presentation Conservation:**PASS**。 + +正式 artifact: + +`build/audits/cm95_s52_kyushu_v3_presentation_conservation.json` + +SHA-256: + +`34faff2314996e2daaceb57e7ad3411c5d8ab5e46fa48a874fdd7d66ae5df317` +## 5. 审计范围修正 + +第一版独立审计曾返回: + +- Source expected groups:193 +- PBF actual groups:187 +- Source expected presentations:403 +- PBF actual presentations:390 +- missing:17 +- unexpected:11 + +进一步调查确认这不是新版 PBF 再次丢数据,而是审计 expected scope 定义过宽。 + +Production 实际范围不是“AOI bbox 内所有允许 scale 的 Source record”,而是: + +`Production Plan + tile buffer + scale ladder + coverage mask + per-tile selected members` + +两个具体误差来源: + +1. 17 个 missing 全部来自 100k Source。它们虽然落在工程 AOI 内,但在对应 zoom 被 finer-scale coverage 遮掉,不会进入任何实际 tile。 +2. 11 个 unexpected 是合法 tile-buffer occurrence。对象位于裸 AOI 边缘之外,但进入了计划 tile 的 8px expanded bounds,因此 Production 正确输出。 + +审计器已改为逐 tile / 逐 native_scale 重放可见区域: + +- 使用与 plan 相同的 `buffer_tile_pixels`; +- finer coverage 先占用,coarser scale 使用 `coverage - occupied`; +- relation group 只使用该 tile、该 scale 当时实际 visible 的成员; +- base 选择在该 selected 集合内重新执行; +- expected tile occurrence set 与 PBF actual occurrence set 做严格等值比较。 + +修正后全九州审计 187/390 完全一致。 +## 6. CA 灯弧 Runtime + +CA renderer 按 OpenCPN / S-52 当前语义实现: + +- arc 半径为 screen mm; +- sector leg 半径为 screen mm; +- outline 与彩色弧分层绘制; +- sector start / end 转为屏幕方向; +- sector legs 以 screen-space 虚线绘制; +- 随 MapLibre move / rotate / resize 重绘; +- 不把 CA 预烘焙成地理 PBF geometry。 + +release-scoped style audit 的 runtime coverage: + +- RUNTIME_CA:1276 tile occurrences +- RUNTIME_DEPARE:47516 +- RUNTIME_DEPCNT:46189 +- RUNTIME_SOUNDG:146728 +- RUNTIME_OBSTRN:45633 +- RUNTIME_WRECKS:909 + +CA capability audit: + +- required kind:RUNTIME_CA +- status:IMPLEMENTED +- fidelity:S52_SCREEN_MM +- evidence failures:0 +- overall:**PASS** + +Artifact: + +`build/audits/cm95_s52_kyushu_v3_runtime_ca_capability.json` +## 7. 完整 Web Runtime Capability + +严格检查当前 Web compare target 的全部 runtime kinds: + +- RUNTIME_CA:IMPLEMENTED +- RUNTIME_DEPARE:PREVIEW_ONLY +- RUNTIME_DEPCNT:PREVIEW_ONLY +- RUNTIME_SOUNDG:PREVIEW_ONLY +- RUNTIME_OBSTRN:NOT_IMPLEMENTED +- RUNTIME_WRECKS:NOT_IMPLEMENTED + +因此完整 target runtime capability audit: + +**FAIL — runtime_capability_not_implemented** + +Artifact: + +`build/audits/cm95_s52_kyushu_v3_web_runtime_capability.json` + +这个 FAIL 不应被“修成 PASS”。它的作用就是阻止把当前 Web debug preview 描述为完整 S-52 runtime portrayal。 +## 8. Release-scoped Style + +生成: + +`build/s52_style_compiler_release_1c27f0a66f6b/style.s52.json` + +结果: + +- PBF count:5064 +- source layer count:28 +- feature count:476121 +- compiled layers:142 +- style bytes:1598139 +- resource contract:PASS +- hard_gate_failures:[] + +完整 sprite 继续使用: + +`build/s52_resources_v2/final/s52-sprite-full` +## 9. 双-sector 实际浏览器验证 + +目标灯标位置约: + +`130.78248061577605E, 34.09885081506925N` + +真实 headless Chrome + MapLibre 运行验证: + +- left style loaded:true +- source loaded:true +- 稳定 Feature ID:370384 +- `s52_light_presentations` 同时包含 record 3077 和 3078 + +Record 3077: + +- mode:sector +- color:LITYW +- arc:66° → 106° +- radius:15mm +- sector radius:25mm + +Record 3078: + +- mode:sector +- color:LITYW +- arc:116° → 320° +- radius:15mm +- sector radius:25mm + +CA runtime flatten 后目标 arc features:2。 + +当前视口总 lightArcFeatures:9。 + +Canvas non-transparent pixels:5920。 + +实际 WebGL + CA overlay 截图已生成: + +- `/tmp/cm95_v3_multi_light.png` +- `/tmp/cm95_v3_multi_light_left.png` + +因此原先“同一航标多 LIGHTS 只剩一个 sector”的问题已经在最终 release 中闭环验证。 +## 10. Web Compare 当前状态 + +18888 已切到: + +`cm95-d1-62a85bd40d3a-1c27f0a66f6b` + +Health 当前应返回: + +- release_status:PASS +- pbf_file_count:5064 +- decoded_pbf_features:476121 +- s52_runtime_contract:PASS +- s52_light_presentation_contract:PASS +- s52_presentation_conservation_contract:PASS + +页面左侧标识更新为: + +`Production v3 / 0.4.11` + +## 11. 最终判断 + +本阶段可以确认: + +1. 原 LIGHTS composite 数据损失根因已修复; +2. 所有进入最终 release 的 multi-LIGHTS presentation 在 Source→PBF signature 层面守恒; +3. tile occurrence scope 与 Production Plan 完全一致; +4. CA 灯弧 runtime renderer 已实现并实际绘制; +5. 双-sector 实例已真实浏览器验证; +6. Data Release 可以正式使用; +7. Web compare 仍不是完整 S-52 runtime target,剩余 runtime capability 必须继续逐项实现和审计。 diff --git a/web/kyushu_compare.html b/web/kyushu_compare.html index 8b5a2b2..b856d6d 100644 --- a/web/kyushu_compare.html +++ b/web/kyushu_compare.html @@ -44,7 +44,7 @@
九州 S-52 PBF 对比 - 左:Production v2 本地 PBF 右:extentfix 原始数据源 + 左:Production v3 / 0.4.11 本地 PBF 右:extentfix 原始数据源 @@ -63,7 +63,7 @@
-
左:九州 Production v2等待参考 style…
+
左:九州 Production v3 / 0.4.11等待参考 style…
点击任一侧海图,对比同一位置的 Feature。
LOCAL
@@ -478,8 +478,8 @@ function cloneReferenceForLocal(referenceStyle){ source.minzoom=5; source.maxzoom=12; source.bounds=[128.0,29.5,132.5,34.8]; - source.attribution="CM95 九州 S-52 Production Pipeline v2"; - style.name=(style.name || "NavSea") + " / Kyushu Production v2"; + source.attribution="CM95 九州 S-52 Production 0.4.11 multi-light"; + style.name=(style.name || "NavSea") + " / Kyushu Production v3 / 0.4.11"; return style; } @@ -492,7 +492,7 @@ function prepareS52Style(raw){ source.minzoom=5; source.maxzoom=12; source.bounds=[128.0,29.5,132.5,34.8]; - source.attribution="CM95 九州 S-52 Production Pipeline v2"; + source.attribution="CM95 九州 S-52 Production 0.4.11 multi-light"; style.sprite=location.origin + "/s52/sprite"; if(referenceStyleTemplate && referenceStyleTemplate.glyphs){ style.glyphs=referenceStyleTemplate.glyphs; @@ -550,9 +550,9 @@ leftMap.on("style.load",function(){ const sources=Object.keys((leftMap.getStyle() || {}).sources || {}); if(sources.includes("cm95")){ installRuntimePreviewLayers(leftMap); - setState(leftState,"Production v2 / S-52 style" + (debugRuntimePreview.checked ? " + runtime preview" : ""),"ok"); + setState(leftState,"Production v3 / 0.4.11 / S-52 style" + (debugRuntimePreview.checked ? " + runtime preview" : ""),"ok"); }else if(sources.includes("navsea_delivery")){ - setState(leftState,"Production v2 / extentfix style","ok"); + setState(leftState,"Production v3 / 0.4.11 / extentfix style","ok"); } applyDebugStyles(); refreshLightArcFeatures(); @@ -607,7 +607,7 @@ debugLeftS52.addEventListener("change",function(){ debugRuntimePreview.addEventListener("change",function(){ if(leftMap.getSource("cm95")){ installRuntimePreviewLayers(leftMap); - setState(leftState,"Production v2 / S-52 style" + (debugRuntimePreview.checked ? " + runtime preview" : ""),"ok"); + setState(leftState,"Production v3 / 0.4.11 / S-52 style" + (debugRuntimePreview.checked ? " + runtime preview" : ""),"ok"); applyDebugStyles(); } }); diff --git a/web/kyushu_compare_server.py b/web/kyushu_compare_server.py index 0c75c33..3acd8d6 100644 --- a/web/kyushu_compare_server.py +++ b/web/kyushu_compare_server.py @@ -7,7 +7,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from pathlib import Path from urllib.parse import urlparse -RELEASE_ID = "cm95-d1-62a85bd40d3a-2b2e7d976a43" +RELEASE_ID = "cm95-d1-62a85bd40d3a-1c27f0a66f6b" def parse_args(): @@ -28,7 +28,7 @@ def main(): manifest_path = release_dir / "release_manifest_v1.json" html_path = web_root / "kyushu_compare.html" assets_root = project_root / "build" / "fukuoka_s52_web" / "site" / "assets" - s52_style_path = project_root / "build" / "s52_style_compiler_release_2b2e7d976a43" / "style.s52.json" + s52_style_path = project_root / "build" / "s52_style_compiler_release_1c27f0a66f6b" / "style.s52.json" s52_sprite_root = project_root / "build" / "s52_resources_v2" / "final" required = [ @@ -73,6 +73,8 @@ def main(): "pbf_file_count": manifest.get("pbf_file_count"), "decoded_pbf_features": manifest.get("decoded_pbf_features"), "s52_runtime_contract": (manifest.get("s52_runtime_contract") or {}).get("status"), + "s52_light_presentation_contract": (manifest.get("s52_light_presentation_contract") or {}).get("status"), + "s52_presentation_conservation_contract": (manifest.get("s52_presentation_conservation_contract") or {}).get("status"), } self.send_bytes(json.dumps(payload, ensure_ascii=False).encode("utf-8"), "application/json; charset=utf-8") return