"""Evidence-backed method content and editable publication figure for the multiview site."""
from __future__ import annotations
from pathlib import Path
import hashlib
import html
import json
import re
import shutil
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch

ROOT = next(parent for parent in Path(__file__).resolve().parents if (parent / 'wf.py').is_file())
OUT = ROOT / 'outputs/real2sim-dynamics-multiview/site/method'
BASE = ROOT / 'outputs/real2sim-dynamics'
OUT.mkdir(parents=True, exist_ok=True)

def read(path):
    return json.loads(Path(path).read_text(encoding='utf-8'))

def sha(path):
    return hashlib.sha256(Path(path).read_bytes()).hexdigest()

names = ['geometry-1mm-01', 'expansion-thin-aligned-01', 'expansion-thin-blend15-01',
         'expansion-thin-blend30-01', 'expansion-thin-higher-01', 'expansion-thin-highest-01']
runs = {name: read(BASE / name / 'analysis.json') for name in names}
old_object_path = ROOT / 'outputs/real2sim-r1lite/delivery/evidence/cup-physics-final/result.json'
old_object = read(old_object_path)

GATES = [
 ('native_execution_complete','执行完成','原生 execution_status = PASS；全程有限状态检查另随执行及数据审计保留。','执行'),
 ('cups_all_at_or_above_table','杯口不穿桌','三杯最低杯口高度 ≥ 桌面 − 1 mm。','终态几何'),
 ('green_rim_on_table_height','底杯贴桌','绿色杯最低杯口与桌面高度差的绝对值 < 2 mm。','终态几何'),
 ('three_cup_nesting_xy','套叠水平对齐','紫/红杯原点相对绿杯的最大 XY 距离 < 6 mm。','终态几何'),
 ('three_cup_nesting_height','两层套入深度','两级杯口高度差均在 (7.955, 13.259) mm。来自当前 1 mm 锥壳碰撞几何：g = w(h−w)/(R−r)，允差 max(2 mm, 0.25g)。','终态几何'),
 ('all_cups_upright','三杯直立','三杯中最大杯轴倾角 < 8°。','终态几何'),
 ('final_no_positive_cup_finger_contacts','终态脱离手指','最后状态没有正冲量的杯—手指接触。','释放与支撑'),
 ('both_wrists_clear_of_all_cups','双腕撤离','每个腕原点到每个杯原点的距离 > 150 mm；这是保守代理，并非指尖表面最短距离。','释放与支撑'),
 ('support_chain_positive_native_impulses','支撑链成立','原生正冲量历史包含绿杯—桌面、紫杯—绿杯、红杯—紫杯；后两对最近接触时刻分别 ≥ 36.70 s、65.05 s。与终态几何联合判定，允许 PhysX 正常休眠。','释放与支撑'),
 ('complete_half_second_tail','稳定窗口完整','末尾连续 0.5 s 状态记录完整（浮点容差 10⁻⁶ s）。','末段稳定'),
 ('tail_no_positive_cup_finger_contacts','稳定窗口无手指支撑','末 0.5 s 的杯—手指正冲量接触步数 = 0。','末段稳定'),
 ('tail_position_drift_below_1mm','位置稳定','末 0.5 s 三杯相对窗口起点的最大位移 < 1 mm。','末段稳定'),
 ('tail_linear_speed_below_15mm_s','线速度稳定','末 0.5 s 三杯最大线速度 < 15 mm/s；读取原生状态。','末段稳定'),
 ('tail_angular_speed_below_0_15rad_s','角速度稳定','末 0.5 s 三杯最大角速度 < 0.15 rad/s。','末段稳定'),
 ('both_cups_lifted_with_bilateral_contact','确实双指提起','紫/红杯分别在高于初值 25 mm 时有双指正冲量接触，累计时长各 > 0.1 s。','全过程'),
 ('observed_joint_positions_within_configured_usd_limits','关节位置限位','240 Hz 实测 q 在实际配置 USD 限位内；转动/平移容差分别 0.005 rad / 0.5 mm。','全过程'),
 ('observed_joint_velocities_within_urdf_limits','关节速度限位','240 Hz 实测 |q̇| 不超过 URDF 上限加容差：max(上限的 5%, 0.02 rad/s 或 2 mm/s)。','全过程'),
 ('native_penetration_bounded','接触穿透有界','全程最小 solver separation > −1.5 mm，即最大报告穿透 < 1.5 mm。','全过程'),
]
assert set(g[0] for g in GATES) == set(runs[names[0]]['independent_task_assessment']['checks'])

def metrics(name):
    a = runs[name]
    m = a['independent_task_assessment']['metrics']
    c = a['native_contact_diagnostics']
    return dict(id=name, status=a['independent_task_assessment']['status'],
        checks_passed=sum(a['independent_task_assessment']['checks'].values()), checks_total=18,
        max_xy_mm=m['max_stack_xy_offset_m'] * 1000,
        gap_mm=[g * 1000 for g in m['rim_height_gaps_m']],
        max_tilt_deg=max(m['final_tilt_deg'].values()),
        max_penetration_mm=-c['deepest_native_contact']['separation_m'] * 1000)

content = {
 'schema_version': 1,
 'title': '从真实示范到可验证的物理轨迹',
 'subtitle': 'Real2Sim reconstruction · native dynamics · validated 1→X',
 'abstract': {
  'zh': 'WorldFoundry 将真实示范转化为可执行、可追溯的仿真实验：以四路录像和配套关节反馈为输入，结合厂家机器人模型、夹爪 CAD 与估计杯壳几何，分别构建物体、场景和任务。原反馈按原时间轴输入 Isaac Sim，由 PhysX 在 240 Hz 下求解接触与杯子运动；独立评估器同时检查动作执行、接触支撑、释放稳定性及相对真实参考的轨迹差异。每次失败都回溯到具体几何、参数或控制候选，以新版本复验。当前原动作在 1 mm 有效杯壁模型中仍出现红杯侧倒，尚未实现 1:1 物理复现；同一场景中重新生成的三条局部控制变体通过全部 18 项任务检查，形成 54,216 条状态—动作—下一状态转换。该案例把视觉对齐、物理任务成功与真实一致性分开度量，使后续参数辨识和训练数据筛选具有明确证据。',
  'en': 'WorldFoundry turns a recorded demonstration into an executable and traceable simulation experiment. Four camera streams and companion joint feedback are combined with a manufacturer robot model, gripper CAD, and estimated hollow-cup geometry through object, scene, and task harnesses. Recorded feedback is applied on its original clock, while Isaac Sim / PhysX resolves contact and free cup motion at 240 Hz. An independent evaluator checks task execution, support, release stability, and discrepancy from the available real reference. Failures guide explicit revisions of geometry, parameters, or control candidates, each evaluated as a new version. The recorded-motion attempt in the 1 mm effective-wall scene ends with the red cup tipped over; one-to-one physical reproduction is not yet achieved. Three newly generated local control variants in the same scene pass all 18 task gates and yield 54,216 state–action–next-state transitions. The experiment separates visual alignment, physical task validity, and agreement with reality to support subsequent identification and data admission.'
 },
 'pipeline': {'svg':'method/pipeline.svg', 'png':'method/pipeline.png', 'pdf':'method/pipeline.pdf',
              'source':'method/build_method.py', 'caption':'实线为本案例已执行的数据链与候选修订循环；底部虚线区域列出仍需实测的参数辨识。一次原生执行可渲染多视角，视角数量不增加行为条数。'},
 'harness_intro':'Harness 是组织输入、执行器、验证器与证据的程序框架。本案例由任务专用后端接入这套分层组织；通用 scene 接口目前只处理规范化刚体摆放，双臂叠杯并非任意场景、任意资产的通用策略。',
 'harnesses': [
  {'id':'object','title':'Object harness · 物体','input':'厂家机器人 USD/URDF、原始 v5 夹爪 CAD、图像估计的杯壳参数。', 'operation':'分离视觉和碰撞表达；保留杯子空腔，记录质量、重心、惯量和接触材料；几何修改创建独立资产 ID、父版本与哈希。', 'output':'可加载物理资产 + 参数来源 + 对应测试证据。', 'scope':'1 mm 为图像支持的有效壁厚候选。外形与 18 g 质量保持，内壁/碰撞和一致惯量同时重建；材料未实测标定。'},
  {'id':'scene','title':'Scene harness · 场景','input':'物理资产、桌面、相机估计、初始杯姿和机器人初值。', 'operation':'组装相机与支撑关系，设置动力学、驱动、初始状态及可复现源场景；将房间背景用于视觉对照。', 'output':'配置场景 + 初值 + 物理设置与来源闭包。', 'scope':'房间上部为首帧照片投射到估计外壳，不能作为新视角几何或相机标定的证明；参与接触的桌面、杯和机器人具有独立几何。'},
  {'id':'task','title':'Task harness · 任务','input':'原反馈 q(t) 或独立生成的关节目标，以及明确的叠杯判据。', 'operation':'按 1/240 s 推进 PhysX，记录 q/q̇、杯姿/速度、接触对/冲量与动作；独立 evaluator 重算任务成败并筛选数据。', 'output':'240 Hz transitions + 20 Hz 状态缓存 + 任务报告 + 多视角呈现。', 'scope':'生成分支使用离线 IK、轨迹构造和局部插值；驱动跟踪由物理关节执行，没有已训练策略或在线视觉重规划。'}
 ],
 'loop': {
  'title':'Harness loop · 让失败返回具体修改',
  'intro':'每轮固定候选输入，先求解再判定；失败不会通过换相机、删帧或动画补偿变成成功。循环目前由分析脚本与 coding agent 提出修订、再完整执行，尚无自动系统辨识优化器。',
  'steps':[
   {'title':'构建候选','text':'冻结资产/场景、初值、驱动目标、求解设置与源码 SHA。原反馈分支保留真实时钟；生成分支另存 targets。'},
   {'title':'原生执行','text':'PhysX 240 Hz 求解。杯子自由动态，记录中没有杯姿/杯速度覆盖、抓取焊接或脚本物体外力。'},
   {'title':'独立验证','text':'从原生日志重算 18 项任务 gate，并检查连续性、动作重建、实际关节限位、缓存对应及来源哈希。'},
   {'title':'定位差异','text':'同时看四相机同钟对比、腕部跟踪、推定杯姿差异、接触对/穿透时刻和释放后的支撑链。'},
   {'title':'修订并复验','text':'按证据修改碰撞体、杯壳参数或控制轨迹，创建新版本完整复验。例：掌部凸包→凸分解、有效壁厚 1.5→1 mm、松爪净空与时序变体。'},
   {'title':'按结果入库','text':'通过的同场景轨迹进入成功示范索引；失败保留作诊断。每条行为关联自身场景、控制、原生日志与可用图像。'}
  ],
  'real_fit':'实物参数辨识需新增测量：相机 K/D 与时钟、杯内壁/卷边、开口读数映射、摩擦/质量和 TPU 顺应性，以及原控制 commands。之后用未参与拟合的片段复验真实一致性。'
 },
 'physics': {
  'title':'Physical validation · 从资产到完整任务',
  'intro':'当前任务的 18 项检查直接读取完整原生轨迹。PASS 表示这条轨迹满足本场景协议；真实一致性必须另外比较真实测量，不能由渲染或任务成功推定。',
  'current_model':'Isaac Sim 6.1 / PhysX；1/240 s 步长；固定底盘、关闭机器人自碰撞；v5 TPU 95A CAD 采用刚性接触。主场景三杯均为 1 mm 有效杯壁、18 g 估计质量；指尖驱动上限 3 N，质量、摩擦和驱动参数未实物标定。',
  'gate_groups':['执行','终态几何','释放与支撑','末段稳定','全过程'],
  'gates':[dict(id=key,label=label,threshold=threshold,group=group,
              original_pass=runs[names[0]]['independent_task_assessment']['checks'][key],
              generated_pass=runs[names[1]]['independent_task_assessment']['checks'][key]) for key,label,threshold,group in GATES],
  'run_metrics':[metrics(name) for name in names],
  'gate_note':'这些是当前叠杯协议阈值，不是跨物体通用精度或实物误差标准。间距来自配置碰撞几何；速度/位置限位采用实际 USD/URDF 的明确规则。正冲量记录来自求解器，并非真机力传感。',
  'historical_object':{
   'title':'既有物体级验证：六项独立检查',
   'status':old_object['status'],
   'tests':['资产结构与空腔','引擎有效惯量','自由落下与静置','受力滑动','重力套叠','最终旧场景 1 s 姿态保持'],
   'text':'旧 1.5 mm 紫杯及其绑定场景执行了上述六项原生 PhysX 检查；绿/红杯以相同物理结构合同作等价核对。它们验证旧资产与对应夹具，不能转移为新 1 mm 变体的六项单体测试通过；新变体本轮的直接证据来自独立完整任务执行与 18 项 gate。',
   'protocol':'旧单体协议使用最大穿透 < 3 mm、停稳线速度 < 15 mm/s、角速度 < 0.15 rad/s；重力套叠 XY < 10 mm、倾角 < 10°。它与本次完整任务的 1.5 mm 穿透阈值和 6 mm 对齐阈值分开记录。',
   'evidence':'../../real2sim-r1lite/delivery/evidence/cup-physics-final/result.json'
  },
  'admission':{'title':'通过后，数据怎样进入训练候选？','text':'同一 1 mm 场景中 aligned、blend15、blend30 三条局部控制变体通过全部 18 项检查；higher、highest 两条失败保留。合计 54,216 条 240 Hz transitions、4,521 个 20 Hz 状态缓存。原训练索引中 aligned 有两视角共 3,014 张 RGB，其余两条只有状态/动作。此次补充的展示视频不更改旧训练索引，也不算新增行为；尚未训练策略或验证真机迁移。'}
 },
 'findings':[
  {'title':'原动作为何没有等同复现？','text':'当前原反馈候选红杯最终倾角 95.385°，最大套叠 XY 偏移 34.517 mm；18 项中 14 项通过。失败项集中在水平对齐、套入高度、直立和穿透。真实日志是反馈而非原始 command，夹爪开口映射与接触参数还不能唯一确定真实控制和材料响应。'},
  {'title':'最大差异在接触后被放大','text':'1 mm 候选右腕相对反馈 FK 的全程 RMSE 为 2.489 mm，但红杯仍侧倒。原动作 strict 的红杯相对推定参考 RMSE 在搬运段为 19.87 mm，释放后增至 210.92 mm；有效壁厚调整后对应为 14.40 / 28.25 mm。需要优先解释杯口接触、落座与释放支撑，而不是只拟合自由空间腕轨迹。'},
  {'title':'几何有效，但不构成单一因果解释','text':'杯壁从 1.5→1 mm 同时改变内壁、碰撞与一致惯量，红杯全程位置 RMSE 从 76.352→11.252 mm；最大穿透却从 2.337→3.442 mm。图像拟合支持继续辨识几何，尚不能证明真实壁厚、摩擦或 TPU 参数已校准。'},
  {'title':'怎样理解真实轨迹误差？','text':'腕部参考来自原反馈与厂家 FK；杯姿参考来自图像拟合、FK 和支撑假设，不是独立 6D 真值。旧参考把底部绿杯固定，但真实图像中它发生运动。因此杯轨迹 RMSE 是对推定参考的差异，不能直接等同真机定位误差。'}
 ],
 'evidence':[
  {'label':'完整实验报告','href':'../../real2sim-dynamics/REPORT.md'},
  {'label':'原反馈 1 mm 独立验证','href':'../../real2sim-dynamics/geometry-1mm-01/analysis.json'},
  {'label':'生成 aligned 独立验证','href':'../../real2sim-dynamics/expansion-thin-aligned-01/analysis.json'},
  {'label':'训练数据索引','href':'../../real2sim-dynamics/training-index.json'},
  {'label':'原反馈时间诊断曲线','href':'../../real2sim-dynamics/diagnostics/original-feedback-comparison/motion-differences.png'},
  {'label':'Object validation 协议','href':'../../../docs/PHYSICAL_VALIDATION.md'}
 ],
 'link_base':'All href and pipeline paths are relative to the site index.html, not this JSON file.',
 'source_evidence':[]
}

source_paths = [BASE/'REPORT.md', BASE/'summary.json', BASE/'training-index.json', old_object_path,
               ROOT/'src/worldfoundry/backends/real2sim_dynamics/evaluate.py',
               ROOT/'src/worldfoundry/backends/real2sim_dynamics/expand.py',
               ROOT/'src/worldfoundry/harnesses/object.py', ROOT/'src/worldfoundry/harnesses/scene.py',
               ROOT/'src/worldfoundry/harnesses/task.py', ROOT/'docs/PHYSICAL_VALIDATION.md']
source_paths += [BASE/name/'analysis.json' for name in names]
for path in source_paths:
    content['source_evidence'].append(dict(path=str(path), sha256=sha(path)))
(OUT/'method-content.json').write_text(json.dumps(content,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
(OUT/'method-content.js').write_text('window.WF_METHOD_CONTENT = '+json.dumps(content,ensure_ascii=False,indent=2)+';\n',encoding='utf-8')

plt.rcParams.update({'font.family':['Microsoft YaHei','Arial','DejaVu Sans'], 'font.size':15,
    'svg.fonttype':'none', 'pdf.fonttype':42, 'axes.spines.top':False, 'axes.spines.right':False})
fig, ax = plt.subplots(figsize=(17,10),dpi=160)
fig.patch.set_facecolor('white'); ax.set_facecolor('white')
ax.set_xlim(0,17);ax.set_ylim(0,10);ax.axis('off')
BLUE='#0F4D92'; GRAY='#4D4D4D'; GREEN='#2D7050'; RED='#B64342'

def txt(x,y,s,size=14,color=GRAY,weight='normal',ha='left',va='top'):
    return ax.text(x,y,s,fontsize=size,color=color,fontweight=weight,ha=ha,va=va,linespacing=1.65)
def box(x,y,w,h,title,body,color=BLUE,fill='#F0F5FB',fs=14):
    ax.add_patch(FancyBboxPatch((x,y),w,h,boxstyle='round,pad=0.015,rounding_size=0.10',
                 facecolor=fill,edgecolor=color,linewidth=1.5))
    txt(x+.18,y+h-.17,title,16,color,'bold')
    txt(x+.18,y+h-.66,body,fs)
def arrow(a,b,color=BLUE,style='-',rad=0):
    ax.add_patch(FancyArrowPatch(a,b,arrowstyle='-|>',mutation_scale=18,linewidth=1.7,
                 color=color,linestyle=style,connectionstyle=f'arc3,rad={rad}'))

txt(.25,9.85,'WorldFoundry  |  从真实示范到可验证的物理轨迹',24,BLUE,'bold')
txt(.25,9.27,'本案例：四路视频 + 配套关节反馈   →   原生物理检验   →   同场景 1→X',15)

box(.25,6.85,3.55,1.95,'01  真实证据',
    '四相机 · 原始时钟 · 关节反馈\n厂家模型 / 夹爪 CAD\n图像拟合杯姿与初始场景',fs=14)
box(4.18,6.85,3.85,1.95,'02  Object harness',
    '视觉网格 + 保留空腔的碰撞体\n质量 / 惯量 / 材料与来源\n新资产版本 + 适用测试',fs=14)
box(8.41,6.85,3.85,1.95,'03  Scene harness',
    '机器人 / 桌面 / 杯 / 四相机\n初值 + 动力学与驱动设置\n估计房间背景用于视觉对照',fs=14)
box(12.64,6.85,4.1,1.95,'04  Task harness',
    'A  原反馈 q(t)：同钟复现尝试\nB  离线 IK + 局部控制变体\n明确任务目标与独立判据',fs=14)
for a,b in [(3.8,4.18),(8.03,8.41),(12.26,12.64)]:arrow((a,7.81),(b,7.81))

box(10.53,3.75,6.21,2.26,'05  Isaac Sim / PhysX 原生执行',
    '240 Hz：关节驱动、重力、摩擦和接触求解\n杯子自由动态；不写杯姿、不焊接抓持\n保存状态—动作—下一状态与接触日志\n20 Hz 缓存 → 四相机同钟呈现',fs=14)
arrow((14.69,6.85),(14.69,6.01))
box(4.18,3.75,5.85,2.26,'06  独立验证 + 轨迹差异',
    '18 项：提起 / 套叠 / 释放 / 稳定 / 限位 / 穿透\n原生日志连续性、动作与源哈希检查\n腕部 vs 反馈 FK；杯姿 vs 推定视觉参考\n物理任务成功与真实一致性分别报告',fs=13.5)
arrow((10.53,4.88),(10.03,4.88))
box(.25,3.75,3.43,2.26,'可追溯证据',
    '资产 / 场景 / 控制 / 源码\n运行日志与接触时间点\n同钟多视角对照\n每一轮保留独立版本',color=GRAY,fill='#F7F7F7',fs=13)
arrow((4.18,4.88),(3.68,4.88),GRAY)

box(.25,.95,7.78,1.96,'FAIL / 差异  →  诊断与修订',
    '定位接触对、穿透、套入深度和释放支撑\n修订碰撞几何 / 有效参数 / 控制候选，再完整执行\n本次由分析脚本 + coding agent 推进；原动作仍 FAIL',
    color=RED,fill='#FFF3F1',fs=14)
box(8.41,.95,8.33,1.96,'PASS  →  数据准入与同场景 1→X',
    '三条局部控制变体通过 18 项任务 gate；两条失败保留\n54,216 transitions · 4,521 状态 · 旧索引含 3,014 RGB\n成功示范关联各自初值、控制、环境与证据；尚未训练策略',
    color=GREEN,fill='#EEF8EF',fs=14)
arrow((5.6,3.75),(4.15,2.91),RED)
arrow((8.6,3.75),(11.7,2.91),GREEN)
ax.plot([.25,.08,.08,6.10,6.10],[1.85,1.85,6.45,6.45,6.85],color=RED,lw=1.6)
arrow((6.1,6.45),(6.1,6.84),RED)
txt(.25,6.70,'复验：新资产 / 参数 / 控制版本',11,RED)
txt(.25,.42,'实物辨识（待新增测量）：相机与时钟、杯内壁、夹爪开口、摩擦 / TPU、真实 commands → 独立片段验证',12,GRAY)
ax.plot([.25,16.75],[.65,.65],color=GRAY,lw=1.1,linestyle='--')
fig.tight_layout(pad=.6)
for ext in ('svg','pdf','png'):
    fig.savefig(OUT/f'pipeline.{ext}',dpi=300,facecolor='white',bbox_inches='tight',pad_inches=.15)
plt.close(fig)

# Integration-ready semantic HTML; links are deliberately site-index-relative.
E=html.escape
parts=['<section id="method" class="method-section">',
 '<div class="section-heading"><span class="eyebrow">METHOD</span><h2>'+E(content['title'])+'</h2></div>',
 '<article class="method-abstract"><h3>Technical abstract</h3><p>'+E(content['abstract']['zh'])+'</p>',
 '<details><summary>English abstract</summary><p lang="en">'+E(content['abstract']['en'])+'</p></details></article>',
 '<figure class="method-pipeline"><a href="method/pipeline.svg" target="_blank"><img src="method/pipeline.svg" alt="真实证据经物体、场景、任务 harness 进入原生物理执行，独立验证后将失败返回诊断修订，将成功记录为可追溯示范。"></a><figcaption>'+E(content['pipeline']['caption'])+' <a href="method/pipeline.pdf">PDF</a> · <a href="method/pipeline.svg">SVG</a> · <a href="method/build_method.py">可编辑源代码</a></figcaption></figure>',
 '<h3>Object / Scene / Task harness</h3><p>'+E(content['harness_intro'])+'</p><div class="harness-grid">']
for h in content['harnesses']:
    parts+=['<article class="harness-card"><h4>'+E(h['title'])+'</h4>']
    for label,key in [('输入','input'),('执行','operation'),('输出','output'),('当前范围','scope')]:
        parts+=['<p><strong>'+label+'：</strong>'+E(h[key])+'</p>']
    parts+=['</article>']
parts+=['</div><h3>'+E(content['loop']['title'])+'</h3><p>'+E(content['loop']['intro'])+'</p><ol class="harness-loop">']
for s in content['loop']['steps']:
    parts+=['<li><strong>'+E(s['title'])+'</strong><p>'+E(s['text'])+'</p></li>']
parts+=['</ol><p class="method-scope">'+E(content['loop']['real_fit'])+'</p></section>',
 '<section id="validation" class="method-section"><div class="section-heading"><span class="eyebrow">PHYSICAL VALIDATION</span><h2>每条轨迹，逐项验收</h2></div>',
 '<p>'+E(content['physics']['intro'])+'</p><p>'+E(content['physics']['current_model'])+'</p>',
 '<div class="validation-summary"><article><span>原反馈 · geometry-1mm</span><strong class="fail">FAIL · 14 / 18</strong><p>水平对齐、套入高度、直立和穿透未通过。</p></article><article><span>生成控制 · thin-aligned</span><strong class="pass">PASS · 18 / 18</strong><p>同场景的 blend15 / blend30 也通过 18 项。</p></article></div>',
 '<div class="method-table-wrap"><table class="validation-table"><thead><tr><th>检查项</th><th>本次判据</th><th>原反馈</th><th>生成 aligned</th></tr></thead><tbody>']
for i,g in enumerate(content['physics']['gates'],1):
    parts+=['<tr><th scope="row"><small>'+E(g['group'])+'</small>'+str(i)+'. '+E(g['label'])+'</th><td>'+E(g['threshold'])+'</td>']
    for key in ['original_pass','generated_pass']:
        parts+=['<td class="'+('pass' if g[key] else 'fail')+'">'+('PASS' if g[key] else 'FAIL')+'</td>']
    parts+=['</tr>']
parts+=['</tbody></table></div><p class="method-scope">'+E(content['physics']['gate_note'])+'</p>',
 '<details class="object-validation"><summary>'+E(content['physics']['historical_object']['title'])+'</summary><p>'+E(content['physics']['historical_object']['text'])+'</p><ul>']
for t in content['physics']['historical_object']['tests']:parts+=['<li>'+E(t)+'</li>']
parts+=['</ul><p>'+E(content['physics']['historical_object']['protocol'])+'</p><a href="'+E(content['physics']['historical_object']['evidence'])+'">旧物体协议与原生结果</a></details>',
 '<h3>'+E(content['physics']['admission']['title'])+'</h3><p>'+E(content['physics']['admission']['text'])+'</p></section>',
 '<section id="findings" class="method-section"><div class="section-heading"><span class="eyebrow">WHAT THE EXPERIMENT SHOWS</span><h2>真实与仿真，差异在哪里？</h2></div><div class="findings-grid">']
for f in content['findings']:parts+=['<article><h3>'+E(f['title'])+'</h3><p>'+E(f['text'])+'</p></article>']
parts+=['</div><div class="method-evidence">']
for e in content['evidence']:parts+=['<a href="'+E(e['href'])+'">'+E(e['label'])+'</a>']
parts+=['</div></section>']
fragment = '\n'.join(parts)
fragment = fragment.replace('<div class="method-table-wrap">', '<details class="method-gates"><summary>展开全部 18 项阈值与逐项结果</summary><div class="method-table-wrap">')
fragment = fragment.replace('</tbody></table></div>', '</tbody></table></div></details>')
fragment = re.sub(r'class="([^"]+)"', lambda m: 'class="'+' '.join(c if c.startswith('method-') else 'method-'+c for c in m[1].split())+'"', fragment)
(OUT/'method-fragment.html').write_text(fragment,encoding='utf-8')
(OUT/'method.html').write_text(fragment,encoding='utf-8')
css = '''
.method-section {padding: 50px 0; border-top: 1px solid #dbe3e7; color: #243341;}
.method-section h2 {font-size: clamp(26px, 3vw, 38px); line-height: 1.25; margin: 10px 0 24px; letter-spacing: -.035em;}
.method-section h3 {font-size: 22px; line-height: 1.4; margin: 30px 0 12px;}
.method-section h4 {font-size: 17px; margin: 0 0 12px; color: #144b72;}
.method-section p {font-size: 15px; line-height: 1.85; margin: 10px 0; max-width: none;}
.method-section strong {font-weight: 650;}
.method-section a {color: #0f4d92; text-underline-offset: 3px;}
.method-section details {border: 1px solid #dbe3e7; border-radius: 12px; padding: 16px 20px; background: #fff; margin-top: 16px;}
.method-section summary {cursor: pointer; color: #144b72; font-weight: 650; line-height: 1.6;}
.method-section details[open] summary {margin-bottom: 15px;}
.method-eyebrow {font-size: 11px; font-weight: 700; letter-spacing: .16em; color: #477185;}
.method-abstract {padding: 24px 28px; border-left: 3px solid #0f4d92; background: #f0f5f8; border-radius: 0 12px 12px 0;}
.method-abstract h3 {margin-top: 0;}
.method-abstract details {background: transparent; padding: 10px 0 0; border: 0; margin-top: 10px;}
.method-pipeline {margin: 30px 0;}
.method-pipeline img {display: block; width: 100%; height: auto; border: 1px solid #dbe3e7; border-radius: 12px; background: white;}
.method-pipeline figcaption {font-size: 12px; line-height: 1.7; color: #60727f; margin-top: 10px;}
.method-harness-grid {display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px;}
.method-harness-card {padding: 22px; border: 1px solid #dbe3e7; border-radius: 12px; background: #fff;}
.method-harness-card p {font-size: 13px;}
.method-harness-loop {display: grid; grid-template-columns: repeat(3,minmax(0,1fr)); gap: 16px; padding: 0; list-style: none; counter-reset: loop;}
.method-harness-loop li {counter-increment: loop; padding: 20px; background: #f0f5f8; border-radius: 12px;}
.method-harness-loop li:before {content: '0' counter(loop); display: block; color: #477185; font-size: 12px; letter-spacing: .12em; margin-bottom: 10px;}
.method-harness-loop p {font-size: 13px; margin-bottom: 0;}
.method-scope {color: #60727f; font-size: 13px !important;}
.method-validation-summary {display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 22px 0;}
.method-validation-summary article {padding: 20px 24px; background: #f5f7f8; border-radius: 12px;}
.method-validation-summary span {display: block; font-size: 12px; color: #60727f;}
.method-validation-summary strong {display: block; font-size: 28px; margin: 10px 0;}
.method-validation-summary p {font-size: 13px;}
.method-pass {color: #28734d !important; font-weight: 650;}
.method-fail {color: #b64342 !important; font-weight: 650;}
.method-table-wrap {overflow-x: auto;}
.method-validation-table {width: 100%; min-width: 680px; border-collapse: collapse; font-size: 13px; text-align: left;}
.method-validation-table th,.method-validation-table td {padding: 13px 12px; border-bottom: 1px solid #e2e8ec; line-height: 1.65; vertical-align: top;}
.method-validation-table thead th {background: #f0f5f8; color: #144b72; white-space: nowrap;}
.method-validation-table tbody th {min-width: 160px; font-weight: 600;}
.method-validation-table small {display: block; font-size: 10px; color: #60727f; font-weight: normal;}
.method-validation-table td:nth-last-child(-n+2) {white-space: nowrap;}
.method-object-validation ul {columns: 2; padding-left: 20px; line-height: 1.9; font-size: 14px;}
.method-findings-grid {display: grid; grid-template-columns: 1fr 1fr; gap: 20px;}
.method-findings-grid article {padding: 22px; background: #f5f7f8; border-radius: 12px;}
.method-findings-grid h3 {font-size: 18px; margin-top: 0;}
.method-findings-grid p {font-size: 14px;}
.method-evidence {display: flex; flex-wrap: wrap; gap: 12px 24px; padding-top: 24px; font-size: 13px;}
@media(max-width: 850px) {.method-harness-grid,.method-harness-loop {grid-template-columns: 1fr;}}
@media(max-width: 600px) {.method-validation-summary,.method-findings-grid {grid-template-columns: 1fr;} .method-abstract {padding: 18px;} .method-section {padding: 35px 0;} .method-object-validation ul {columns: 1;}}
'''
(OUT/'method.css').write_text(css,encoding='utf-8')
if Path(__file__).resolve() != (OUT/'build_method.py').resolve():
    shutil.copy2(__file__,OUT/'build_method.py')
(OUT/'method-evidence.json').write_text(json.dumps({'status':'PASS','scope':'Content values extracted from bound historical reports; no new physics execution.',
 'gate_count':18,'original_pass_count':14,'generated_pass_count':18,'sources':content['source_evidence'],
 'artifacts':{p.name:sha(p) for p in OUT.iterdir() if p.is_file() and p.name!='method-evidence.json'}},ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print(json.dumps({'output':str(OUT),'gates':18,'sources':len(source_paths),'figure':'pipeline.svg'},ensure_ascii=False))
