Star Conflict 资源提取 — 项目总结
===============================

解包: scunpack.exe (846 PAK) + tpak_extract.py (844 .pak)
模型: Noesis 26插件 (.msh000-987, 99.5%) + msh_to_obj_v3.py (OBJ+UV)
纹理: tex_targem_py.py (纯Python, PHP逻辑迁移) + batch_tex_all.py (批量)

纹理支持现状 (2026-06-18):
  ✅ _d, _nm       — DXT1/3/5, 全覆盖
  ✅ _s (新旧版)   — DXT1/5/RGBA, format 0x07→DXT1
  ✅ _s1           — DXT1/5/RGBA, format 0x07→DXT1, 非方形支持
  ✅ mapskit/decorative — DXT1/3/5/RGBA, 全覆盖
  ✅ fonts         — R8/L8/ARGB, 70/70 (Noesis v2/v3, 纯Python不支持)
  ✅ ui/particles/reaper — 全量
  ✅ levels        — 基本覆盖 (irradiance cubemap缺TFD)

PBR 贴图通道分析 (2026-06-30 — 着色器源码验证):
  ⚠️ v2.3 重大修正：通过 object.fx 着色器源码验证，废弃此前 ORM 假设
  ✅ _d            — RGB=漫反射 (sRGB)
  ✅ _nm           — DXT5nm: R=AO(FetchBumpOccl.z), G=NormalY, A=NormalX (Non-Color)
  ✅ _sc / _msk_sc — RGB=高光颜色, A=Gloss(部分模式) (sRGB)
  ✅ _msk          — R=Height(Parallax), G=AO, B=Glossiness (Non-Color)
                     object.fx L422-428: masks = tex2D(AmbOcclSampler).rgb
                     glossFactor = masks.b; texOcclusion = masks.g
  ✅ _pdo          — R=余弦加权AO, G=PDO方向Y, Z=方向, W=强度 (Non-Color, UV2)
                     object.fx L629: globalOcclusion = pdoTex.x
  ✅ _glow         — RGB=自发光 (sRGB)
  ✅ _s1           — RGB=染色颜色, A=混合权重 (sRGB)
  
  游戏使用 Specular-Gloss 工作流 (Blinn-Phong):
    specPower = exp2( 9 * glossFactor + 2 )  (object.fx L683)
    
  _msk 双用途 (已确认):
    默认模式:  R=未使用, G=AO, B=Gloss
    PARALLAX:  R=高度偏移
    BL2_DETAIL: RGB=细节反射率
    SHIP_DECAL: G=AO
  
  _nm AO vs _msk AO 优先级:
    BL2_DETAIL或PD_OCCL时, _nm的R通道AO覆盖_msk的G通道AO (object.fx L550)
  
批量转换结果: 11,671个TFH → 11,623个DDS成功 (99.6%), 11.2GB
输出目录: scunpack/tex_universe_check/ (保持源层级)

核心工具:
  tex_targem_py.py            — 主力纹理转换 (PHP TargemImage逻辑)
  batch_tex_all.py            — 批量转换, 多进程, 保持目录结构
  tex_StarConflict_tfh_tfd_v3.py — Noesis插件 (PHP mip表 + fallback)
  tex_StarConflict_tfh_tfd_v4_php.py — Noesis插件 (纯PHP对比验证)

已知限制:
  ⚠️ Noesis RGBA渲染: B/R通道互换 (DDS导出正常)
  ⚠️ RGBA DDS兼容性: 部分查看器不支持无压缩RGBA (用Honeyview/GIMP/PS)
  ⚠️ 极小尺寸纹理: 部分mip级仅几个像素, 属正常现象
  ⚠️ irradiance cubemap: 缺TFD文件
   ✅ VBytes=40 flag=0x13: UV偏移已修正 24→20 (2026-07-11, loader/reptile_01)
   ✅ VBytes=40 flag=0x10: UV偏移已修正 24→16 (2026-07-11, fed_mercenary_man, skinned character, sentinel@24)
   ✅ VBytes=32 flag=0x0F: UV偏移已修正 20→16 (2026-07-11, fed_mercenary_tool)
   ℹ️  jerichonec (flag=0x11 vb=28): UV正常, MDF贴图引用正确; 贴图位于 tex_universe_check\models\characters\jericho\*.dds
   ⚠️ VBytes=28模型: 无UV2空间, lightmap使用UV1采样 (PD_OCCL Type=0)
   ✅ fed_plates01_msk 通道: 已通过着色器源码确认为 G=AO, B=Glossiness
   ⚠️ Blender Principled BSDF 与 Blinn-Phong 不完全匹配: Gloss→Roughness 转换为近似

   ── 透明材质着色器分析 (2026-07-02 — animated_mock.fx 源码验证) ──

   ✅ 透明遮罩材质 (SC_animated_mock)
   - 专门用于半透明空气墙/护盾/传送门/屏障效果
   - UV 动画完全由顶点着色器驱动，**无需网格旋转或变换矩阵**
   - VS_INPUT: VertexTransformInput + Tex0(UV1) + Tex1(UV2/lightmap)
   - PS_OUTPUT: baseUv=Tex0, cmUv=Tex1 (Tex1 作为 ColormapSampler 的采样坐标)

   ✅ UV 动画模式 (animated_mock.fx vertex shader):
   - 平移偏移:  o.uv0.xy += UserParam2_Float4.xy (常量正向偏移)
   - 垂直滚动:  o.uv0.y += animTime * UserParam2_Float4.z (随时间滚动)
   - 全方向:    o.uv0 += animTime * UserParam4_Float4.xyzw (UV 四方向平移)
   - 旋转变换:  sincos(animTime * UserParam2_Float4.w * PI*2) 环绕 (0.5,0.5)
   - 离散帧:    UserParam1_Float4.xz=周期, yw=帧数; 公式简化:
                frame = floor(frac(animTime / period) * frames)
                uv += sign * frame / frames
   - 时间源:    CodeParam0_Float4[0].w (游戏代码驱动) 或 GlobalTime.y (回退)

   ✅ ColormapSampler (如 gate_mask02.dds) 在像素着色器中的双用途:
        float4 cm = tex2D(ColormapSampler, cmUv);
        albedo.rgb *= cm.rgb;           ← RGB = 颜色调制 (与漫反射相乘着色)
        albedo.a = cm.a;                 ← A   = 透明度混合 (最终 Alpha 值)
   - ⚠️ 这是 Alpha Blending (透明混合), 不是 Alpha Test (裁剪)
   - 无 CUBE 定义时走 else: albedo.a = cm.a (gate_mask02 走此路径)
   - 有 CUBE 时: albedo = lerp(cube, albedo, cm.a); albedo.a = 1.0

   ✅ gate_mask02 在 pvp_omega map.mdf 中的材质参数 (animated_mock block #14/#17):
        DiffuseSampler    "models\shields\gate_scroll"  ← 基础纹理(卷轴发光图案)
        ColormapSampler   "models\shields\gate_mask02"  ← 遮罩纹理(RGB=色调, A=透明度)
        UserParam0_Float4 ( 1 1 0 0 )                   ← 亮度=1.0, 光泽度=1.0
        UserParam2_Float4 ( 0 0 0.5 0 )                 ← 偏移=(0,0), 滚动速度=0.5, 旋转速度=0
        UserParam3_Float4 ( 0 0 0 1 )                   ← Fresnel 参数
        UserParam1_Float4 ( 1e+006 1 1e+006 1 )         ← 离散帧动画=禁用(周期极大)
     - ⚠️ MDF 默认值中旋转速度=0, 游戏运行时可经由 CodeParam0 覆盖
     - 该材质在 mdl-geo 中无变换节点, 所有动画效果由着色器参数驱动

   ✅ 工程结论: map_504 (msh504) 网格无旋转矩阵, UV 动画完全由 animated_mock.fx 的
     顶点着色器通过 UserParam/CodeParam 运行时参数驱动。gate_mask02 为混合遮罩材质:
     RGB 控制颜色调制, Alpha 控制半透明混合。

版本历史:
  v1: rawtex_py.py (简单TFH启发式) + tex_v1 (简单位流)
  v2: tex_v2 (NoeBitStream + guess_size fallback + font)
  v3: tex_v3 (PHP mip表精确计算 + v2 fallback, 当前主力)
  v4: tex_v4_php (纯PHP逻辑迁移, 对比验证用)

  ── 模型转换工具版本历史 ──

  v1.0 (初始): 面法线翻转，模型在 Blender/Maya 方向异常。
      Noesis 用 RPGOPT_TRIWINDBACKWARD=1 内部修正，其他工具未处理。

  v1.1 (2026-06-22, 试错): 三角形索引反转 + X 镜像
      错误假设左右镜像导致法线问题。索引改为 [i0,i2,i1]，X 轴取反。
      结果: 法线正常，但模型仍左右颠倒，且 Maya 仍需 Y180 旋转。

  v1.2 (2026-06-23, 正确): Z 轴取反 — 前向轴修复
      根因: MSH 前向轴是 -Z，Maya/FBX 前向是 +Z。
      Z 取反 (x,y,z)→(x,y,-z) 一次性解决法线翻转+前向问题。
      修改: msh2fbx.c, Noesis inc_starconflict_msh.py,
            blender_plugin 基础版/Pro版, msh_to_obj_v3.py

  v2.3 (2026-06-30): PBR 通道修正 — 着色器源码验证
      **重大修正**: 通过 object.fx 第422-428行着色器源码验证，废弃此前错误的ORM假设
      _msk 通道: G=AO (非Roughness), B=Glossiness→Roughness (非Metallic)
      _pdo 通道: R=余弦AO (object.fx L629)
      _nm R通道: AO提取 (FetchBumpOccl.z, object.fx L493)
      确认游戏使用 Specular-Gloss 工作流 (Blinn-Phong)
      修改: material_builder.py, shader_presets.py, README.md, README_PRO.md

   v2.2 (2026-06-29): UV2 Lightmap + PBR _msk 通道完整解析
       UV2: msh_parser.py 新增 VBytes=32 的 UV2 解析 (offset=28, uint16_unorm)
            dreadnought_control_cab_imp 等 bigship/map 模型现在正确导入 lightmap UV 通道
       _msk: material_builder.py AmbOcclSampler 从单通道(R)改为三通道(R/G/B)提取
             R→AO/Cavity, G→Roughness, B→Metallic (完整 PBR 还原)
             ⚠️ 以上通道映射已在 v2.3 中通过着色器源码纠正
       shader_presets.py: 更新 object_norm/dyn_object_norm 采样器列表
       README/README_PRO: 新增 PBR 贴图通道分析与 UV2 文档

   v2.4 (2026-07-02): Blender 着色器节点还原修复 — alpha/自发光/lightmap/空气墙混合
       通过遍历所有 .fx 着色器源码验证材质属性映射，修复三项 Blender 节点网络问题:

       ① Alpha 连接 (material_builder.py)
       - sky (大气层): ceres_aura.dds 的 alpha 通道为纯白色, 改用颜色亮度→BSDF Alpha
         (ShaderNodeRGBToBW: 亮部不透明, 暗部透明, 营造大气辉光渐变)
       - skybackground (建筑透贴片): fed_station_01.dds alpha有实际轮廓, 直连BSDF Alpha
       - animated_mock 等透明材质: ColormapSampler.Alpha → BSDF Alpha (albedo.a = cm.a)
       - sky材质 Alpha*Color 乘法保留 (模拟 color.a * MAX_RGBS_RANGE)

       ② 自发光连接 (material_builder.py)
       - sky/skybackground/planets 无 _glow 贴图时: DiffuseSampler → Base Color + Emission Color
       - 材质预览模式下不连接到自发光会显示灰白色

       ③ Lightmap 混合系数 (material_builder.py)
       - 原为简单乘法 (强度=1.0) 导致颜色过暗
       - 改为: BaseColor 与 BaseColor*Lightmap 的 Mix 混合, 默认系数 0.3
       - 通过 ShaderNodeMix 实现用户可调系数

       ④ 空气墙材质 gate 混合 (material_builder.py)
       - map_481 等空气墙: ColormapSampler RGB × DiffuseSampler → Base Color
       - animated_mock.fx: albedo.rgb *= cm.rgb (gate_scroll × gate_mask02 调色)
       - ColormapSampler Alpha → BSDF Alpha (透明度)
       - VB=28 flag=0x000E 无第二UV通道, Tex0/Tex1 共享同一套UV

       shader_presets.py: 新增 sky/skybackground/planets 到 TRANSPARENT_MATERIAL_TYPES
       修改文件: material_builder.py, shader_presets.py, PROJECT_NOTES.txt, README_PRO.md

   ── Shader Type 别名字典与材质合并 (2026-07-03) ──

   背景:
     组件模型 (models/objects/) 使用 dyn_object_norm 等 dyn_ 前缀 shader,
     关卡地图 (mapskit/maps/) 使用 object_norm 等无前缀 shader。
     两者在 material_builder 中走完全相同的节点创建路径、sampler 集功能等价,
     但因 shader_type 不同导致指纹不同, 生成两份完全相同的 Blender 材质。
     例: bigship_empire_01 的 bs_communications_01 材质 vs allidium_yard 的
         同一贴图组 → SC_dyn_object_norm_xxxx vs SC_object_norm_yyyy。

   发现: 两套指纹算法不一致
     material_library.py:366 _compute_fingerprint()     — 缺 UserParam2_Float4
     material_registry.py:255 _compute_fingerprint()    — 含 UserParam2_Float4
     不一致导致: 库 JSON 生成 mat_id ≠ 运行时生成 mat_id → 跨 session 复用失败。
     * 非 FX UV2 导致。FX UV2 是 UV 层命名 ("FX" vs "lightmap"), 无关联。

   方案: Shader Type 别名字典
     shader_presets.py 新增 SHADER_TYPE_ALIASES:
       dyn_object_norm       → object_norm
       dyn_object_norm_blend → object_norm_blend
       dyn_object            → object
       dyn_animated_mock     → animated_mock

     两个 _compute_fingerprint 同步应用别名:
       - fingerprint key 使用 aliased shader_type
       - mat_id 命名也使用 aliased shader_type

   Lightmap 差异保护:
     关卡模型 (pvp_omega) 的 MDF 含 LightmapSampler → 贴图基底名集合多出
     lightmap basename (如 station_03) → 指纹自然不同 → 不会与无 lightmap 的
     组件模型材质错误合并。这是正确的差异化行为。

   验证:
     ✅ fed_big_station_08 (组件): VBytes=28 Flag=0x11 → 无 UV2 → 无 lightmap
     ✅ pvp_omega/map (关卡):  VBytes=32 Flag=0x14 → 有 UV2 → 有 LightmapSampler
     ✅ FX UV2 命名 ("FX" 用于 animated_mock ColormapSampler) 不受影响
     ✅ dyn_glass/dyn_fresnel 等不在别名表中, 不受影响

    修改文件: shader_presets.py, material_registry.py, material_library.py
              README_PRO.md, PROJECT_NOTES.txt

   ── 关卡一键组装 + 批量自动放置 (2026-07-04) ──

   新增文件:
     scene_xml_parser.py  — scene.xml 解析器
     level_assembler.py   — 模型路径解析 + 关卡组装核心

   新增功能:
     ① File → Import → Star Conflict Level (scene.xml) — 一键关卡组装
     ② 批量导入 Auto-Place — 自动从 scene.xml 查找模型世界位置

   scene.xml 解析能力:
     - 提取 <Entity Def="ModelEntity" Model="..."> 实体列表
     - 解析 Pos (世界坐标) + Rot (四元数) 变换
     - 解析 <Inheritance> 子场景链（递归加载）
     - Model 路径 → unpack_root 中查找 .mdl-msh* 文件
     - 策略A: <model_path> 作为目录 → *.mdl-msh*
     - 策略B: <parent>/<basename>.mdl-msh*
     - 策略C: unpack_root 全量递归查找

   坐标系转换:
     Hammer Y-up → Blender Z-up
     - 位置: (hx, hy, hz) → (hx, hz, hy)
     - 四元数: (qx, qy, qz, qw) → (qw, qx, qz, qy)
     - 默认 up_axis='Z_UP_TO_Y_UP' (与批量导入一致)

   关卡静态场景导入:
     mapskit/maps/<area>/<map>/*.mdl-msh* → 全局匹配
     (命名模式: map.mdl-msh*/<map>.mdl-msh*/<comp>.mdl-msh*)

    Def 实体回退:
     当 scene.xml 无 Model= 引用时, 尝试 Def 名称 → 模型路径映射:
       Def="PvE_Geostation_Diafragma" → mapskit/maps/<area>/<map>/pve_geostation_diafragma
     ⚠ 大部分 Def 实体 (Character_*, Debris_*, VitalPoint_*) 模型嵌入引擎,
        解包数据中不存在对应 .mdl-msh 文件, 无法自动导入.

   dyn_glass 材质:
     allidium_glass_01/02 使用 dyn_glass shader, MDF 仅含参数无采样器:
       UserParam0_Float4: 玻璃着色 RGB
       UserParam0_Float[0]: Alpha 不透明度 (默认 0.5)
       UserParam1_Float4: Fresnel 参数
     material_builder.py: BSDF 设置后早期返回 (Transmission=1.0, Alpha Blend)
     新增 _parse_float4() 辅助函数

   Collection 命名:
     场景导入使用 NameResolver (与批量导入一致), 基于文件路径生成层级
     Collection → 基于 unpack_root 相对路径自动组织

   进度提示:
     operators.py → SC_PRO_OT_import_scene_xml.execute()
     使用 wm.progress_begin/update/end + status_text_set

   菜单注册:
     Blender 4.2 中 ImportHelper 自动注册 import_scene.* 操作符
     scene_xml 需手动 TOPBAR_MT_file_import.append (其他两个是自动的)

   修改文件: scene_xml_parser.py (新), level_assembler.py (新),
             operators.py, __init__.py, material_builder.py,
             shader_presets.py, README_PRO.md, PROJECT_NOTES.txt
