Fix comparison baseline for JP-field migration
This commit is contained in:
27
AGENTS.md
27
AGENTS.md
@@ -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
|
||||
|
||||
@@ -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 线上逐步去掉日文字段依赖,同时保持视觉尽量不变。`
|
||||
|
||||
360
src/Domain/navsea-compare-legacy-domain-compatible.html
Normal file
360
src/Domain/navsea-compare-legacy-domain-compatible.html
Normal 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>
|
||||
Reference in New Issue
Block a user