diff --git a/AGENTS.md b/AGENTS.md
index c8be928..a3e9f20 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -46,6 +46,13 @@ Current official order:
Do not treat a visual match as a pass if objects have already been dropped or wrongly merged.
+Preferred current 20nm object-preservation entry point:
+
+- [`navsea_object_preservation_audit.py`](/root/sourceserver/pbf/navsea_object_preservation_audit.py)
+- current reports:
+ - [`report/object_preservation_20nm/object_preservation_delivery_20nm.md`](/root/sourceserver/pbf/report/object_preservation_20nm/object_preservation_delivery_20nm.md)
+ - [`report/object_preservation_20nm/object_preservation_final_20nm.md`](/root/sourceserver/pbf/report/object_preservation_20nm/object_preservation_final_20nm.md)
+
## Current Visual Regression Baseline
The current working visual baseline is:
diff --git a/NavSea_Audit_Logic_And_Evolution_2026-04-01.md b/NavSea_Audit_Logic_And_Evolution_2026-04-01.md
index d265e19..f110d9b 100644
--- a/NavSea_Audit_Logic_And_Evolution_2026-04-01.md
+++ b/NavSea_Audit_Logic_And_Evolution_2026-04-01.md
@@ -135,6 +135,22 @@ Last Updated: 2026-04-01
- `fid=13025299` 这种具体对象为什么消失
+### D. Current 20nm Object-Preservation Entry Point
+
+当前已经补上一条专门的对象保真审计:
+
+- [`navsea_object_preservation_audit.py`](/root/sourceserver/pbf/navsea_object_preservation_audit.py)
+
+当前最重要的结论是:
+
+- 旧 `delivery_20nm` 的危险物对象虽然几何还在,但原始 `分類番号` 全部丢失
+- 当前 `final_20nm` 的危险物对象和原始 `分類番号` 已经完整保留下来
+
+代表报告:
+
+- [`report/object_preservation_20nm/object_preservation_delivery_20nm.md`](/root/sourceserver/pbf/report/object_preservation_20nm/object_preservation_delivery_20nm.md)
+- [`report/object_preservation_20nm/object_preservation_final_20nm.md`](/root/sourceserver/pbf/report/object_preservation_20nm/object_preservation_final_20nm.md)
+
## Audit Evolution
下面是审计方法升级到现在的过程。
@@ -403,4 +419,3 @@ Last Updated: 2026-04-01
当前项目的正式审计规则可以压成一句话:
- 先审“对象有没有丢”,再审“渲染有没有错”,最后才审“视觉像不像”。
-
diff --git a/STEP_RECORD.md b/STEP_RECORD.md
index 8cefe8f..a13396e 100644
--- a/STEP_RECORD.md
+++ b/STEP_RECORD.md
@@ -12,12 +12,12 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
当前页面版本体系:
-- `HTML: compare-r13-20260401-1105`
+- `HTML: compare-r14-20260401-1523`
当前页面支持 2 个 profile,但仍是同一张 HTML:
1. 唐津 20 海里
- - `Style: karatsu-final-style-r3-20260401-1105 (style.navsea-delivery-karatsu-20nm-final.json)`
+ - `Style: karatsu-final-style-r4-20260401-1523 (style.navsea-delivery-karatsu-20nm-final.json)`
- `PBF: karatsu-final-pbf-20nm-20260401-0248 (pbf-delivery-karatsu-20nm-final)`
2. 九州
@@ -55,16 +55,19 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
- `20nm pbf` 本身最近没有被重建改坏
- 鱼礁、灯塔、灯台等对象在 `20nm pbf` 里仍然存在
+- 当前 compare 页右侧使用的是 `20nm final PBF`,不是旧的 `20nm delivery`
- 当前“看起来又丢了”的主因优先判断为:
- style 表达式 / icon 选择问题
- 图层叠放和 icon-size 导致的近距离互相遮挡
- 不是 compare HTML 切错
- - 也不是 20nm pbf 最近重建
+ - 也不是 20nm pbf 最近重建坏了对象
当前新增结论:
- 紫色大 `X` 对应的不是 `428`
-- 它实际对应 `symbol-daytime-429`
+- 在当前 sprite 体系里:
+ - `409 -> symbol-daytime-409`
+ - `429 -> symbol-daytime-429`
- `428` 更像鱼礁/椭圆眼形符号
- 第二类“X 丢了”的问题,已经确认有一部分是危险物点与红绿小灯在近坐标重叠:
- 例如同一热点存在:
@@ -72,25 +75,48 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
- red light `30600002` at `[3013,524]`
- green light `30600001` at `[3029,505]`
- 这类问题更像图层叠放和当前 icon-size 过大,不是 PBF 缺对象
-- 但 `fid=13025299` 这类对象不是重叠问题:
- - 左侧原始版是 `p航行危険障害物 / 分類番号 409`
- - 当前右侧 `20nm final` 在同坐标附近没有等价保留下这个 `409`
- - 这是 `builder / mapping` 问题,不是单纯 style 问题
-- 当前审计没把它快速暴露出来的原因有两层:
- - `20nm` backend render audit 只能在层级总量上报:
- - `icon_missing_in_engineering | p航行危険障害物 | 664`
- - `extra_icon_in_engineering | navigation_hazard_point | 664`
- - 它提示“这一层有问题”,但不会自然收敛到 `fid=13025299`
- - AOI 小范围图像审计当前热点没有覆盖这块 `荒曽根/大曽根` 区域
-- 当前 builder 风险点:
- - `p航行危険障害物 / p投錨注意障害物` 的 hazard 语义推断太粗
- - 很多对象会被归并进 `hazard_mark / obstruction / fish_reef / wreck` 这些大桶
- - 原始 `分類番号 409` 这类细分对象没有独立保真规则
+- `fid=13025299` 这类对象的最新结论已经纠正:
+ - 左侧原始版是 `p航行危険障害物 / 分類番号 409 / 孤立危険物`
+ - 当前 `20nm final` 后台对象保真审计显示这类 `409` 在 final 里是完整保留下来的
+ - 当前问题已经收敛成:
+ - 旧 `20nm delivery` 没保留 `class_code`
+ - `20nm final style` 之前没有对 `navigation_hazard_point` 按 `class_code` 选 icon
+ - 也就是说,这批点现在的主问题是 `final style`,不是 `final PBF` 丢对象
一句话判断:
+- 当前 `20nm final` 这条线,危险物对象保真已经过关
- 当前主矛盾是 `style`
-- 不是 `PBF 不见了`
+
+## Object Preservation Audit
+
+当前已经新增一条专门的对象保真审计:
+
+- 脚本:
+ - [`navsea_object_preservation_audit.py`](/root/sourceserver/pbf/navsea_object_preservation_audit.py)
+- 报告:
+ - [`report/object_preservation_20nm/object_preservation_delivery_20nm.md`](/root/sourceserver/pbf/report/object_preservation_20nm/object_preservation_delivery_20nm.md)
+ - [`report/object_preservation_20nm/object_preservation_final_20nm.md`](/root/sourceserver/pbf/report/object_preservation_20nm/object_preservation_final_20nm.md)
+
+当前结论非常明确:
+
+- `delivery_20nm`
+ - raw objects: `2090`
+ - preserved: `0`
+ - raw class lost: `2090`
+- `final_20nm`
+ - raw objects: `2090`
+ - preserved: `2090`
+ - missing object: `0`
+ - raw class lost: `0`
+
+重点含义:
+
+- 旧 `20nm delivery` 的危险物细分类全部丢失
+- 当前 `20nm final` 的危险物细分类已经完整保留下来
+- `p航行危険障害物:409` 在 final 中是:
+ - raw=`25`
+ - preserved=`25`
## Final Release Candidate
@@ -152,7 +178,9 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
- 为了恢复危险物和灯标/浮标细节,重新带回了:
- `class_code`
- `display_code`
-- `r3` 已把危险物和航标 icon-size 收回旧版默认尺度,优先减轻危险物与灯标互相遮挡
+- `r4` 已补上:
+ - `navigation_hazard_point` 按 `class_code` 选 icon
+- 之前 `r3` 已把危险物和航标 icon-size 收回旧版默认尺度,优先减轻危险物与灯标互相遮挡
- 但当前 AOI 图像审计仍显示大差异
- 所以它目前只能算 `final release candidate`
- 还不能直接认定为稳定最终版
@@ -189,24 +217,31 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
- 先 `object preservation audit`
- 再 `render audit`
- 最后 `AOI / visual audit`
+- 其中 `20nm` 的危险物对象保真,当前已经有专门脚本和报告,不再只靠 render audit 猜
## Current Audit Status
### 20nm Visual Pilot
+当前新版 `r14 / style-r4` strict audit:
+
+- [`report/strict_audit_final_20nm_r4/strict_audit_summary.md`](/root/sourceserver/pbf/report/strict_audit_final_20nm_r4/strict_audit_summary.md)
+- changed ratio: `0.916883`
+
当前 3 个热点:
1. `takashima_main_harbor_marks`
- - changed ratio: `0.783759`
+ - changed ratio: `0.800170`
2. `takashima_inner_nearshore_hazards`
- - changed ratio: `0.744246`
+ - changed ratio: `0.711509`
3. `east_breakwater_marks`
- - changed ratio: `0.617833`
+ - changed ratio: `0.999713`
这说明:
- 视觉仍然没有收敛到“可推广”状态
- 主港航标区仍是第一优先级观察点
+- 近岸碍航与鱼礁区有一点改善,但离稳定还远
### 10nm Object-Level Backend Audit
diff --git a/navsea_object_preservation_audit.py b/navsea_object_preservation_audit.py
new file mode 100644
index 0000000..65f12da
--- /dev/null
+++ b/navsea_object_preservation_audit.py
@@ -0,0 +1,329 @@
+#!/usr/bin/env python3
+from __future__ import annotations
+
+import argparse
+import json
+from collections import Counter, defaultdict
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any
+
+import mapbox_vector_tile
+
+
+DEFAULT_RAW_ROOT = Path(
+ "/home/wwwroot/newpec/exported_auto/"
+ "tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles"
+)
+DEFAULT_DELIVERY_ROOT = Path("/home/wwwroot/pbf-delivery-karatsu-20nm")
+DEFAULT_FINAL_ROOT = Path("/home/wwwroot/pbf-delivery-karatsu-20nm-final")
+DEFAULT_REPORT_ROOT = Path("/root/sourceserver/pbf/report/object_preservation_20nm")
+
+HAZARD_LAYER_RULES = (
+ {
+ "raw_layer": "p航行危険障害物",
+ "delivery_layer": "navigation_hazard_point",
+ "raw_class_field": "分類番号",
+ "delivery_class_field": "class_code",
+ "label": "navigation hazards",
+ },
+ {
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class_field": "分類番号",
+ "delivery_class_field": "class_code",
+ "label": "anchor hazards",
+ },
+)
+
+MAX_EXAMPLES = 20
+
+
+@dataclass(frozen=True)
+class TileCoord:
+ z: int
+ x: int
+ y: int
+
+
+def canonical_json(value: Any) -> str:
+ return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
+
+
+def parse_at_name(at_raw: Any) -> str | None:
+ if not at_raw:
+ return None
+ try:
+ parsed = json.loads(at_raw)
+ except Exception:
+ return None
+ for item in parsed:
+ if not isinstance(item, list) or len(item) != 2:
+ continue
+ if item[0] in {"名称", "船名"}:
+ text = str(item[1]).strip()
+ if text:
+ return text
+ return None
+
+
+def geometry_signature(feature: dict[str, Any]) -> str:
+ geometry = feature.get("geometry") or {}
+ return canonical_json(geometry)
+
+
+def build_tile_index(tile_path: Path, rules: tuple[dict[str, str], ...]) -> dict[str, list[dict[str, Any]]]:
+ decoded = mapbox_vector_tile.decode(tile_path.read_bytes())
+ indexed: dict[str, list[dict[str, Any]]] = {}
+ for rule in rules:
+ indexed[rule["delivery_layer"]] = decoded.get(rule["delivery_layer"], {}).get("features", [])
+ indexed[rule["raw_layer"]] = decoded.get(rule["raw_layer"], {}).get("features", [])
+ return indexed
+
+
+def audit_one_target(
+ *,
+ target_name: str,
+ raw_root: Path,
+ target_root: Path,
+ report_root: Path,
+) -> tuple[Path, Path]:
+ report_root.mkdir(parents=True, exist_ok=True)
+ report_json = report_root / f"object_preservation_{target_name}.json"
+ report_md = report_root / f"object_preservation_{target_name}.md"
+
+ target_tiles = sorted(target_root.glob("*/*/*.pbf"))
+
+ totals = Counter()
+ rule_stats: dict[str, Counter[str]] = defaultdict(Counter)
+ class_stats: dict[str, Counter[str]] = defaultdict(Counter)
+ issue_examples: dict[str, list[dict[str, Any]]] = defaultdict(list)
+
+ for target_tile in target_tiles:
+ rel = target_tile.relative_to(target_root)
+ raw_tile = raw_root / rel
+ if not raw_tile.exists():
+ continue
+ totals["tiles_compared"] += 1
+ z = int(rel.parts[0])
+ x = int(rel.parts[1])
+ y = int(Path(rel.parts[2]).stem)
+ tile = TileCoord(z=z, x=x, y=y)
+ raw_index = build_tile_index(raw_tile, HAZARD_LAYER_RULES)
+ target_index = build_tile_index(target_tile, HAZARD_LAYER_RULES)
+
+ all_target_geoms: dict[str, list[tuple[str, dict[str, Any]]]] = defaultdict(list)
+ for rule in HAZARD_LAYER_RULES:
+ for feat in target_index[rule["delivery_layer"]]:
+ all_target_geoms[geometry_signature(feat)].append((rule["delivery_layer"], feat))
+
+ for rule in HAZARD_LAYER_RULES:
+ raw_layer = rule["raw_layer"]
+ delivery_layer = rule["delivery_layer"]
+ raw_class_field = rule["raw_class_field"]
+ delivery_class_field = rule["delivery_class_field"]
+ key = f"{raw_layer}->{delivery_layer}"
+
+ target_by_geom: dict[str, list[dict[str, Any]]] = defaultdict(list)
+ for feat in target_index[delivery_layer]:
+ target_by_geom[geometry_signature(feat)].append(feat)
+
+ for raw_feat in raw_index[raw_layer]:
+ totals["raw_objects"] += 1
+ rule_stats[key]["raw_objects"] += 1
+ raw_props = raw_feat.get("properties", {})
+ raw_class = raw_props.get(raw_class_field)
+ class_key = f"{raw_layer}:{raw_class}"
+ geom_sig = geometry_signature(raw_feat)
+ name = raw_props.get("名称") or parse_at_name(raw_props.get("at"))
+ candidates = target_by_geom.get(geom_sig, [])
+ matched_class = False
+
+ if not candidates:
+ other_layer_hits = all_target_geoms.get(geom_sig, [])
+ issue = "missing_object"
+ if other_layer_hits:
+ issue = "wrong_relayer"
+ totals[issue] += 1
+ rule_stats[key][issue] += 1
+ class_stats[class_key][issue] += 1
+ if len(issue_examples[issue]) < MAX_EXAMPLES:
+ issue_examples[issue].append(
+ {
+ "tile": [tile.z, tile.x, tile.y],
+ "fid": raw_props.get("fid"),
+ "name": name,
+ "raw_layer": raw_layer,
+ "delivery_layer": delivery_layer,
+ "raw_class": raw_class,
+ "other_layer_hits": [
+ {
+ "delivery_layer": layer_name,
+ "delivery_class": feat.get("properties", {}).get(delivery_class_field),
+ "canonical_object_type": feat.get("properties", {}).get("canonical_object_type"),
+ }
+ for layer_name, feat in other_layer_hits[:5]
+ ],
+ }
+ )
+ continue
+
+ for candidate in candidates:
+ target_props = candidate.get("properties", {})
+ if target_props.get(delivery_class_field) == raw_class:
+ matched_class = True
+ break
+
+ if matched_class:
+ totals["preserved"] += 1
+ rule_stats[key]["preserved"] += 1
+ class_stats[class_key]["preserved"] += 1
+ continue
+
+ if all(candidate.get("properties", {}).get(delivery_class_field) is None for candidate in candidates):
+ issue = "raw_class_lost"
+ else:
+ issue = "class_mismatch"
+
+ totals[issue] += 1
+ rule_stats[key][issue] += 1
+ class_stats[class_key][issue] += 1
+ if len(issue_examples[issue]) < MAX_EXAMPLES:
+ issue_examples[issue].append(
+ {
+ "tile": [tile.z, tile.x, tile.y],
+ "fid": raw_props.get("fid"),
+ "name": name,
+ "raw_layer": raw_layer,
+ "delivery_layer": delivery_layer,
+ "raw_class": raw_class,
+ "candidates": [
+ {
+ "delivery_class": candidate.get("properties", {}).get(delivery_class_field),
+ "canonical_object_type": candidate.get("properties", {}).get("canonical_object_type"),
+ "chart_symbol_code": candidate.get("properties", {}).get("chart_symbol_code"),
+ "chart_icon_image": candidate.get("properties", {}).get("chart_icon_image"),
+ }
+ for candidate in candidates[:5]
+ ],
+ }
+ )
+
+ top_class_issues = []
+ for class_key, stats in class_stats.items():
+ top_class_issues.append(
+ {
+ "class_key": class_key,
+ "raw_objects": sum(stats.values()),
+ "preserved": stats.get("preserved", 0),
+ "missing_object": stats.get("missing_object", 0),
+ "wrong_relayer": stats.get("wrong_relayer", 0),
+ "raw_class_lost": stats.get("raw_class_lost", 0),
+ "class_mismatch": stats.get("class_mismatch", 0),
+ }
+ )
+ top_class_issues.sort(
+ key=lambda item: (
+ item["missing_object"] + item["wrong_relayer"] + item["raw_class_lost"] + item["class_mismatch"],
+ item["raw_objects"],
+ ),
+ reverse=True,
+ )
+
+ payload = {
+ "target_name": target_name,
+ "raw_root": str(raw_root),
+ "target_root": str(target_root),
+ "totals": dict(totals),
+ "rule_stats": {key: dict(stats) for key, stats in sorted(rule_stats.items())},
+ "top_class_issues": top_class_issues[:20],
+ "issue_examples": issue_examples,
+ }
+ report_json.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
+
+ lines = [
+ f"# Object Preservation Audit: {target_name}",
+ "",
+ f"- raw root: `{raw_root}`",
+ f"- target root: `{target_root}`",
+ "",
+ "## Totals",
+ "",
+ f"- tiles compared: `{totals.get('tiles_compared', 0)}`",
+ f"- raw objects: `{totals.get('raw_objects', 0)}`",
+ f"- preserved: `{totals.get('preserved', 0)}`",
+ f"- missing object: `{totals.get('missing_object', 0)}`",
+ f"- wrong relayer: `{totals.get('wrong_relayer', 0)}`",
+ f"- raw class lost: `{totals.get('raw_class_lost', 0)}`",
+ f"- class mismatch: `{totals.get('class_mismatch', 0)}`",
+ "",
+ "## Per Rule",
+ "",
+ ]
+ for key, stats in sorted(rule_stats.items()):
+ lines.extend(
+ [
+ f"### `{key}`",
+ "",
+ f"- raw objects: `{stats.get('raw_objects', 0)}`",
+ f"- preserved: `{stats.get('preserved', 0)}`",
+ f"- missing object: `{stats.get('missing_object', 0)}`",
+ f"- wrong relayer: `{stats.get('wrong_relayer', 0)}`",
+ f"- raw class lost: `{stats.get('raw_class_lost', 0)}`",
+ f"- class mismatch: `{stats.get('class_mismatch', 0)}`",
+ "",
+ ]
+ )
+
+ lines.extend(["## Top Class Issues", ""])
+ for item in top_class_issues[:12]:
+ lines.append(
+ "- `{class_key}` raw=`{raw_objects}` preserved=`{preserved}` missing=`{missing_object}` "
+ "wrong_relayer=`{wrong_relayer}` class_lost=`{raw_class_lost}` class_mismatch=`{class_mismatch}`".format(
+ **item
+ )
+ )
+ lines.append("")
+
+ for issue_name, examples in issue_examples.items():
+ if not examples:
+ continue
+ lines.extend([f"## Examples: `{issue_name}`", ""])
+ for example in examples:
+ lines.append(f"- {canonical_json(example)}")
+ lines.append("")
+
+ report_md.write_text("\n".join(lines), encoding="utf-8")
+ return report_md, report_json
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ description="Audit raw-object preservation for hazard layers between original and delivery/final PBFs."
+ )
+ parser.add_argument("--raw-root", type=Path, default=DEFAULT_RAW_ROOT)
+ parser.add_argument("--delivery-root", type=Path, default=DEFAULT_DELIVERY_ROOT)
+ parser.add_argument("--final-root", type=Path, default=DEFAULT_FINAL_ROOT)
+ parser.add_argument("--report-root", type=Path, default=DEFAULT_REPORT_ROOT)
+ return parser.parse_args()
+
+
+def main() -> None:
+ args = parse_args()
+ targets = (
+ ("delivery_20nm", args.delivery_root),
+ ("final_20nm", args.final_root),
+ )
+ for target_name, target_root in targets:
+ report_md, report_json = audit_one_target(
+ target_name=target_name,
+ raw_root=args.raw_root,
+ target_root=target_root,
+ report_root=args.report_root,
+ )
+ print(f"wrote {report_md}")
+ print(f"wrote {report_json}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/report/object_preservation_20nm/object_preservation_delivery_20nm.json b/report/object_preservation_20nm/object_preservation_delivery_20nm.json
new file mode 100644
index 0000000..429a4f0
--- /dev/null
+++ b/report/object_preservation_20nm/object_preservation_delivery_20nm.json
@@ -0,0 +1,606 @@
+{
+ "target_name": "delivery_20nm",
+ "raw_root": "/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles",
+ "target_root": "/home/wwwroot/pbf-delivery-karatsu-20nm",
+ "totals": {
+ "tiles_compared": 154,
+ "raw_objects": 2090,
+ "raw_class_lost": 2090
+ },
+ "rule_stats": {
+ "p投錨注意障害物->anchor_caution_hazard_point": {
+ "raw_objects": 1426,
+ "raw_class_lost": 1426
+ },
+ "p航行危険障害物->navigation_hazard_point": {
+ "raw_objects": 664,
+ "raw_class_lost": 664
+ }
+ },
+ "top_class_issues": [
+ {
+ "class_key": "p投錨注意障害物:428",
+ "raw_objects": 702,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 702,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:404",
+ "raw_objects": 413,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 413,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:420",
+ "raw_objects": 355,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 355,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:425",
+ "raw_objects": 255,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 255,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:403",
+ "raw_objects": 128,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 128,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:432",
+ "raw_objects": 40,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 40,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:421",
+ "raw_objects": 32,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 32,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:415",
+ "raw_objects": 25,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 25,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:413",
+ "raw_objects": 25,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 25,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:409",
+ "raw_objects": 25,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 25,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:431",
+ "raw_objects": 22,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 22,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:434",
+ "raw_objects": 18,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 18,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:433",
+ "raw_objects": 15,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 15,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:427",
+ "raw_objects": 9,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 9,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:405",
+ "raw_objects": 5,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 5,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:412",
+ "raw_objects": 5,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 5,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:422",
+ "raw_objects": 4,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 4,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:424",
+ "raw_objects": 3,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 3,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:402",
+ "raw_objects": 3,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 3,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:410",
+ "raw_objects": 3,
+ "preserved": 0,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 3,
+ "class_mismatch": 0
+ }
+ ],
+ "issue_examples": {
+ "raw_class_lost": [
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018869,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018870,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018918,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13020459,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13020673,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13020674,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13020675,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13020697,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13020698,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13020699,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 428,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "魚礁",
+ "chart_symbol_code": "fish_reef",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018650,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018651,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018652,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018653,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018654,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018655,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018656,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018657,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018658,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ },
+ {
+ "tile": [
+ 11,
+ 1761,
+ 819
+ ],
+ "fid": 13018659,
+ "name": null,
+ "raw_layer": "p投錨注意障害物",
+ "delivery_layer": "anchor_caution_hazard_point",
+ "raw_class": 425,
+ "candidates": [
+ {
+ "delivery_class": null,
+ "canonical_object_type": "海草",
+ "chart_symbol_code": "hazard_mark",
+ "chart_icon_image": "symbol-daytime-428"
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/report/object_preservation_20nm/object_preservation_delivery_20nm.md b/report/object_preservation_20nm/object_preservation_delivery_20nm.md
new file mode 100644
index 0000000..c7e17ea
--- /dev/null
+++ b/report/object_preservation_20nm/object_preservation_delivery_20nm.md
@@ -0,0 +1,72 @@
+# Object Preservation Audit: delivery_20nm
+
+- raw root: `/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
+- target root: `/home/wwwroot/pbf-delivery-karatsu-20nm`
+
+## Totals
+
+- tiles compared: `154`
+- raw objects: `2090`
+- preserved: `0`
+- missing object: `0`
+- wrong relayer: `0`
+- raw class lost: `2090`
+- class mismatch: `0`
+
+## Per Rule
+
+### `p投錨注意障害物->anchor_caution_hazard_point`
+
+- raw objects: `1426`
+- preserved: `0`
+- missing object: `0`
+- wrong relayer: `0`
+- raw class lost: `1426`
+- class mismatch: `0`
+
+### `p航行危険障害物->navigation_hazard_point`
+
+- raw objects: `664`
+- preserved: `0`
+- missing object: `0`
+- wrong relayer: `0`
+- raw class lost: `664`
+- class mismatch: `0`
+
+## Top Class Issues
+
+- `p投錨注意障害物:428` raw=`702` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`702` class_mismatch=`0`
+- `p航行危険障害物:404` raw=`413` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`413` class_mismatch=`0`
+- `p投錨注意障害物:420` raw=`355` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`355` class_mismatch=`0`
+- `p投錨注意障害物:425` raw=`255` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`255` class_mismatch=`0`
+- `p航行危険障害物:403` raw=`128` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`128` class_mismatch=`0`
+- `p航行危険障害物:432` raw=`40` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`40` class_mismatch=`0`
+- `p投錨注意障害物:421` raw=`32` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`32` class_mismatch=`0`
+- `p投錨注意障害物:415` raw=`25` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`25` class_mismatch=`0`
+- `p投錨注意障害物:413` raw=`25` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`25` class_mismatch=`0`
+- `p航行危険障害物:409` raw=`25` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`25` class_mismatch=`0`
+- `p航行危険障害物:431` raw=`22` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`22` class_mismatch=`0`
+- `p投錨注意障害物:434` raw=`18` preserved=`0` missing=`0` wrong_relayer=`0` class_lost=`18` class_mismatch=`0`
+
+## Examples: `raw_class_lost`
+
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018869,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018870,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018918,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13020459,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13020673,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13020674,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13020675,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13020697,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13020698,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"魚礁","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"fish_reef","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13020699,"name":null,"raw_class":428,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018650,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018651,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018652,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018653,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018654,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018655,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018656,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018657,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018658,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
+- {"candidates":[{"canonical_object_type":"海草","chart_icon_image":"symbol-daytime-428","chart_symbol_code":"hazard_mark","delivery_class":null}],"delivery_layer":"anchor_caution_hazard_point","fid":13018659,"name":null,"raw_class":425,"raw_layer":"p投錨注意障害物","tile":[11,1761,819]}
diff --git a/report/object_preservation_20nm/object_preservation_final_20nm.json b/report/object_preservation_20nm/object_preservation_final_20nm.json
new file mode 100644
index 0000000..fc808d7
--- /dev/null
+++ b/report/object_preservation_20nm/object_preservation_final_20nm.json
@@ -0,0 +1,203 @@
+{
+ "target_name": "final_20nm",
+ "raw_root": "/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles",
+ "target_root": "/home/wwwroot/pbf-delivery-karatsu-20nm-final",
+ "totals": {
+ "tiles_compared": 154,
+ "raw_objects": 2090,
+ "preserved": 2090
+ },
+ "rule_stats": {
+ "p投錨注意障害物->anchor_caution_hazard_point": {
+ "raw_objects": 1426,
+ "preserved": 1426
+ },
+ "p航行危険障害物->navigation_hazard_point": {
+ "raw_objects": 664,
+ "preserved": 664
+ }
+ },
+ "top_class_issues": [
+ {
+ "class_key": "p投錨注意障害物:428",
+ "raw_objects": 702,
+ "preserved": 702,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:404",
+ "raw_objects": 413,
+ "preserved": 413,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:420",
+ "raw_objects": 355,
+ "preserved": 355,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:425",
+ "raw_objects": 255,
+ "preserved": 255,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:403",
+ "raw_objects": 128,
+ "preserved": 128,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:432",
+ "raw_objects": 40,
+ "preserved": 40,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:421",
+ "raw_objects": 32,
+ "preserved": 32,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:415",
+ "raw_objects": 25,
+ "preserved": 25,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:413",
+ "raw_objects": 25,
+ "preserved": 25,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:409",
+ "raw_objects": 25,
+ "preserved": 25,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:431",
+ "raw_objects": 22,
+ "preserved": 22,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:434",
+ "raw_objects": 18,
+ "preserved": 18,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:433",
+ "raw_objects": 15,
+ "preserved": 15,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:427",
+ "raw_objects": 9,
+ "preserved": 9,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:405",
+ "raw_objects": 5,
+ "preserved": 5,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:412",
+ "raw_objects": 5,
+ "preserved": 5,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:422",
+ "raw_objects": 4,
+ "preserved": 4,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p投錨注意障害物:424",
+ "raw_objects": 3,
+ "preserved": 3,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:402",
+ "raw_objects": 3,
+ "preserved": 3,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ },
+ {
+ "class_key": "p航行危険障害物:410",
+ "raw_objects": 3,
+ "preserved": 3,
+ "missing_object": 0,
+ "wrong_relayer": 0,
+ "raw_class_lost": 0,
+ "class_mismatch": 0
+ }
+ ],
+ "issue_examples": {}
+}
\ No newline at end of file
diff --git a/report/object_preservation_20nm/object_preservation_final_20nm.md b/report/object_preservation_20nm/object_preservation_final_20nm.md
new file mode 100644
index 0000000..4f4f48c
--- /dev/null
+++ b/report/object_preservation_20nm/object_preservation_final_20nm.md
@@ -0,0 +1,49 @@
+# Object Preservation Audit: final_20nm
+
+- raw root: `/home/wwwroot/newpec/exported_auto/tile.mapple-on.jp__newpec-mvt-20260106__z___x___y_.pbf/tiles`
+- target root: `/home/wwwroot/pbf-delivery-karatsu-20nm-final`
+
+## Totals
+
+- tiles compared: `154`
+- raw objects: `2090`
+- preserved: `2090`
+- missing object: `0`
+- wrong relayer: `0`
+- raw class lost: `0`
+- class mismatch: `0`
+
+## Per Rule
+
+### `p投錨注意障害物->anchor_caution_hazard_point`
+
+- raw objects: `1426`
+- preserved: `1426`
+- missing object: `0`
+- wrong relayer: `0`
+- raw class lost: `0`
+- class mismatch: `0`
+
+### `p航行危険障害物->navigation_hazard_point`
+
+- raw objects: `664`
+- preserved: `664`
+- missing object: `0`
+- wrong relayer: `0`
+- raw class lost: `0`
+- class mismatch: `0`
+
+## Top Class Issues
+
+- `p投錨注意障害物:428` raw=`702` preserved=`702` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p航行危険障害物:404` raw=`413` preserved=`413` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p投錨注意障害物:420` raw=`355` preserved=`355` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p投錨注意障害物:425` raw=`255` preserved=`255` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p航行危険障害物:403` raw=`128` preserved=`128` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p航行危険障害物:432` raw=`40` preserved=`40` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p投錨注意障害物:421` raw=`32` preserved=`32` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p投錨注意障害物:415` raw=`25` preserved=`25` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p投錨注意障害物:413` raw=`25` preserved=`25` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p航行危険障害物:409` raw=`25` preserved=`25` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p航行危険障害物:431` raw=`22` preserved=`22` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
+- `p投錨注意障害物:434` raw=`18` preserved=`18` missing=`0` wrong_relayer=`0` class_lost=`0` class_mismatch=`0`
diff --git a/report/strict_audit_final_20nm_r4/strict_audit_summary.json b/report/strict_audit_final_20nm_r4/strict_audit_summary.json
new file mode 100644
index 0000000..a78124b
--- /dev/null
+++ b/report/strict_audit_final_20nm_r4/strict_audit_summary.json
@@ -0,0 +1,323 @@
+{
+ "compare_url": "http://192.168.200.184/newpec/navsea-compare-karatsu-20nm.html",
+ "compare_version": "compare-r14-20260401-1523",
+ "backend_audit_json": "/root/sourceserver/pbf/NavSea_Original_vs_Delivery_Render_Audit_Karatsu_20nm_2026-03-31.r7.json",
+ "visual_metrics": {
+ "image_width": 640,
+ "image_height": 400,
+ "total_pixels": 256000,
+ "changed_pixels": 234722,
+ "changed_ratio": 0.916883,
+ "mean_abs_rgb": [
+ 32.4853,
+ 31.6172,
+ 51.0769
+ ],
+ "rms_rgb": [
+ 56.1542,
+ 51.3272,
+ 69.6673
+ ],
+ "diff_bbox": [
+ 0,
+ 0,
+ 640,
+ 400
+ ],
+ "full_crop_left": [
+ 0,
+ 150,
+ 640,
+ 550
+ ],
+ "full_crop_right": [
+ 640,
+ 150,
+ 1280,
+ 550
+ ],
+ "hotspots": [
+ {
+ "image_width": 109,
+ "image_height": 64,
+ "total_pixels": 6976,
+ "changed_pixels": 6974,
+ "changed_ratio": 0.999713,
+ "mean_abs_rgb": [
+ 111.1264,
+ 94.1901,
+ 85.8125
+ ],
+ "rms_rgb": [
+ 148.771,
+ 125.2909,
+ 112.8771
+ ],
+ "diff_bbox": [
+ 0,
+ 0,
+ 109,
+ 64
+ ],
+ "id": "east_breakwater_marks",
+ "label": "东侧防波堤灯台区",
+ "notes": "防波堤灯台和港口标记区。优先验证远一点的灯台/航标是否也能稳定打出来。",
+ "backend_focus": [
+ "p航路標識群",
+ "safety_icon_missing"
+ ],
+ "pane_box_norm": [
+ 0.83,
+ 0.0,
+ 1.0,
+ 0.16
+ ],
+ "pane_box_pixels": [
+ 531,
+ 0,
+ 640,
+ 64
+ ],
+ "left_image": "report/strict_audit_final_20nm_r4/hotspots/east_breakwater_marks_left.png",
+ "right_image": "report/strict_audit_final_20nm_r4/hotspots/east_breakwater_marks_right.png",
+ "diff_image": "report/strict_audit_final_20nm_r4/hotspots/east_breakwater_marks_diff.png"
+ },
+ {
+ "image_width": 147,
+ "image_height": 80,
+ "total_pixels": 11760,
+ "changed_pixels": 9410,
+ "changed_ratio": 0.80017,
+ "mean_abs_rgb": [
+ 23.2099,
+ 21.248,
+ 26.5393
+ ],
+ "rms_rgb": [
+ 47.0367,
+ 43.8919,
+ 48.7282
+ ],
+ "diff_bbox": [
+ 0,
+ 0,
+ 147,
+ 80
+ ],
+ "id": "takashima_main_harbor_marks",
+ "label": "高岛主港航标区",
+ "notes": "主港口灯塔、红绿标识、近岸障碍标记。优先验证灯塔、小灯、红绿标识是否真正恢复。",
+ "backend_focus": [
+ "p航路標識群",
+ "safety_icon_missing",
+ "text_missing_in_engineering"
+ ],
+ "pane_box_norm": [
+ 0.34,
+ 0.05,
+ 0.57,
+ 0.25
+ ],
+ "pane_box_pixels": [
+ 218,
+ 20,
+ 365,
+ 100
+ ],
+ "left_image": "report/strict_audit_final_20nm_r4/hotspots/takashima_main_harbor_marks_left.png",
+ "right_image": "report/strict_audit_final_20nm_r4/hotspots/takashima_main_harbor_marks_right.png",
+ "diff_image": "report/strict_audit_final_20nm_r4/hotspots/takashima_main_harbor_marks_diff.png"
+ },
+ {
+ "image_width": 115,
+ "image_height": 68,
+ "total_pixels": 7820,
+ "changed_pixels": 5564,
+ "changed_ratio": 0.711509,
+ "mean_abs_rgb": [
+ 20.363,
+ 20.8864,
+ 23.9097
+ ],
+ "rms_rgb": [
+ 34.4925,
+ 37.2038,
+ 41.2721
+ ],
+ "diff_bbox": [
+ 0,
+ 0,
+ 115,
+ 68
+ ],
+ "id": "takashima_inner_nearshore_hazards",
+ "label": "高岛近岸碍航与鱼礁区",
+ "notes": "近岸鱼礁、碍航点、灯塔与小灯混合区。优先验证鱼礁和碍航物是否还在。",
+ "backend_focus": [
+ "p投錨注意障害物",
+ "p航行危険障害物",
+ "p航路標識群"
+ ],
+ "pane_box_norm": [
+ 0.44,
+ 0.14,
+ 0.62,
+ 0.31
+ ],
+ "pane_box_pixels": [
+ 282,
+ 56,
+ 397,
+ 124
+ ],
+ "left_image": "report/strict_audit_final_20nm_r4/hotspots/takashima_inner_nearshore_hazards_left.png",
+ "right_image": "report/strict_audit_final_20nm_r4/hotspots/takashima_inner_nearshore_hazards_right.png",
+ "diff_image": "report/strict_audit_final_20nm_r4/hotspots/takashima_inner_nearshore_hazards_diff.png"
+ }
+ ]
+ },
+ "backend_summary": {
+ "original_feature_instances": 258317,
+ "engineering_feature_instances": 258317,
+ "result_count": 516634,
+ "status_counts": {
+ "extra_in_engineering": 258317,
+ "missing_in_engineering": 258317
+ },
+ "top_annotation_issues": [
+ {
+ "issue": "extra_text_in_engineering",
+ "source_layer": "bathymetry_line",
+ "count": 24168
+ },
+ {
+ "issue": "text_missing_in_engineering",
+ "source_layer": "L海底地形",
+ "count": 24168
+ },
+ {
+ "issue": "extra_text_in_engineering",
+ "source_layer": "depth_contour",
+ "count": 11929
+ },
+ {
+ "issue": "text_missing_in_engineering",
+ "source_layer": "L等深線",
+ "count": 11929
+ },
+ {
+ "issue": "extra_text_in_engineering",
+ "source_layer": "seabed_text_point",
+ "count": 4499
+ },
+ {
+ "issue": "text_missing_in_engineering",
+ "source_layer": "p底質",
+ "count": 4499
+ },
+ {
+ "issue": "extra_text_in_engineering",
+ "source_layer": "navigation_marks",
+ "count": 1516
+ },
+ {
+ "issue": "text_missing_in_engineering",
+ "source_layer": "p航路標識群",
+ "count": 1516
+ },
+ {
+ "issue": "text_missing_in_engineering",
+ "source_layer": "p地名",
+ "count": 1444
+ },
+ {
+ "issue": "extra_text_in_engineering",
+ "source_layer": "place_label_sea",
+ "count": 1444
+ }
+ ],
+ "top_style_semantic_issues": [
+ {
+ "issue": "depth_numeric_missing",
+ "source_layer": "L海底地形",
+ "count": 24168
+ },
+ {
+ "issue": "depth_numeric_missing",
+ "source_layer": "L等深線",
+ "count": 11929
+ },
+ {
+ "issue": "safety_icon_missing",
+ "source_layer": "p航路標識群",
+ "count": 1885
+ },
+ {
+ "issue": "depth_numeric_missing",
+ "source_layer": "L概略等深線",
+ "count": 350
+ },
+ {
+ "issue": "clearance_numeric_missing",
+ "source_layer": "p高さ制限",
+ "count": 33
+ }
+ ],
+ "top_source_layer_issues": [
+ {
+ "status": "extra_in_engineering",
+ "source_layer": "baseline_outline",
+ "count": 87709
+ },
+ {
+ "status": "missing_in_engineering",
+ "source_layer": "P基本線ククリ",
+ "count": 87709
+ },
+ {
+ "status": "extra_in_engineering",
+ "source_layer": "baseline_area",
+ "count": 50421
+ },
+ {
+ "status": "missing_in_engineering",
+ "source_layer": "P基本線",
+ "count": 50421
+ },
+ {
+ "status": "extra_in_engineering",
+ "source_layer": "depth_contour",
+ "count": 36460
+ },
+ {
+ "status": "missing_in_engineering",
+ "source_layer": "L等深線",
+ "count": 36460
+ },
+ {
+ "status": "extra_in_engineering",
+ "source_layer": "bathymetry_line",
+ "count": 35940
+ },
+ {
+ "status": "missing_in_engineering",
+ "source_layer": "L海底地形",
+ "count": 35940
+ },
+ {
+ "status": "extra_in_engineering",
+ "source_layer": "onshore_structure_line",
+ "count": 10544
+ },
+ {
+ "status": "missing_in_engineering",
+ "source_layer": "L陸上構造物陸",
+ "count": 10544
+ }
+ ]
+ },
+ "notes": [
+ "Visual metrics are browser-rendered screenshot diff metrics from the fixed compare page.",
+ "Backend metrics are current render-audit summary metrics and may remain blind to sprite-load failures."
+ ]
+}
\ No newline at end of file
diff --git a/report/strict_audit_final_20nm_r4/strict_audit_summary.md b/report/strict_audit_final_20nm_r4/strict_audit_summary.md
new file mode 100644
index 0000000..f69888f
--- /dev/null
+++ b/report/strict_audit_final_20nm_r4/strict_audit_summary.md
@@ -0,0 +1,104 @@
+# NavSea Strict Audit Summary
+
+## Scope
+
+- Compare page: `http://192.168.200.184/newpec/navsea-compare-karatsu-20nm.html`
+- Compare version: `compare-r14-20260401-1523`
+- Backend render audit: `/root/sourceserver/pbf/NavSea_Original_vs_Delivery_Render_Audit_Karatsu_20nm_2026-03-31.r7.json`
+
+## Visual Audit
+
+- changed pixels: `234722` / `256000`
+- changed ratio: `0.916883`
+- mean abs rgb: `[32.4853, 31.6172, 51.0769]`
+- rms rgb: `[56.1542, 51.3272, 69.6673]`
+- left crop: `[0, 150, 640, 550]`
+- right crop: `[640, 150, 1280, 550]`
+
+Artifacts:
+
+- `report/strict_audit_final_20nm_r4/compare_full.png`
+- `report/strict_audit_final_20nm_r4/compare_left.png`
+- `report/strict_audit_final_20nm_r4/compare_right.png`
+- `report/strict_audit_final_20nm_r4/compare_diff.png`
+
+## Hotspot AOI Audit
+
+### 东侧防波堤灯台区
+
+- id: `east_breakwater_marks`
+- notes: `防波堤灯台和港口标记区。优先验证远一点的灯台/航标是否也能稳定打出来。`
+- backend focus: `['p航路標識群', 'safety_icon_missing']`
+- changed pixels: `6974` / `6976`
+- changed ratio: `0.999713`
+- mean abs rgb: `[111.1264, 94.1901, 85.8125]`
+- pane box norm: `[0.83, 0.0, 1.0, 0.16]`
+- pane box pixels: `[531, 0, 640, 64]`
+- left image: `report/strict_audit_final_20nm_r4/hotspots/east_breakwater_marks_left.png`
+- right image: `report/strict_audit_final_20nm_r4/hotspots/east_breakwater_marks_right.png`
+- diff image: `report/strict_audit_final_20nm_r4/hotspots/east_breakwater_marks_diff.png`
+
+### 高岛主港航标区
+
+- id: `takashima_main_harbor_marks`
+- notes: `主港口灯塔、红绿标识、近岸障碍标记。优先验证灯塔、小灯、红绿标识是否真正恢复。`
+- backend focus: `['p航路標識群', 'safety_icon_missing', 'text_missing_in_engineering']`
+- changed pixels: `9410` / `11760`
+- changed ratio: `0.80017`
+- mean abs rgb: `[23.2099, 21.248, 26.5393]`
+- pane box norm: `[0.34, 0.05, 0.57, 0.25]`
+- pane box pixels: `[218, 20, 365, 100]`
+- left image: `report/strict_audit_final_20nm_r4/hotspots/takashima_main_harbor_marks_left.png`
+- right image: `report/strict_audit_final_20nm_r4/hotspots/takashima_main_harbor_marks_right.png`
+- diff image: `report/strict_audit_final_20nm_r4/hotspots/takashima_main_harbor_marks_diff.png`
+
+### 高岛近岸碍航与鱼礁区
+
+- id: `takashima_inner_nearshore_hazards`
+- notes: `近岸鱼礁、碍航点、灯塔与小灯混合区。优先验证鱼礁和碍航物是否还在。`
+- backend focus: `['p投錨注意障害物', 'p航行危険障害物', 'p航路標識群']`
+- changed pixels: `5564` / `7820`
+- changed ratio: `0.711509`
+- mean abs rgb: `[20.363, 20.8864, 23.9097]`
+- pane box norm: `[0.44, 0.14, 0.62, 0.31]`
+- pane box pixels: `[282, 56, 397, 124]`
+- left image: `report/strict_audit_final_20nm_r4/hotspots/takashima_inner_nearshore_hazards_left.png`
+- right image: `report/strict_audit_final_20nm_r4/hotspots/takashima_inner_nearshore_hazards_right.png`
+- diff image: `report/strict_audit_final_20nm_r4/hotspots/takashima_inner_nearshore_hazards_diff.png`
+
+## Backend Render Audit
+
+- original feature instances: `258317`
+- delivery feature instances: `258317`
+- result count: `516634`
+
+Status counts:
+
+- `extra_in_engineering`: `258317`
+- `missing_in_engineering`: `258317`
+
+Top annotation issues:
+
+- `extra_text_in_engineering` | `bathymetry_line` | `24168`
+- `text_missing_in_engineering` | `L海底地形` | `24168`
+- `extra_text_in_engineering` | `depth_contour` | `11929`
+- `text_missing_in_engineering` | `L等深線` | `11929`
+- `extra_text_in_engineering` | `seabed_text_point` | `4499`
+- `text_missing_in_engineering` | `p底質` | `4499`
+- `extra_text_in_engineering` | `navigation_marks` | `1516`
+- `text_missing_in_engineering` | `p航路標識群` | `1516`
+- `text_missing_in_engineering` | `p地名` | `1444`
+- `extra_text_in_engineering` | `place_label_sea` | `1444`
+
+Top style semantic issues:
+
+- `depth_numeric_missing` | `L海底地形` | `24168`
+- `depth_numeric_missing` | `L等深線` | `11929`
+- `safety_icon_missing` | `p航路標識群` | `1885`
+- `depth_numeric_missing` | `L概略等深線` | `350`
+- `clearance_numeric_missing` | `p高さ制限` | `33`
+
+## Interpretation
+
+- This report intentionally puts browser-rendered visual output and backend render-audit summary in one place.
+- If the browser screenshot improves but backend counts do not, the current backend audit is likely blind to a browser/runtime issue such as sprite resolution.
diff --git a/src/pbf/navsea-compare-karatsu-20nm.html b/src/pbf/navsea-compare-karatsu-20nm.html
index 6ae71bd..3eca7d0 100644
--- a/src/pbf/navsea-compare-karatsu-20nm.html
+++ b/src/pbf/navsea-compare-karatsu-20nm.html
@@ -314,7 +314,7 @@