import { esc, number, icon, enhance, table as sharedTable } from "./ui.js";
import { versionsView, versionFromHash } from "./versions.js?v=history-20261103";
let content,
versionHistory,
registry = [],
snapshot,
root,
timer,
ageTimer,
request,
generation = 0;
let selectedCampaign = null,
selectedVersion = "006",
selectedJobs = "active",
registryQuery = "",
registryPage = 0;
const clock = (t) =>
new Date(t * 1000).toLocaleString("zh-CN", {
timeZone: "Asia/Kuala_Lumpur",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
hour12: false,
}) + " MYT";
const table = (heads, rows) => sharedTable(heads, rows, "synth-table");
const phases = {
WAIT_SSD_CAPACITY: "等待存储准入",
WAIT_CAPACITY: "等待容量",
WAIT_SHARED_GPU_FOR_CYCLES: "等待GPU",
V5_APPEARANCE_CANONICAL_SURFACE: "表面与材质",
V5_THREE_MOUNT_INDEPENDENT_CAMERA: "相机轨迹",
V5_RANDOMIZED_SCENE_CANONICAL_SURFACE: "场景组装",
CYCLES_OPTIX_16SPP_GPU_OIDN: "RGB渲染",
VIDEO_POST_AND_DEPTH_OVERLAPPED: "视频后处理 / 深度",
PACKAGE_NATIVE_RENDER_CHANNELS: "打包",
VERIFIED_SEQUENTIAL_NAS_PUBLICATION: "NAS归档",
INITIALIZING: "初始化",
FAILED: "待定点修复",
WAIT_UPSTREAM_ASSET: "等待上游资产",
};
const sections = [
["progress", "进度"],
["versions", "版本"],
["reading", "数据读取"],
["samples", "样例"],
];
const tab = () => location.hash.split("/")[1] || "progress";
function section(title, body) {
return ``;
}
function code(title, text) {
return `${esc(title)}
${icon("copy", "复制代码", "data-copy")}
${esc(text)}
`;
}
function icons() {
enhance();
}
export function valid(value) {
const c = value && value.counts;
return (
value &&
value.schema === "egorenderer.public_progress.v1" &&
Number.isFinite(value.collected_at) &&
c &&
["complete", "running", "ready", "failed", "total"].every(
(k) => Number.isInteger(c[k]) && c[k] >= 0,
) &&
c.total > 0 &&
c.complete + c.running + c.ready + c.failed === c.total
);
}
function header() {
return `
EgoRenderer
V006读取快照${icon("refresh-cw", "刷新进度", 'id="synth-refresh"')}
`;
}
function progressView() {
return `
${section("数据范围", '')}
主机与存储
${section("最近归档", '')}`;
}
export function progressState(value, error = false, now = Date.now() / 1000) {
if (!value)
return { state: "error", prefix: error ? "状态暂不可用" : "读取快照" };
if (error) return { state: "stale", prefix: "连接中断 · " };
if (now - value.collected_at > 180)
return { state: "stale", prefix: "更新已过期 · " };
const c = value.counts;
if (
c.complete === c.total &&
c.running === 0 &&
c.ready === 0 &&
c.failed === 0
)
return { state: "complete", prefix: "生成队列结束 · " };
if (
!value.queue_alive &&
c.waiting_assets > 0 &&
!c.running &&
!c.ready &&
!c.failed
)
return { state: "waiting", prefix: "等待上游资产 · " };
if (!value.queue_alive) return { state: "stopped", prefix: "队列已停 · " };
if (value.admission_held) return { state: "stopped", prefix: "暂停新增 · " };
return { state: "live", prefix: "" };
}
export function campaignSnapshot(value, selected = null) {
if (!value) return null;
const choice = selected || value.default_campaign || "ARCTIC";
const view =
choice === "OakInk2" && value.oakink_render
? value.oakink_render
: { dataset: "ARCTIC" };
return {
...value,
...view,
arctic_render: {
counts: value.counts,
scope: value.scope,
coverage: value.coverage,
known_gt_missing: value.known_gt_missing,
},
};
}
function updateFreshness(error) {
if (!root) return;
const status = root.querySelector("#synth-status"),
label = root.querySelector("#synth-freshness");
if (!snapshot) {
status.dataset.state = "error";
label.textContent = error ? "状态暂不可用" : "读取快照";
return;
}
const current = campaignSnapshot(snapshot, selectedCampaign);
const selector = root.querySelector("#synth-campaign");
selector.querySelector('option[value="OakInk2"]').disabled =
!snapshot.oakink_render;
selector.value = current.dataset;
const display = progressState(current, error);
status.dataset.state = display.state;
label.textContent = display.prefix + clock(snapshot.collected_at);
root.querySelector("#synth-execution").textContent =
"执行 " + current.execution_revision;
}
function updateProgress() {
if (!root || tab() !== "progress") return;
if (!snapshot) {
root.querySelector("#synth-summary").innerHTML =
'状态暂不可用。不会以零值替代上次进度。
';
return;
}
const s = campaignSnapshot(snapshot, selectedCampaign),
c = s.counts;
const metrics = [
["渲染与深度完成", `${number(c.complete)} / ${number(c.total)}`],
["运行中", number(c.running)],
["待领取", number(c.ready)],
["失败", number(c.failed)],
];
if (c.waiting_assets) metrics.push(["待上游资产", number(c.waiting_assets)]);
root.querySelector("#synth-summary").dataset.columns = metrics.length;
root.querySelector("#synth-summary").innerHTML = metrics
.map(
([k, v]) =>
`${v}${k}
`,
)
.join("");
const progress = root.querySelector("#synth-progress");
progress.setAttribute("aria-label", s.dataset + "包生成进度");
progress.setAttribute("aria-valuemin", "0");
progress.setAttribute("aria-valuemax", c.total);
progress.setAttribute("aria-valuenow", c.complete);
progress.innerHTML = [
["done", c.complete],
["running", c.running],
["failed", c.failed],
]
.map(
([cl, n]) =>
``,
)
.join("");
root.querySelector("#synth-facts").innerHTML =
`完成 ${((100 * c.complete) / c.total).toFixed(1)}%已生成帧 ${number(s.coverage.completed_variant_frames)} / ${number(s.scope.expected_variant_frames)}最近1小时 ${number(s.throughput.packages_last_hour)}包队列预计(不含失败) ${s.throughput.queued_eta_hours == null ? "—" : number(s.throughput.queued_eta_hours) + "小时"}`;
root.querySelector("#synth-notices").innerHTML =
`训练资格待确认 · 辅助拟合待交付${s.dataset === "OakInk2" ? " · 下半身为生成结果" : ""}${s.known_gt_missing.length ? ` · ${s.known_gt_missing.length} 个历史包缺GT深度` : ""}
`;
const oak = s.oakink_assets,
hc = s.hardcases;
root.querySelector("#synth-cohorts").innerHTML = table(
["数据", "规模", "当前口径"],
[
`| ARCTIC · V006 | ${number(s.arctic_render.counts.complete)} / ${number(s.arctic_render.counts.total)}包 | ${number(s.arctic_render.scope.source_frames)}源帧;每条动作1份${s.arctic_render.known_gt_missing.length ? ";" + s.arctic_render.known_gt_missing.length + "个历史包缺GT深度" : ""} |
`,
`| Hard Cases · V006 | ${hc.complete} / ${hc.expected}条样例 | ${number(hc.frames)}主源帧;三类各5条,独立于主队列 |
`,
`| OakInk2 · V006 | ${snapshot.oakink_render ? number(snapshot.oakink_render.counts.complete) + " / " + number(snapshot.oakink_render.counts.total) + "包" : number(oak.total) + "源"} | ${number(oak.counts.COMPLETE || 0)}条人体资产就绪${snapshot.oakink_render ? ";" + number(snapshot.oakink_render.counts.waiting_assets) + "条待上游;每条动作1份" : ";尚无最终合成包计数"} |
`,
'| GRAB · V005 | 1,314源组 / 2,425包 | 历史参数附件更新完成;V006新深度尚未据此宣称完成 |
',
],
);
const selected = root.querySelector("#synth-job-filter");
selected.value = selectedJobs;
const jobs = s.jobs.filter(
(j) =>
selectedJobs === "all" ||
(selectedJobs === "failed"
? j.state === "FAILED"
: selectedJobs === "inputs"
? j.state === "WAIT_UPSTREAM_ASSET"
: ["RUNNING", "RECOVERING_DEPTH"].includes(j.state)),
);
const multi = s.scope.variants > 1;
root.querySelector("#synth-jobs").innerHTML = jobs.length
? table(
["源动作", ...(multi ? ["变体"] : []), "帧数", "存储", "阶段"],
jobs.map(
(j) =>
`| ${esc(j.source)} | ${multi ? `${j.variant} | ` : ""}${number(j.frames)} | ${esc(j.storage || "待准入")} | ${esc(phases[j.phase] || j.phase)} |
`,
),
)
: '当前没有此类任务。
';
const gpu = s.hardware.gpus || [];
root.querySelector("#synth-resources").innerHTML = gpu
.map(
(g) =>
`- GPU ${g.index}
- ${g.utilization}%
- 显存 GiB
- ${number(g.used_mib / 1024)} / ${number(g.total_mib / 1024)}
`,
)
.join("");
root.querySelector("#synth-storage").innerHTML =
`CPU(主机) ${number(s.hardware.cpu_percent)}%SSD预留 ${number(s.storage.ssd_reserved_gib)} / ${number(s.storage.ssd_budget_gib)} GiBSSD可用 ${number(s.storage.ssd_available_gib)} GiBNAS可用 ${number(s.storage.nas_available_gib / 1024)} TiBNAS通道 ${s.storage.nas_active} / ${s.storage.nas_limit}`;
root.querySelector("#synth-recent").innerHTML = table(
["源动作", ...(multi ? ["变体"] : []), "归档时间"],
s.recent.map(
(j) =>
`| ${esc(j.source)} | ${multi ? `${j.variant} | ` : ""}${clock(j.at)} |
`,
),
);
}
function versionPage() {
return `${versionsView(content, versionHistory, selectedVersion)}
${section(
"V006关键执行修订",
table(
["修订", "主题", "变化"],
content.milestones.map(
(r) => `${r.map((x) => `| ${esc(x)} | `).join("")}
`,
),
),
)}
执行登记簿
登记状态不是实时任务状态;执行修订不等同数据发布版本。
`;
}
function updateRegistry() {
const target = root.querySelector("#synth-registry");
if (!target) return;
const rows = registry
.filter((r) =>
[r.id, r.title, r.status, r.scope]
.join(" ")
.toLowerCase()
.includes(registryQuery.toLowerCase()),
)
.slice()
.reverse();
registryPage = Math.max(
0,
Math.min(registryPage, Math.ceil(rows.length / 25) - 1),
);
const start = registryPage * 25;
target.innerHTML =
table(
["修订", "模块", "登记状态 / 范围"],
rows
.slice(start, start + 25)
.map(
(r) =>
`| ${esc(r.id)} | ${esc(r.title)} | ${esc(r.status)}${esc(r.scope || "范围见对应冻结合同。")} SHA256 ${esc(r.sha256)} |
`,
),
) +
``;
const prev = target.querySelector("[data-registry-prev]"),
next = target.querySelector("[data-registry-next]");
prev.disabled = registryPage === 0;
next.disabled = start + 25 >= rows.length;
prev.onclick = () => {
registryPage--;
updateRegistry();
};
next.onclick = () => {
registryPage++;
updateRegistry();
};
icons();
}
function readingView() {
const active = campaignSnapshot(snapshot, selectedCampaign);
const isOak = active?.dataset === "OakInk2";
const indexPath = isOak
? active.package_index
: "ARCTIC/packages/final_surface279_v287_20261030/INDEX.json";
const index = `import json, os\nfrom pathlib import Path\n\nroot = Path(os.environ["EGORENDER_ROOT"])\nindex = json.loads((root / "${indexPath}").read_text())\nfor item in index["complete"]:\n package = Path(item["package"])\n meta = json.loads((package / "metadata.json").read_text())\n print(meta["sequence_index"], meta["frames"], meta["dataset_release"])\n # complete 是渲染终态;另查深度缺口与辅助标注。`;
const depth = `from egorender_reader import DepthReader, depth_pair\nimport numpy as np\n\ngt_path, sensor_path = depth_pair(package)\nwith DepthReader(gt_path) as gt, DepthReader(sensor_path) as sensor:\n assert np.array_equal(gt.frames, sensor.frames)\n z_gt, valid_gt, source_frame = gt.frame(0)\n z_sensor, valid_sensor, other_frame = sensor.frame(0)\n assert source_frame == other_frame\n # z: float32 米,相机轴向Z;分别使用各自的valid。\n # 每个DataLoader worker长期持有reader,不逐帧重新打开整个NPZ。`;
const camera = `with np.load(package / "camera_parameters.npz", allow_pickle=False) as c:\n source_frames = c["source_frame_index"]\n K = c["K"]\n world_to_camera = c["world_to_camera"]\n camera_to_world = c["camera_to_world"]\n print(c["camera_coordinates"], c["world_coordinates"])\n # 先核对坐标约定和K是否逐帧,不额外套一次坐标轴翻转。\n\nfrom egorender_reader import unpack_class_frame\nwith np.load(package / "masks_visible_2bit.npz", allow_pickle=False) as m:\n width, height = map(int, m["image_size"])\n classes = unpack_class_frame(m["packed_classes"][0], (height, width))\n print(m["classes"], m["bitorder"])\n # 当前常见: 0背景 / 1人体及衣物 / 2交互物体 / 3桌面;以文件为准。`;
const native = isOak
? `descriptor = json.loads((package / "native_mano_descriptor.json").read_text())\nassert descriptor["quaternion_layout"] == "wxyz"\nwith np.load(package / "mano_parameters.npz", allow_pickle=False) as h:\n print(h.files) # 保留原始字段,不转成ARCTIC的axis-angle表示\nsource_offsets = np.load(package / "source_offsets.npy", allow_pickle=False)\nwith np.load(package / "native_object_poses.npz", allow_pickle=False) as p:\n object_ids = p["object_ids"]\n object_to_oakink = p["object_to_oakink"]\nwith np.load(package / "object_membership.npz", allow_pickle=False) as m:\n print(m.files)\n# 原始source_offsets与30fps动作索引分别保留。\n# 物体姿态在原始OakInk坐标;按descriptor.transform_chain及metadata.source_to_world变换一次。\nassert meta["lower_motion_is_gt"] is False`
: `with np.load(package / "mano_parameters.npz", allow_pickle=False) as h:\n print(h.files)\n left_pose = h["left_pose"]\n left_translation = h["left_trans"]\n source_frames = h["source_frames"]\n # left/right_rot, pose, trans, shape来自原始数据集参数。\n # 参数化/手均值/坐标系遵循原始模型与包内来源,不把shape当拟合真值。\n\n# V005辅助附件不是render输入:\nfit_ref = json.loads((legacy_package / "mano_fit_current.json").read_text())\n# 按fit_ref和对应合同加载参数;左手必须搭配V005修正基底。`;
const relocation = `from egorender_reader import DatasetLocations\n\nloc = DatasetLocations(package / "artifact_locations.json")\n# 给定包内记录的 {"path": ..., "sha256": ...} 引用:\nresolved = loc.read_bound_json(binding)\n# 先检查原JSON字节SHA,再解析历史路径及path-keyed字典。\n# 不覆盖immutable报告,也不要通过字符串替换伪造新SHA。`;
return `${section(
"文件与语义",
table(
["通道", "文件", "表示", "读取约束"],
content.channels.map(
(r) => `${r.map((x) => `| ${esc(x)} | `).join("")}
`,
),
),
)}
${code("1. 按包索引定位", index)}${code("2. 两套深度与有效掩码", depth)}${code("3. 相机与可见分割", camera)}${code("4. 原始手参数与辅助拟合分开", native)}${code("5. 历史路径显式解析", relocation)}
${section("训练边界", content.quality.map((q, i) => `${["完成与训练资格", "公开split与视角", "历史深度格式", "防止变体泄漏", "模拟传感器边界", "数据许可"][i]}
${esc(q)}
`).join(""))}
${section("来源", `${content.references.map(([name, url]) => `
${esc(name)}`).join("")}
`)}`;
}
function samplesView() {
return `${section("Hard Cases · V006", `三类各 5 条10,838 主源帧执行修订 320
${content.samples.map((s) => `
${esc(s.title)}${esc(s.source)}${esc(s.note)}
`).join("")}
`)}
这些是同源动作的历史参考帧,不是当前全部随机变体的逐帧验收。镜面样例在完整生产前另外加入固定种子的污渍材质。
${section(
"样本组织",
table(
["类别", "场景来源", "读取差异"],
[
"| bimanual_crossing | 公开源动作中的真实双手交叉 | 不改变原始手部运动来制造交叉 |
",
"| mirror_reflection | 真实镜面几何与反射 | 镜面深度为物理首交点;反射的人不新增instance |
",
"| multiple_people | 多个源人体及各自衣物 | 检查scene_instances.json的每人源时钟;主源帧数不能直接当所有演员帧总数 |
",
],
),
)}`;
}
function paint() {
if (!root || !content) return;
const body =
tab() === "versions"
? versionPage()
: tab() === "reading"
? readingView()
: tab() === "samples"
? samplesView()
: progressView();
root.innerHTML = header() + body;
root.querySelector("#synth-refresh").onclick = () => refresh();
root.querySelector("#synth-campaign").onchange = (event) => {
selectedCampaign = event.target.value;
if (tab() === "reading") {
paint();
return;
}
updateFreshness();
updateProgress();
};
const filter = root.querySelector("#synth-job-filter");
if (filter)
filter.onchange = () => {
selectedJobs = filter.value;
updateProgress();
};
const search = root.querySelector("#synth-registry-search");
if (search)
search.oninput = () => {
registryQuery = search.value;
registryPage = 0;
updateRegistry();
};
root.querySelectorAll("[data-copy]").forEach(
(b) =>
(b.onclick = async () => {
try {
await navigator.clipboard.writeText(
b.parentNode.querySelector("code").textContent,
);
b.title = "已复制";
b.setAttribute("aria-label", "已复制");
} catch (_) {
b.title = "复制失败";
}
}),
);
updateFreshness();
updateProgress();
updateRegistry();
icons();
}
async function refresh() {
if (!content || !root || request) return;
const own = generation,
button = root.querySelector("#synth-refresh");
button.disabled = true;
const controller = new AbortController();
request = controller;
const timeout = setTimeout(() => controller.abort(), 10000);
try {
const response = await fetch(content.status_url + "?t=" + Date.now(), {
cache: "no-store",
signal: controller.signal,
});
if (!response.ok) throw new Error("HTTP");
const value = await response.json();
if (!valid(value)) throw new Error("schema");
if (own !== generation) return;
if (!snapshot || value.collected_at >= snapshot.collected_at)
snapshot = value;
updateFreshness();
updateProgress();
} catch (_) {
if (own === generation) updateFreshness(true);
} finally {
clearTimeout(timeout);
if (request === controller) request = null;
if (own === generation && root)
root.querySelector("#synth-refresh").disabled = false;
}
}
function unmount() {
generation++;
clearInterval(timer);
clearInterval(ageTimer);
if (request) request.abort();
request = null;
root = null;
}
async function mount() {
root = document.getElementById("synthetic-root");
selectedVersion = versionFromHash(location.hash);
const own = generation;
try {
if (!content) {
const responses = await Promise.all(
["egorender", "egorender-registry", "egorender-progress", "egorender-versions"].map((n) =>
fetch("data/" + n + ".json"),
),
);
if (!responses[0].ok || !responses[3].ok) throw new Error("content");
versionHistory = await responses[3].json();
content = await responses[0].json();
registry = responses[1].ok ? await responses[1].json() : [];
const cached = responses[2].ok ? await responses[2].json() : null;
if (
valid(cached) &&
(!snapshot || cached.collected_at > snapshot.collected_at)
)
snapshot = cached;
}
if (own !== generation || !root) return;
paint();
refresh();
timer = setInterval(refresh, 60000);
ageTimer = setInterval(() => updateFreshness(), 15000);
} catch (_) {
if (root)
root.innerHTML =
'合成数据文档暂不可用,请刷新重试。
';
}
}
export default {
render: () =>
'',
mount,
unmount,
};