Fix comparison baseline for JP-field migration

This commit is contained in:
OpenAI Codex
2026-03-31 15:47:09 +08:00
parent b566af331d
commit fc1bbb3673
3 changed files with 426 additions and 16 deletions

View File

@@ -35,6 +35,33 @@ Important limitation:
- `Karatsu 20nm delivery` is currently not object-auditable with the existing audit script because the delivery PBF no longer preserves enough trace-back anchor fields
## Current Visual Regression Baseline
For the current goal of:
- removing Japanese fields from the PBF-facing path
- while keeping the rendered appearance as close as possible to the legacy chart
use this fixed comparison page as the only primary visual baseline:
- `http://192.168.200.184/newpec/domain/navsea-compare-legacy-domain-compatible.html`
The right side of that page is the current working target line.
Current target PBF line for this goal:
- `pbf-domain-karatsu-10nm`
- package sources:
- `domain_safety`
- `domain_detail`
Rules:
- do not create additional comparison pages for this migration unless explicitly requested
- do not switch the primary comparison URL casually
- treat the fixed page above as the stable regression surface for visual checking
- keep delivery-line experiments separate from this baseline unless the user explicitly redirects the work
## Working Rules
### 1. Step Record Must Stay Fresh

View File

@@ -21,6 +21,30 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
- 已把 `Karatsu 20nm` 的最新展示页和左右对比页切到更贴近旧视觉的 compare style
- 已确认 `Karatsu 20nm` 当前“和旧版差很大”的问题,既有 style 原因,也有 delivery 数据覆盖范围与旧版上下文不一致的原因
- 已确认 `domain-compatible` 线上“最接近旧版”的那份 style与仓库当前同名文件并不一致
- 已正式确定:当前“去掉 PBF 里的日文字段,同时视觉保持尽量不变”的主工作线,不再以 `20nm delivery compare` 为主,而改用固定的 `domain-compatible` 对比页
## Fixed Compare Baseline
从现在开始,这轮工作的固定比较基线是:
- 页面:
- `http://192.168.200.184/newpec/domain/navsea-compare-legacy-domain-compatible.html`
- 目标 PBF
- `http://192.168.200.184/pbf-domain-karatsu-10nm/safety/{z}/{x}/{y}.pbf`
- `http://192.168.200.184/pbf-domain-karatsu-10nm/detail/{z}/{x}/{y}.pbf`
这条线的定义是:
- 目标不是先追 `20nm delivery` 交付口径
- 目标是先在 `Karatsu 10nm Domain compatible` 这条线上,完成:
- PBF / style 对日文字段依赖的移除
- 同时保持视觉尽量贴近旧版
工作规则:
- 不再新增新的 compare 页面
- 后续视觉比较统一只看上面这一张固定页面
- `20nm delivery` 仍可保留为交付线参考,但不再作为当前这轮视觉回归的主页面
## Latest Audit Status
@@ -189,24 +213,23 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
## Concrete Next Step
下次继续时,建议直接从这一组开始
下次继续时,建议按这个顺序推进
-排查 [`src/pbf/style.navsea-delivery-karatsu-10nm.json`](/root/sourceserver/pbf/src/pbf/style.navsea-delivery-karatsu-10nm.json)
- 重点看:
- `baseline_outline`
- `baseline_area`
- `depth_contour`
- `bathymetry_line`
- `navigation_marks`
-以固定页面
- `http://192.168.200.184/newpec/domain/navsea-compare-legacy-domain-compatible.html`
作为唯一视觉基线
- 以 [`src/Domain/style.domain-legacy-compatible-karatsu-10nm.deployed-2026-03-31.json`](/root/sourceserver/pbf/src/Domain/style.domain-legacy-compatible-karatsu-10nm.deployed-2026-03-31.json)
作为“最接近旧版”的右侧样式母版
- 在 Domain compatible 线上逐层确认:
- 哪些 layer 仍依赖 legacy 日文字段
- 哪些可以直接切到标准字段而不改变视觉
- 哪些需要加兼容映射才能保持视觉不变
目标
当前真正的下一步不再是继续调 `20nm compare` 页面,而是
- `baseline` 组的大头 mismatch 压下去
- 再修 `depth_numeric_missing`
- 最后修 `safety_icon_missing`
- 同时继续拆分 `20nm` 的视觉差距:
- 哪些能靠 style 收敛
- 哪些必须回到 delivery 数据覆盖范围本身去补
-deployed baseline style 中对 legacy 字段的依赖系统列出来
- 以固定 compare 页面做逐步替换验证
- 每次替换后只在这一个页面上检查视觉回归
## Important Notes
@@ -220,4 +243,4 @@ Remote: `ssh://git@nas:2222/tei/pbf.git`
下次开机如果要快速接上,可以先看这份文件,再按下面这句继续:
`继续处理 STEP_RECORD.md 里记录的下一步,从 Karatsu 10nm delivery 审计的 baseline / depth / navigation marks 三组问题开始修。`
`继续处理 STEP_RECORD.md 里记录的固定 compare 基线,围绕 navsea-compare-legacy-domain-compatible.html 这一个页面,在 pbf-domain-karatsu-10nm 线上逐步去掉日文字段依赖,同时保持视觉尽量不变。`

View File

@@ -0,0 +1,360 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>NavSea Fixed Compare Baseline</title>
<link href="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.css" rel="stylesheet" />
<style>
:root {
--bg: #f0f3ef;
--panel: rgba(248, 246, 241, 0.94);
--ink: #19252c;
--muted: #5f6d74;
--line: rgba(25, 37, 44, 0.14);
--accent: #85501d;
--accent-dark: #5f3812;
--shadow: 0 18px 36px rgba(25, 37, 44, 0.14);
}
* { box-sizing: border-box; }
html, body {
margin: 0;
height: 100%;
background:
radial-gradient(circle at top left, rgba(133, 80, 29, 0.12), transparent 30%),
radial-gradient(circle at bottom right, rgba(11, 108, 143, 0.10), transparent 28%),
var(--bg);
color: var(--ink);
font-family: "Avenir Next", "Segoe UI", "PingFang SC", "Noto Sans SC", sans-serif;
}
body { display: grid; grid-template-rows: auto 1fr; }
.toolbar {
display: flex;
justify-content: space-between;
align-items: center;
gap: 18px;
padding: 14px 18px 12px;
background: linear-gradient(180deg, rgba(248, 246, 241, 0.98), rgba(248, 246, 241, 0.88));
border-bottom: 1px solid var(--line);
box-shadow: var(--shadow);
z-index: 10;
flex-wrap: wrap;
}
.title { font-size: 18px; font-weight: 700; letter-spacing: 0.02em; }
.subtitle { margin-top: 4px; font-size: 13px; color: var(--muted); }
.toolbar-actions { display: flex; gap: 10px; align-items: center; }
button {
height: 42px; padding: 0 16px; border-radius: 12px;
border: 1px solid rgba(95, 56, 18, 0.18);
background: linear-gradient(180deg, var(--accent), var(--accent-dark));
color: #fff; font: inherit; font-weight: 700; cursor: pointer;
box-shadow: 0 10px 22px rgba(95, 56, 18, 0.18);
}
.layout { display: grid; grid-template-columns: 1fr 1fr; min-height: 0; }
.pane { position: relative; min-width: 0; border-right: 1px solid var(--line); }
.pane:last-child { border-right: 0; }
.map { position: absolute; inset: 0; }
.pane-tag {
position: absolute; top: 12px; left: 12px; z-index: 5; padding: 10px 12px;
border-radius: 12px; background: var(--panel);
border: 1px solid rgba(25, 37, 44, 0.08);
box-shadow: 0 12px 24px rgba(25, 37, 44, 0.12); backdrop-filter: blur(6px);
}
.pane-tag strong { display: block; font-size: 13px; margin-bottom: 3px; }
.pane-tag span { display: block; font-size: 12px; color: var(--muted); }
.status {
position: absolute; left: 50%; bottom: 14px; transform: translateX(-50%);
z-index: 5; padding: 8px 12px; border-radius: 999px;
background: rgba(25, 37, 44, 0.84); color: #f7f9fb; font-size: 12px;
letter-spacing: 0.03em; max-width: calc(100% - 28px);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.layer-panel {
position: absolute;
top: 92px;
right: 12px;
z-index: 6;
width: 320px;
max-height: calc(100% - 128px);
overflow: auto;
padding: 12px;
border-radius: 14px;
background: rgba(248, 246, 241, 0.96);
border: 1px solid rgba(25, 37, 44, 0.1);
box-shadow: 0 12px 24px rgba(25, 37, 44, 0.14);
backdrop-filter: blur(8px);
}
.layer-panel h3 {
margin: 0 0 8px;
font-size: 14px;
}
.layer-panel p {
margin: 0 0 10px;
font-size: 12px;
color: var(--muted);
line-height: 1.45;
}
.layer-actions {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.layer-actions button {
height: 34px;
padding: 0 10px;
border-radius: 10px;
font-size: 12px;
}
.layer-list {
display: grid;
gap: 6px;
}
.layer-item {
display: flex;
align-items: flex-start;
gap: 8px;
padding: 7px 8px;
border-radius: 10px;
background: rgba(255, 255, 255, 0.7);
}
.layer-item input {
margin-top: 2px;
}
.layer-meta {
min-width: 0;
}
.layer-id {
display: block;
font-size: 12px;
font-weight: 700;
line-height: 1.35;
word-break: break-word;
}
.layer-desc {
display: block;
font-size: 11px;
color: var(--muted);
line-height: 1.35;
word-break: break-word;
}
@media (max-width: 920px) {
.layout { grid-template-columns: 1fr; grid-template-rows: 1fr 1fr; }
.pane { border-right: 0; border-bottom: 1px solid var(--line); }
.pane:last-child { border-bottom: 0; }
.layer-panel {
width: calc(100% - 24px);
max-height: 42%;
left: 12px;
right: 12px;
}
}
</style>
</head>
<body>
<div class="toolbar">
<div>
<div class="title">NavSea Fixed Compare Baseline</div>
<div class="subtitle">这张页面现在是当前项目唯一固定使用的视觉对比页。左边固定加载原始样式 <code>style.patched.local.json</code>,右边固定加载 Domain compatible 样式 <code>style.domain-legacy-compatible-karatsu-10nm.json</code>,用于在 <code>pbf-domain-karatsu-10nm</code> 线上持续验证“去掉日文字段依赖,同时保持视觉尽量不变”。</div>
</div>
<div class="toolbar-actions">
<button id="reload-btn" type="button">重新加载</button>
</div>
</div>
<div class="layout">
<section class="pane">
<div class="pane-tag">
<strong>左侧</strong>
<span>原始样式 · style.patched.local.json</span>
</div>
<div id="map-left" class="map"></div>
</section>
<section class="pane">
<div class="pane-tag">
<strong>右侧</strong>
<span>固定基线 · Domain compatible / pbf-domain-karatsu-10nm</span>
</div>
<div id="map-right" class="map"></div>
<aside class="layer-panel">
<h3>右侧图层开关</h3>
<p>这里列出右侧 Domain 兼容样式的全部 `layer id`。你可以逐个开关,方便定位是哪一层造成差异。</p>
<div class="layer-actions">
<button id="layers-all-on" type="button">全开</button>
<button id="layers-all-off" type="button">全关</button>
</div>
<div id="layer-list" class="layer-list">样式加载后显示…</div>
</aside>
</section>
</div>
<div id="status" class="status">等待加载样式…</div>
<script src="https://unpkg.com/maplibre-gl@4.7.1/dist/maplibre-gl.js"></script>
<script>
const STYLE_URLS = {
left: '../style.patched.local.json',
right: './style.domain-legacy-compatible-karatsu-10nm.json'
};
const initialView = {
center: [129.9697, 33.4425],
zoom: 11,
pitch: 0,
bearing: 0
};
const statusEl = document.getElementById('status');
const reloadBtn = document.getElementById('reload-btn');
const layerListEl = document.getElementById('layer-list');
const layersAllOnBtn = document.getElementById('layers-all-on');
const layersAllOffBtn = document.getElementById('layers-all-off');
const maps = { left: null, right: null };
let syncing = false;
function setStatus(text) { statusEl.textContent = text; }
function isToggleableLayer(layer) {
return layer && layer.id && layer.type !== 'background';
}
function getVisibility(map, layerId) {
return map.getLayoutProperty(layerId, 'visibility') !== 'none';
}
function setVisibility(map, layerId, visible) {
map.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');
}
function renderLayerPanel() {
const map = maps.right;
if (!map || !map.isStyleLoaded()) {
layerListEl.textContent = '样式加载后显示…';
return;
}
const style = map.getStyle();
const toggleableLayers = (style.layers || []).filter(isToggleableLayer);
layerListEl.innerHTML = '';
toggleableLayers.forEach((layer) => {
const item = document.createElement('label');
item.className = 'layer-item';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = getVisibility(map, layer.id);
checkbox.addEventListener('change', () => {
setVisibility(map, layer.id, checkbox.checked);
});
const meta = document.createElement('span');
meta.className = 'layer-meta';
const title = document.createElement('span');
title.className = 'layer-id';
title.textContent = layer.id;
const desc = document.createElement('span');
desc.className = 'layer-desc';
const src = layer.source || 'no-source';
const srcLayer = layer['source-layer'] || 'no-source-layer';
desc.textContent = `${layer.type} · ${src} · ${srcLayer}`;
meta.appendChild(title);
meta.appendChild(desc);
item.appendChild(checkbox);
item.appendChild(meta);
layerListEl.appendChild(item);
});
}
function setAllRightLayers(visible) {
const map = maps.right;
if (!map || !map.isStyleLoaded()) return;
const style = map.getStyle();
(style.layers || []).filter(isToggleableLayer).forEach((layer) => {
setVisibility(map, layer.id, visible);
});
renderLayerPanel();
}
async function fetchStyle(url) {
const response = await fetch(url, { cache: 'no-store' });
if (!response.ok) throw new Error(`加载样式失败: ${url}`);
return response.json();
}
function syncMap(sourceMap, targetMap) {
if (syncing || !targetMap) return;
syncing = true;
targetMap.jumpTo({
center: sourceMap.getCenter(),
zoom: sourceMap.getZoom(),
bearing: sourceMap.getBearing(),
pitch: sourceMap.getPitch()
});
syncing = false;
}
function attachSync(map, otherKey) {
['move', 'zoom', 'rotate', 'pitch'].forEach((eventName) => {
map.on(eventName, () => syncMap(map, maps[otherKey]));
});
}
async function applyStyle(side) {
const style = await fetchStyle(STYLE_URLS[side]);
const map = maps[side];
const camera = map ? {
center: map.getCenter(),
zoom: map.getZoom(),
bearing: map.getBearing(),
pitch: map.getPitch()
} : initialView;
if (!map) {
maps[side] = new maplibregl.Map({
container: side === 'left' ? 'map-left' : 'map-right',
style,
center: camera.center,
zoom: camera.zoom,
bearing: camera.bearing,
pitch: camera.pitch
});
maps[side].addControl(new maplibregl.NavigationControl(), 'top-right');
attachSync(maps[side], side === 'left' ? 'right' : 'left');
if (side === 'right') {
maps[side].on('style.load', renderLayerPanel);
}
} else {
map.setStyle(style, { diff: false });
map.once('style.load', () => {
map.jumpTo(camera);
if (side === 'right') renderLayerPanel();
});
}
}
async function reloadStyles() {
setStatus('正在加载左右样式…');
reloadBtn.disabled = true;
try {
await Promise.all([applyStyle('left'), applyStyle('right')]);
setStatus('样式已加载。拖动任一侧,另一侧会跟随。');
} catch (error) {
console.error(error);
setStatus(error.message || '样式加载失败');
} finally {
reloadBtn.disabled = false;
}
}
reloadBtn.addEventListener('click', reloadStyles);
layersAllOnBtn.addEventListener('click', () => setAllRightLayers(true));
layersAllOffBtn.addEventListener('click', () => setAllRightLayers(false));
reloadStyles();
</script>
</body>
</html>