Compare commits

...
16 Commits
Author SHA1 Message Date
shenlei 1db9e9a129 no message 2026-09-16 22:15:52 +09:00
shenleiandClaude Opus 5 400e3e8ea5 Merge classic enter-game framing fixes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
2026-09-12 21:29:22 +09:00
shenleiandClaude Opus 5 85ce75e707 fix(net): 修复 40250 进入游戏时的收包错位
- GC_ITEM_DEL(20) 按 40250 旧结构为 42 字节,GC_REFINE_INFORMATION(95) 为 59 字节
- 公会标记连接接受 GC_MARK_DIFF_DATA(101),按 1 字节消费
- GUILD_SUBHEADER_GC_SKILL_INFO 服务器声明 22 字节但实际只写 21 字节,
  按实际长度分帧,修复 "unknown GC header 200 (last: 75,20)" 断线
- 会话测试的 quest info 包改为 40250 按 flag 定长的格式,新增对应分帧测试

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
2026-09-12 21:29:21 +09:00
shenlei 474a709f69 Merge combat 40250 parity fixes 2026-09-12 13:15:28 +09:00
shenleiandClaude Opus 5 1933a5ceda fix(combat): 战斗逐项对齐 40250,修复移动 / 技能释放与武器挂点
- 连击类型由 combo_changed 驱动(SetComboSkillFlag,技能 122 等级)
- 命中判定改为 .msa 刀尖 Z 圆柱 vs .msm 防御球(hit_collision.gd 逐行移植)
- _register_hit 改为 map::insert 语义,修正命中上限
- 攻速同时缩放 GetAttackingElapsedTime 与攻击动作速率;按武器动作模式目录绑定攻击段
- 去掉本地连击超时,motion_bound 清命中表 / 连击段号
- __ProcessDataAttackSuccess:InsertDelay 硬直、physics_push.gd 击退 + GetBlendingPosition 同步、
  命中特效、__HitGood / __HitGreate / __HitStone 受击动作链与抖动(ui/hit_reaction.gd)
- ClassicSession::send_use_skill 不再夹带 CG_FLY_TARGETING
- mac 前进 / 后退方向与技能释放修复;武器按职业骨骼挂到手上
- 新增 hit_collision / physics_push / hit_view / net_world_push / weapon_attach 测试;
  gpu_pose_bounds / race_motion_assembly 适配 damage 随机变体
- 文档:CLIENT-GAP.md、CLIENT-GAP-FIX.md §3.5 / §3.7 与 C.5 增量 130

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
2026-09-12 13:15:28 +09:00
shenandshen 96c29d2bb7 Merge playable STB-01 validation fixes 2026-09-11 21:23:52 +08:00
shenandshen cc5573ddc1 fix(playable): close release validation lifecycle gaps 2026-09-11 21:23:44 +08:00
shenleiandClaude Opus 5 f39a55fdd5 feat(playable): 首个 Mac 联网内测版 STB-01 soak 工具与发布状态文档
- 新增 script/playable_soak.sh:先做 soak 配置/资源校验,无 --allow-gameplay
  只校验不启动客户端;N(>=10) 次独立正常退出运行 + 墙钟 soak,失败即停止后续
  运行并记 BLOCKED;写本批次 release-manifest.json 后聚合
- run_client_gate.sh:确认的故障代理对所有 suite 启动(共享场景的退出运行也经代理);
  soak 超时下限只约束 soak 客户端(validate_soak 增加 soak_client 参数)
- 新增本地 127.0.0.1 故障代理、RSS 采样/内存判定、窗口/指标/流程模块及其测试
- forest_mob_render_test 输出 PASS/FAIL 标记,供 rendering_batch_test.sh 识别
- 新增 docs/FIRST-MAC-PLAYABLE-STATUS.md:如实记录 PASS/BLOCKED、已知问题与环境需求
- .gitignore 排除本地场景配置、凭据文件与运行输出

离线回归:rendering_batch_test.sh failures=0,playable_gate_test.sh PASS,
node 夹具测试 PASS。未联网运行,未重建候选包。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ft3kXpNdLbKEfg5uDLfqVF
2026-09-11 22:06:48 +09:00
shenandshen 374d4165d8 Implement playable Mac client and rendering validation 2026-09-11 14:58:22 +08:00
shenandshen 1ab68bd06e Merge branch 'main' of http://192.168.21.200:8418/4v5u09Z5a4Yuc/metin2-client
# Conflicts:
#	docs/RENDERING-REPAIR-PLAN.md
2026-09-08 16:25:54 +08:00
shenleiandClaude Opus 5 e0c1853909 fix(net): 主角 VID 归参考端所有,GC_CHARACTER_DEL 不再把玩家「去主角化」
实机故障:进入游戏后整屏灰紫,主角还是蓝色胶囊占位体,2D UI 与小地图正常。

根因(已实证,非推测)。`EntityStore::mut_despawn()` 在 despawn 的 vid 等于
主角 vid 时把 `m_main_vid` 清零。40250 服务器在 spawn-in 头几秒会对玩家自己的
vid 反复下发 GC_CHARACTER_DEL + GC_CHARACTER_ADD(视野重算,实测约 10 次)。
第一次 DEL 就把玩家永久去主角化:`get_main_vid()` 返回 0 →
`_sync_main_character()` 直接 return → 建模代码从未跑过(main_attempts=0)→
占位胶囊留在 y=0 → 相机跟着它钻到 C1 地形底下 → 满屏灰紫。地图其实一直是好的
(chunks_built 20/20、objects_placed 565)。

参考端怎么做(40250 ClientVS22)。主角身份属于 `CNetworkActorManager::m_dwMainVID`,
只由 `SetMainActorVID()`(GC_MAIN_CHARACTER)写,只在管理器析构时清;
`RemoveActor()` 删 actor 行和角色实例,但绝不碰它;重新加入时
`__AppendCharacterManagerActor()` 用 `kCreateData.m_isMain = __IsMainActorVID(dwVID)`
重新推导,并再跑一次 `SetMainCharacterIndex(dwVID)`。

修法(entity_store.cpp):
- `mut_despawn()` 只删行、只发 Despawn,不动 m_main_vid;m2dev 分支的
  GC_CHARACTER_DEL 同规则。
- `mut_spawn_full()` 在 `e.vid == m_main_vid` 时把 `is_main` 置回 true,
  并重新广播 MainSet,对齐参考端的重新推导。

顺带落地的渲染修复(同一次实机排查中定位):
- libgr2 `sample_pose()` 增加 `root_offset` 形参;`Metin2AnimPlayer::apply_pose()`
  显式传单位阵而不是模型的 InitialPlacement。Metin2 动画 gr2 的根骨轨道自带骨盆
  高度,再乘一次 IP 会把整个人抬高一个骨盆高(武 99.4cm / 刺 92.6 / 巫 103.7 /
  萨 95.5,正好各自的 IP.z)——即此前的「人物悬空」。`bind_pose()` 语义不变。
- `Metin2Model::_load_hair()` 改用与身体/武器同一条 `make_material()`
  (specular_disabled)而不是 roughness=1.0 的 StandardMaterial3D,头发不再被
  PBR 高光洗成奶白色块。

取证与回归工具:
- `project/net_trace.gd`(新):主角 vid 生命周期取证。挂在 AppFlow 之后逐帧读
  `get_main_vid()`,归零时打印 `NETTRACE: MAIN_LOST was=.. now=0 reason=..` 加最近
  24 条环形事件;`reason` 区分 despawn 与 silent(reset_for_map_change 清空
  m_changes,是静默清零)。`MT_NETTRACE=1` 打开逐事件日志。
- `game_scene.gd`:诊断 tick 支持重复采样(分辨主角装配是「没跑」还是「没跑完」),
  新增 main_sync_ready / main_loading / main_view_key / main_map_vid /
  main_retry_in_ms 字段;`MT_DIAG_SHOT=<png>` 让游戏自己存一帧实机画面
  (screencapture 拍到的是终端窗口,没用)。
- `project/model_render_test.gd` + `test/rendering/model_baseline.json`(新):
  八个种族的模型渲染基线(--bless 重新落盘)。
- `project/char_bench_test.gd`(新):无网络无地图的单角色实验台,五变体 ×
  六机位出证据图,只产图不做阈值判定。
- `project/package_render_test.gd`(新):导出包内的渲染自检入口
  (导出模板不接受 --script,只能走 MT_TEST_MODE)。
- `/project/build/` 加入 .gitignore(渲染用例的落图输出)。

实机复验(真实服务器,非模拟):
- 修前 `LIVE_SMOKE FAIL: 主角色 VID 有效`;
  `NETTRACE: MAIN_LOST was=25910 now=0 reason=despawn t=+7737ms`,
  环里紧跟 `spawn vid=25910 is_main=false`。
- 修后 `LIVE_SMOKE RESULT: PASS`,main_vid=25933、entities=19;
  40s 实机跑 16 次诊断采样全部 model_built=True / main_attempts=1 /
  main_sync_ready=True,player_pos 落在 C1 地形上而非 y=0;
  MT_DIAG_SHOT 存下的帧里地形、建筑、NPC、树木、真实角色模型、HUD、小地图俱全。

回归:gamescene_test / equip_model_test / gamescene_visual_test(feet_gap
0.001373)/ model_render_test(8 races)/ char_select_visual_test / 包内
MT_TEST_MODE=render 全绿;ctest 22/23,唯一失败 net.classic_session 已用
「还原本次改动 → 重跑 → diff 失败集合」确认与本次修改无关(失败集合完全相同)。

注:画面整体偏暗属于光照/色调差距,受 PARITY-GAP.md §0 门禁约束(对照帧未落盘前
不做相机/光照/色调/材质调参),本次不碰。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJugvEJwz3FK4hw9ti3SRb
2026-09-08 17:15:55 +09:00
shenleiandClaude Opus 5 fb4d2d222b docs: 文档审计——修正失效链接与过期结论
按 docs/ 目录逐篇核对,只改与事实/路径不符的部分,不动尚未验证的计划条目:

- `docs/steps/**` 在早前整理时已迁到 `docs/reference/steps/**`,全仓的旧路径
  引用(PLAN.md / README.md / M0-gr2-reader.md 及 libgr2 的头注释)一并改正。
- CLIENT-GAP.md / CLIENT-GAP-FIX.md / BACKLOG.md / MIDREVIEW.md:把已经落地的
  条目从「待办」改为已完成,删掉与代码现状矛盾的描述。
- ANDROID-TESTING.md / CLIENT-PORT.md / CLIENT-ROADMAP.md / GODOT-POC-PLAN.md /
  SHINSOO-WORLD-RENDERING.md / PARITY-GAP.md:同上,另补当前实际的构建/测试入口。
- THIRD-PARTY.md:补齐实际在用的第三方来源与许可说明。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJugvEJwz3FK4hw9ti3SRb
2026-09-08 17:15:17 +09:00
shenandshen cea79fe17c fix(rendering): complete character visual regression fixes 2026-09-08 13:26:42 +08:00
shenandshen c3a6fb973e fix(rendering): checkpoint main-character recovery and visual regression work
Handle late main-character data, guarded model construction, bounded retries and map correction. Validate player model sources and add lifecycle and real-asset visual regressions.

Include pending material and character-selection changes, updated A1 screenshot, and the detailed rendering repair plan. Hair occlusion, full UI parity and final macOS package acceptance remain unfinished.
2026-09-08 08:52:30 +08:00
shenleiandClaude Code 8092ff44e2 装备校验链/快捷栏/公会/好友情侣/交易仓库五项差距修复(增量 129)
- 装备校验:can_equip 按 CanEquipNow→FindEquipCell→EquipItem 精确重排,
  新增 find_equip_cell() 1:1 复刻(RING/UNIQUE 占用翻转、COSTUME/BELT/DS),
  帝国 antiflag 降级为 tooltip 提示,LIMIT_PCBANG/REAL_TIME* 语义与倒计时文案
- 快捷栏:死亡全槽禁用+激活守卫、变身(affect 220)技能置灰、
  物品槽失配置灰(服务器回收/换武器后刷新)
- 公会:GUILD_AUTH_* 权限位门控踢人/公告/技能升级,/gskillup、
  被宣战应答弹窗(/war·/nowar)、资金存取行(40250 服务端已禁用仍保留 1:1)
- 好友/情侣:messenger 三固定组(好友/公会/情侣),lover_login/logout/
  near/far/divorce 命令事件→在线/在身边状态与离婚隐藏
- 交易/仓库:ExchangeState.last_notice(ALREADY/LESS_GOLD)结束浮层提示;
  仓库改密 /safebox_change_password 三段输入;关窗补 /safebox_close·/mall_close;
  仓库金钱经核实 40250 无 wire 协议,UI 定界只读
- app_flow:重连时 guild_marks_ready 重复连接加 is_connected 守卫
- 相机 look_at 零向量守卫(首帧/传送落点)
- 测试:equip/love/guild 断言更新,79 项中 78 绿(game_camera_test 为
  HEAD 既有失败);真服冒烟 LIVE_SMOKE PASS(192.168.21.203 全链路)
- docs/CLIENT-GAP.md 同步增量 129

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018kUFvQGYusuY3dkfGitmAe
2026-09-07 21:43:30 +09:00
shenandshen 61272b75e9 客户端问题修改 2026-09-07 14:53:36 +08:00
361 changed files with 22566 additions and 882 deletions
+13
View File
@@ -5,6 +5,8 @@
# build
/build/
# 渲染测试的落图输出(test/rendering 用例自建)
/project/build/
/build-ios/
/build-android/
/build-classic/
@@ -33,6 +35,17 @@ compile_commands.json
*.tmp
*.o
*.a
.env.live-smoke.local
# playable network tests: local scenario/fixture configs, credential files and
# run outputs never enter git. Only scenario.example.json and the static gate
# fixtures in test/playable are committed. Run outputs live under /build/playable.
/test/playable/*.local.json
/test/playable/*.local.txt
/test/playable/runs/
*.credentials
*.credentials.json
.env.*.local
# clangd
/.cache/
+4
View File
@@ -24,6 +24,10 @@
[`docs/SHINSOO-WORLD-RENDERING.md`](docs/SHINSOO-WORLD-RENDERING.md)(正式移植,Phase 2 bring-up 之后);
距参考图「目视等价」还差哪些保真化工作见 [`docs/PARITY-GAP.md`](docs/PARITY-GAP.md)。
当前进入游戏、模型/发型、贴地与技能栏问题的分阶段修复步骤,以及结合两个 Godot 项目的
对照验证和 macOS 包验收要求,见 [`docs/RENDERING-REPAIR-PLAN.md`](docs/RENDERING-REPAIR-PLAN.md)
(2026-09-08,实施计划;不表示其中工作已完成)。
---
## 现状
+27
View File
@@ -0,0 +1,27 @@
# Native tree geometry
These 87 GLBs are offline SpeedTreeRT extractions of this workspace's existing
SPT assets, not procedural replacements. Their embedded bark/composite textures
and meshes are game artwork: the same internal-use/licensing restrictions as
`assets/Tree` apply. Do not treat them as freely redistributable open-source art.
`manifest.json` maps original SPT SHA-256 to the GLB filename and SHA-256.
Both hashes are checked by `script/stage_tree_geometry.mjs`; the runtime also
verifies both before loading. The source conversion index lives in the sibling
`metin2-godot-main/generated/content-v1/40250-full-unpacked-final` workspace.
No proprietary Windows DLL is shipped or loaded by the Mac client.
Coordinates are already Godot Y-up metres. Keep original height, origin, UVs,
branches, fronds and leaf cards; do not apply the former proxy's 12m scale.
Leaf materials use double-sided alpha scissor with cutoff 0.3.
This is real **static** geometry: full SpeedTree wind, camera-facing leaf
clusters and native LOD transitions are not reconstructed by these GLBs.
Validation: all 87 species import; all 81 species referenced by current static
AreaData have a conversion; six principal maps render 2,092 native trees with
zero proxies. The 31 legacy SPTs under `Zone/ymir work/zone/b/tree` have different
source hashes and are not converted or aliased to these meshes. They are not
referenced by current tree property records. One unused historical property
references missing `christmastree.spt`; the actual `christmastree1/2/3.spt`
variants are converted. Seasonal event spawning is separate from static maps.
+963
View File
@@ -0,0 +1,963 @@
{
"schema_version": 1,
"coordinates": "godot_y_up_meters",
"trees": {
"1d20b68d50ce007b4814da28936b1992ccb399f1c264df3a5dcaa8b92de5be5e": {
"source": "Tree/ymir work/tree/b1_baobab_rt.spt",
"source_sha256": "1d20b68d50ce007b4814da28936b1992ccb399f1c264df3a5dcaa8b92de5be5e",
"glb": "1d20b68d50ce007b4814da28936b1992ccb399f1c264df3a5dcaa8b92de5be5e.glb",
"glb_sha256": "642c31b558023112fc6c5fba9955c235d39d97a7c05eb56613bd76a4cf33e83e",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"9e5b696ab020cd18232e18c866454c5d52deb1076998db0a1fbf8703d1599741": {
"source": "Tree/ymir work/tree/b1_baobab_rt2.spt",
"source_sha256": "9e5b696ab020cd18232e18c866454c5d52deb1076998db0a1fbf8703d1599741",
"glb": "9e5b696ab020cd18232e18c866454c5d52deb1076998db0a1fbf8703d1599741.glb",
"glb_sha256": "4a4d6013668a084dc3b00d44e9cc7474457ddd1f06e64229a348ff8f0a445326",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"de17c0c13f956cf75d92997063a12c5dbca1a26234ad14a5f868a03362a11b48": {
"source": "Tree/ymir work/tree/b1_beech_rt.spt",
"source_sha256": "de17c0c13f956cf75d92997063a12c5dbca1a26234ad14a5f868a03362a11b48",
"glb": "de17c0c13f956cf75d92997063a12c5dbca1a26234ad14a5f868a03362a11b48.glb",
"glb_sha256": "18f49b44e9b435f91da8b75958aab7aef4b0d368301da7b3789f51e0570481ac",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"cdb81537efcda4c26781463a6018d59c42edcb12ca3d5b724c029810bf434de9": {
"source": "Tree/ymir work/tree/b1_beech_rt2.spt",
"source_sha256": "cdb81537efcda4c26781463a6018d59c42edcb12ca3d5b724c029810bf434de9",
"glb": "cdb81537efcda4c26781463a6018d59c42edcb12ca3d5b724c029810bf434de9.glb",
"glb_sha256": "9da1a6ab558c70dda232c36a2bd85bb710d24031149dac0198af3532b0bd4145",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"9d7ee35f949b4764090b1a916ade1005b622b31fea49b2d4b8586076711c8fc7": {
"source": "Tree/ymir work/tree/b1_beech_rt3.spt",
"source_sha256": "9d7ee35f949b4764090b1a916ade1005b622b31fea49b2d4b8586076711c8fc7",
"glb": "9d7ee35f949b4764090b1a916ade1005b622b31fea49b2d4b8586076711c8fc7.glb",
"glb_sha256": "edf0fbac525cfc48f87527aa25d3f49df6f010ee51f56ae78e0ac69d00aa4ba5",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"25d677d269f8cfc38ca1cd3f692810cb293ab13db46b576b278cc695f7cf3f16": {
"source": "Tree/ymir work/tree/b1_beech_rt4.spt",
"source_sha256": "25d677d269f8cfc38ca1cd3f692810cb293ab13db46b576b278cc695f7cf3f16",
"glb": "25d677d269f8cfc38ca1cd3f692810cb293ab13db46b576b278cc695f7cf3f16.glb",
"glb_sha256": "edc5c99aab45da766baa28ee402086b2b107326ec52d81b3324c06ad38def6ee",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"66392538f04a55d484f9c92cdc9d19d21341f499f30162d288e75812669f1f04": {
"source": "Tree/ymir work/tree/b1_montereycypress_rt.spt",
"source_sha256": "66392538f04a55d484f9c92cdc9d19d21341f499f30162d288e75812669f1f04",
"glb": "66392538f04a55d484f9c92cdc9d19d21341f499f30162d288e75812669f1f04.glb",
"glb_sha256": "9abdfc7f413b5aa4117f36106720dc979ebfa4c647c28a833904aa4d9e713466",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"0b90a99d6480452b12b5f78a9b61e453180ec4a83f4246bdf1b91c02b949f4e8": {
"source": "Tree/ymir work/tree/b1_montereycypress_rt2.spt",
"source_sha256": "0b90a99d6480452b12b5f78a9b61e453180ec4a83f4246bdf1b91c02b949f4e8",
"glb": "0b90a99d6480452b12b5f78a9b61e453180ec4a83f4246bdf1b91c02b949f4e8.glb",
"glb_sha256": "acd4675f30e805d5543c17f0a7a52fe37f31f6e67874b196b7395997e1ea8e83",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"f6691c8b816eea9bc8c49189d7f780f531ed9ec82d432fbba3a847d0776c27db": {
"source": "Tree/ymir work/tree/b1_montereycypress_rt3.spt",
"source_sha256": "f6691c8b816eea9bc8c49189d7f780f531ed9ec82d432fbba3a847d0776c27db",
"glb": "f6691c8b816eea9bc8c49189d7f780f531ed9ec82d432fbba3a847d0776c27db.glb",
"glb_sha256": "56effe5240c0cf44f74c033412fa2bf25a8329be74c66a62da3d2288ae24ab10",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"d7ca320772595a77c28856c45a36674e18918b9e7bc5101cd5cb8cdf12de27e1": {
"source": "Tree/ymir work/tree/b1_montereycypress_rt4.spt",
"source_sha256": "d7ca320772595a77c28856c45a36674e18918b9e7bc5101cd5cb8cdf12de27e1",
"glb": "d7ca320772595a77c28856c45a36674e18918b9e7bc5101cd5cb8cdf12de27e1.glb",
"glb_sha256": "d371eba315defbfb6956cc8cfbd26dde7ff9240d21f832c37a6fc63559bfcca2",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"cb9102770aca71a6e269e806c6e6e74e5d4cb8096abbf078180ab27c8e1367c3": {
"source": "Tree/ymir work/tree/b1_montereycypress_rt5.spt",
"source_sha256": "cb9102770aca71a6e269e806c6e6e74e5d4cb8096abbf078180ab27c8e1367c3",
"glb": "cb9102770aca71a6e269e806c6e6e74e5d4cb8096abbf078180ab27c8e1367c3.glb",
"glb_sha256": "28c7a3e30c21d3874247e10a0bf6107de1775d29b0effa664edeb743c036f953",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"2851666e8f3556aa6606642bcd529672ab45b0f8db1304d6fbb4466900a2ca5a": {
"source": "Tree/ymir work/tree/b1_pagodatree_rt.spt",
"source_sha256": "2851666e8f3556aa6606642bcd529672ab45b0f8db1304d6fbb4466900a2ca5a",
"glb": "2851666e8f3556aa6606642bcd529672ab45b0f8db1304d6fbb4466900a2ca5a.glb",
"glb_sha256": "0af3909bf5fe30ae77f918e7641e12791b8c2d3f40ec576aa375b3005bb8f528",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"d35194db82fe4ca4a7668f95d5d95f116ab6b08c326b7baf0c0ab074276db953": {
"source": "Tree/ymir work/tree/b1_pagodatree_rt2.spt",
"source_sha256": "d35194db82fe4ca4a7668f95d5d95f116ab6b08c326b7baf0c0ab074276db953",
"glb": "d35194db82fe4ca4a7668f95d5d95f116ab6b08c326b7baf0c0ab074276db953.glb",
"glb_sha256": "a6546ab05a6722bf2eaefc0b23ae399d97050a280631488bb82d651ed131ea52",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"a566eff0304a67971167e4e7d3bf254ab0c7a33478fcd657cec613c16058e900": {
"source": "Tree/ymir work/tree/b1_pagodatree_rt3.spt",
"source_sha256": "a566eff0304a67971167e4e7d3bf254ab0c7a33478fcd657cec613c16058e900",
"glb": "a566eff0304a67971167e4e7d3bf254ab0c7a33478fcd657cec613c16058e900.glb",
"glb_sha256": "fd60281f1384d80b51f838ee512e5d7506fef17d8ad9c28c962a4970e5e02be6",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"1bc5e65c0e4efcb60f7ea930f683b80001b8dd97762b6d25281946a295685149": {
"source": "Tree/ymir work/tree/b1_sassafras_rt_fall.spt",
"source_sha256": "1bc5e65c0e4efcb60f7ea930f683b80001b8dd97762b6d25281946a295685149",
"glb": "1bc5e65c0e4efcb60f7ea930f683b80001b8dd97762b6d25281946a295685149.glb",
"glb_sha256": "69ba5cd343cb193e12d5fa9203356e08e5536e6a0083650ca4be88f87472fcf8",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"5c6dabb419b86756136240a32b532746d6f86759c8e744f1701c24bcb840262a": {
"source": "Tree/ymir work/tree/b1_sassafras_rt_fall2.spt",
"source_sha256": "5c6dabb419b86756136240a32b532746d6f86759c8e744f1701c24bcb840262a",
"glb": "5c6dabb419b86756136240a32b532746d6f86759c8e744f1701c24bcb840262a.glb",
"glb_sha256": "14cb7ad036580d3387112e2098d52ee6815d158c533ed52f3db0604a00c33b09",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"d76530cfba5e75a94a9634fd76f86a02126c22bd51cccfc3d995eff8974742f2": {
"source": "Tree/ymir work/tree/b2_cedaroflebanon_rt.spt",
"source_sha256": "d76530cfba5e75a94a9634fd76f86a02126c22bd51cccfc3d995eff8974742f2",
"glb": "d76530cfba5e75a94a9634fd76f86a02126c22bd51cccfc3d995eff8974742f2.glb",
"glb_sha256": "d8df7faf73265c6a5b2a725cfacab421b609cd9a4bd2bad41997e349e9ee4fa7",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"73d757a146ca27a9459ccc3027cd9464ce415f1a6731771cbff37827a3ee1caa": {
"source": "Tree/ymir work/tree/b2_cedaroflebanon_rt2.spt",
"source_sha256": "73d757a146ca27a9459ccc3027cd9464ce415f1a6731771cbff37827a3ee1caa",
"glb": "73d757a146ca27a9459ccc3027cd9464ce415f1a6731771cbff37827a3ee1caa.glb",
"glb_sha256": "c6c15c69d0b60f232293e74f8da191b3ae62f4a69fba61d0e7fe94008c51176e",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"fd2e5bb94c5223e1eaca97544e757e135252edb05f0ddfa991e3abad3fb78b3f": {
"source": "Tree/ymir work/tree/b2_cedaroflebanon_rt3.spt",
"source_sha256": "fd2e5bb94c5223e1eaca97544e757e135252edb05f0ddfa991e3abad3fb78b3f",
"glb": "fd2e5bb94c5223e1eaca97544e757e135252edb05f0ddfa991e3abad3fb78b3f.glb",
"glb_sha256": "81e2b1c1cd753ba17e8d875bc5ad0104c0f2a8cc1831d4decbc64d7a82fb1202",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"50557c6da89615d82f9d81799315549bed25e9ae09dd128a6f161f83a17f04f5": {
"source": "Tree/ymir work/tree/b2_ivyspy_rt.spt",
"source_sha256": "50557c6da89615d82f9d81799315549bed25e9ae09dd128a6f161f83a17f04f5",
"glb": "50557c6da89615d82f9d81799315549bed25e9ae09dd128a6f161f83a17f04f5.glb",
"glb_sha256": "d632d027f5e228e6d85a986068ada05996461692623dc082569b9078965af7dd",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"d012fcd397e8dac1d1778b3ad700ee36f08d282bd53ca6466a7232824e453f5a": {
"source": "Tree/ymir work/tree/b2_ivyspy_rt2.spt",
"source_sha256": "d012fcd397e8dac1d1778b3ad700ee36f08d282bd53ca6466a7232824e453f5a",
"glb": "d012fcd397e8dac1d1778b3ad700ee36f08d282bd53ca6466a7232824e453f5a.glb",
"glb_sha256": "ce88fcfb1f04048912665592e0ad4da69679163c23b8b2f541641bad7e319f2d",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"065babe8c785f54d23b99e91e9d505b91eadaae1dff492bcfe236b657acf9272": {
"source": "Tree/ymir work/tree/b2_ivyspy_rt3.spt",
"source_sha256": "065babe8c785f54d23b99e91e9d505b91eadaae1dff492bcfe236b657acf9272",
"glb": "065babe8c785f54d23b99e91e9d505b91eadaae1dff492bcfe236b657acf9272.glb",
"glb_sha256": "aea108dfb8fcaf7021b54df8c27a194ac5486fac08fb32dbc15c00b7834d2596",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"61f67291ff5d7c49014991dddb3d03550ae18e157f1acf62daf249ca40206bf3": {
"source": "Tree/ymir work/tree/b2_ivyspy_rt4.spt",
"source_sha256": "61f67291ff5d7c49014991dddb3d03550ae18e157f1acf62daf249ca40206bf3",
"glb": "61f67291ff5d7c49014991dddb3d03550ae18e157f1acf62daf249ca40206bf3.glb",
"glb_sha256": "fcd95e7569a5a6d8af5e987f3764d520d941fa518827ae247514b003c2de5a87",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"f40054b4e07fdc6071f8b5bd6ad85782d5207c51fa116bfb8e0aca4d8675badd": {
"source": "Tree/ymir work/tree/b2_ivyspy_rt5.spt",
"source_sha256": "f40054b4e07fdc6071f8b5bd6ad85782d5207c51fa116bfb8e0aca4d8675badd",
"glb": "f40054b4e07fdc6071f8b5bd6ad85782d5207c51fa116bfb8e0aca4d8675badd.glb",
"glb_sha256": "0824f3fc789c420d6eb543cf932f617eabb8f01437e5bf6ef9aacf116e0eca83",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"80422882e2d1ec63f6c5080c9c308634b8e8acc1342ebe1ebc74039fe2384631": {
"source": "Tree/ymir work/tree/b2_ivyspy_rt_winter.spt",
"source_sha256": "80422882e2d1ec63f6c5080c9c308634b8e8acc1342ebe1ebc74039fe2384631",
"glb": "80422882e2d1ec63f6c5080c9c308634b8e8acc1342ebe1ebc74039fe2384631.glb",
"glb_sha256": "f2a4fa095e0d7ca96a49afc371b9f9189a5b370ca4543827fc3098971b779a31",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"8c9d519de4a1652c2b923e11927be3818942235052cc67e6825d9705a09feaeb": {
"source": "Tree/ymir work/tree/b2_ivyspy_rt_winter2.spt",
"source_sha256": "8c9d519de4a1652c2b923e11927be3818942235052cc67e6825d9705a09feaeb",
"glb": "8c9d519de4a1652c2b923e11927be3818942235052cc67e6825d9705a09feaeb.glb",
"glb_sha256": "97a8e81c46cf1f5dbe8084535e17286c7df07e7b71d61ae7c591adff1dd62fda",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"fa2b9ac46f0f3be36f386fb8630f034393bde37d9cc5b2ec8b38470bdbb14c59": {
"source": "Tree/ymir work/tree/b2_japanesemaple_rt_fall.spt",
"source_sha256": "fa2b9ac46f0f3be36f386fb8630f034393bde37d9cc5b2ec8b38470bdbb14c59",
"glb": "fa2b9ac46f0f3be36f386fb8630f034393bde37d9cc5b2ec8b38470bdbb14c59.glb",
"glb_sha256": "34831b921865e35889704938333c80d69daaba09762e4fe8351c673811053dea",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"86b5d9065583eabca29ad89b7ccb67ac640bada57af8504c6448027fa05bd655": {
"source": "Tree/ymir work/tree/b2_japanesemaple_rt_fall2.spt",
"source_sha256": "86b5d9065583eabca29ad89b7ccb67ac640bada57af8504c6448027fa05bd655",
"glb": "86b5d9065583eabca29ad89b7ccb67ac640bada57af8504c6448027fa05bd655.glb",
"glb_sha256": "f38ffdbebb6f60e4e92d3b8993cc3001c502a8223c72bfb3688812d9217a67b2",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"13292b7af3472178d07e40005acd73b09d61f1d9f9570606cc74f115a42a1637": {
"source": "Tree/ymir work/tree/b2_redoak_rt.spt",
"source_sha256": "13292b7af3472178d07e40005acd73b09d61f1d9f9570606cc74f115a42a1637",
"glb": "13292b7af3472178d07e40005acd73b09d61f1d9f9570606cc74f115a42a1637.glb",
"glb_sha256": "6fc61c59dc838a768f10b749a9b7ce1ea659e4a9d6f9fc6ee6c887c0532adc6c",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"55ee1c716113b30ebc1e4166698765f041f1c094bf28d4ec86f8ca61202e5116": {
"source": "Tree/ymir work/tree/b2_redoak_rt2.spt",
"source_sha256": "55ee1c716113b30ebc1e4166698765f041f1c094bf28d4ec86f8ca61202e5116",
"glb": "55ee1c716113b30ebc1e4166698765f041f1c094bf28d4ec86f8ca61202e5116.glb",
"glb_sha256": "3200e730f4551792a504f20e4046e7bcf466e1e51d90229c9328b3ea63bc47cb",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"c50b043a6ceed03fbdac17cd0a861b1de1d06f4c5933a9078d073f42867e1a22": {
"source": "Tree/ymir work/tree/b2_redoak_rt3.spt",
"source_sha256": "c50b043a6ceed03fbdac17cd0a861b1de1d06f4c5933a9078d073f42867e1a22",
"glb": "c50b043a6ceed03fbdac17cd0a861b1de1d06f4c5933a9078d073f42867e1a22.glb",
"glb_sha256": "6781839f9fcdcdbae7b156b5708436e2580781a11bb784ff11f5f088a841471a",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"0d62629ce2423bdfce51595038cab213dec628acb4cf063b0b271279a28e8832": {
"source": "Tree/ymir work/tree/b2_redoak_rt4.spt",
"source_sha256": "0d62629ce2423bdfce51595038cab213dec628acb4cf063b0b271279a28e8832",
"glb": "0d62629ce2423bdfce51595038cab213dec628acb4cf063b0b271279a28e8832.glb",
"glb_sha256": "febe8dcbe1880cec8f91dc5844a55749c8d183e86c1b581d07440f6bf38ab794",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"eace2fb1af759da813559a33d38690596a066397983994cfea75232441ca3086": {
"source": "Tree/ymir work/tree/b3_beech_rt.spt",
"source_sha256": "eace2fb1af759da813559a33d38690596a066397983994cfea75232441ca3086",
"glb": "eace2fb1af759da813559a33d38690596a066397983994cfea75232441ca3086.glb",
"glb_sha256": "1d29e58f3e04b4dbfe4cbfb91403bddcfed2af0f282636f506d358b5f15cccb1",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"65846f0ea0ac8ace155a11cc6a41631d3330e6db25ba9a9c8b8171b96fdfde67": {
"source": "Tree/ymir work/tree/b3_beech_rt2.spt",
"source_sha256": "65846f0ea0ac8ace155a11cc6a41631d3330e6db25ba9a9c8b8171b96fdfde67",
"glb": "65846f0ea0ac8ace155a11cc6a41631d3330e6db25ba9a9c8b8171b96fdfde67.glb",
"glb_sha256": "2bdbe584e694ec4516803ac0966f741cf39920f20c4692b6f5f4496e5fff0b06",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"09c12d2bf8ef2c5ada7d54323b7b11816bebc56cc3fddd7373aba4eed7d3527e": {
"source": "Tree/ymir work/tree/b3_beech_rt3.spt",
"source_sha256": "09c12d2bf8ef2c5ada7d54323b7b11816bebc56cc3fddd7373aba4eed7d3527e",
"glb": "09c12d2bf8ef2c5ada7d54323b7b11816bebc56cc3fddd7373aba4eed7d3527e.glb",
"glb_sha256": "cae93d33e0849b769cb9070bb094239a2d68c33e22942df24836190757c3cd98",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"e622c7689d6525c09b22244a9a112ce9448c1c189cbe7f0468da9de82b8d9c36": {
"source": "Tree/ymir work/tree/b3_beech_rt_fall.spt",
"source_sha256": "e622c7689d6525c09b22244a9a112ce9448c1c189cbe7f0468da9de82b8d9c36",
"glb": "e622c7689d6525c09b22244a9a112ce9448c1c189cbe7f0468da9de82b8d9c36.glb",
"glb_sha256": "59e6e7c3378af28c44a3293b1bd026023cbcc9c5447be1684e63a88d9b3691f8",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"8aaf7a77ff61d8dd3828057df396ae27a6471faa7db94a6f2c93c0522ce22aa0": {
"source": "Tree/ymir work/tree/b3_beech_rt_fall2.spt",
"source_sha256": "8aaf7a77ff61d8dd3828057df396ae27a6471faa7db94a6f2c93c0522ce22aa0",
"glb": "8aaf7a77ff61d8dd3828057df396ae27a6471faa7db94a6f2c93c0522ce22aa0.glb",
"glb_sha256": "3e684dc00afd7451b28ed3f89a00baa7ea6c9d45c442caad332b9596ed0b872c",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"6600b5cae4c4f473b5cea7e9d90614ffeb3559807d4605d6d729ab7b3182eb77": {
"source": "Tree/ymir work/tree/b3_pagodatree_rt_winter.spt",
"source_sha256": "6600b5cae4c4f473b5cea7e9d90614ffeb3559807d4605d6d729ab7b3182eb77",
"glb": "6600b5cae4c4f473b5cea7e9d90614ffeb3559807d4605d6d729ab7b3182eb77.glb",
"glb_sha256": "bec163b2c1fa99b26d97bad86155a401ab19c067b2acb160a747fceb5b168226",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"ea34cd93e320c38e79837ab348022ea6cab4a62b295fd20a54c7a4a9688129f5": {
"source": "Tree/ymir work/tree/b3_pagodatree_rt_winter2.spt",
"source_sha256": "ea34cd93e320c38e79837ab348022ea6cab4a62b295fd20a54c7a4a9688129f5",
"glb": "ea34cd93e320c38e79837ab348022ea6cab4a62b295fd20a54c7a4a9688129f5.glb",
"glb_sha256": "63aa6ef7fe8181bc1877e86a6b3f1f3bd617a2af303208006289c8e68a76b859",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"e03e7c40dd56befae1804550d90f114a1496f7a5c0fa18ae111de5a19372639b": {
"source": "Tree/ymir work/tree/b3_shingleoak_rt.spt",
"source_sha256": "e03e7c40dd56befae1804550d90f114a1496f7a5c0fa18ae111de5a19372639b",
"glb": "e03e7c40dd56befae1804550d90f114a1496f7a5c0fa18ae111de5a19372639b.glb",
"glb_sha256": "54bb8cf813b9c6f05f14ea7dbb39689a3a26f9737e7526969c2bea070b8bf78f",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"d59d8a429a63272bcd21b403cd581556b57a140f5cdc65c7af329d30b6770d26": {
"source": "Tree/ymir work/tree/b3_shingleoak_rt2.spt",
"source_sha256": "d59d8a429a63272bcd21b403cd581556b57a140f5cdc65c7af329d30b6770d26",
"glb": "d59d8a429a63272bcd21b403cd581556b57a140f5cdc65c7af329d30b6770d26.glb",
"glb_sha256": "6b85b0a649472c7268791a0adce367099b4fbb783d07ad3a508e0aa88d77c331",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"2bec6d9317e3947386c56c4183db454fe629af9fa87de759324928e681eb5742": {
"source": "Tree/ymir work/tree/b3_shingleoak_rt3.spt",
"source_sha256": "2bec6d9317e3947386c56c4183db454fe629af9fa87de759324928e681eb5742",
"glb": "2bec6d9317e3947386c56c4183db454fe629af9fa87de759324928e681eb5742.glb",
"glb_sha256": "3a3fc8e8afa93b2a65806fd5d6eb625a05426c6f8a99c50a77ac1c197bf9a6f5",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"0a15ca2a0809cc74e6d72ed48b137906f02f0e44994228fa9f840d76a6ca52cc": {
"source": "Tree/ymir work/tree/b3_shingleoak_rt4.spt",
"source_sha256": "0a15ca2a0809cc74e6d72ed48b137906f02f0e44994228fa9f840d76a6ca52cc",
"glb": "0a15ca2a0809cc74e6d72ed48b137906f02f0e44994228fa9f840d76a6ca52cc.glb",
"glb_sha256": "39dc26f0713574b98e8bba0a26ff448bd6fabd97c5b66880f155516f536d35a4",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"dbc300acb7356f4f51e897a2e248a2883241ea570d5d8cded218b31401206534": {
"source": "Tree/ymir work/tree/b3_shingleoak_rt5.spt",
"source_sha256": "dbc300acb7356f4f51e897a2e248a2883241ea570d5d8cded218b31401206534",
"glb": "dbc300acb7356f4f51e897a2e248a2883241ea570d5d8cded218b31401206534.glb",
"glb_sha256": "08dc609a9aa5b16ce943dfd6bc63d2db45daf3bc7252fd6d1ee58a7ce6449685",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"0e8ab0197ee0db8e3a40e4b8d6c5c8f0b291284d2ccb7e13cee41d0f0792cc6f": {
"source": "Tree/ymir work/tree/b3_umbrellathorn_rt_flowers.spt",
"source_sha256": "0e8ab0197ee0db8e3a40e4b8d6c5c8f0b291284d2ccb7e13cee41d0f0792cc6f",
"glb": "0e8ab0197ee0db8e3a40e4b8d6c5c8f0b291284d2ccb7e13cee41d0f0792cc6f.glb",
"glb_sha256": "580a3ff0582a7723250650e8b0828912000faf494afb25ace42578e0d1397bb8",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"3c5c75535819e405b61cd21c89d514a8d2b0e6afdd75428cc0687745a3314cfe": {
"source": "Tree/ymir work/tree/b3_umbrellathorn_rt_flowers2.spt",
"source_sha256": "3c5c75535819e405b61cd21c89d514a8d2b0e6afdd75428cc0687745a3314cfe",
"glb": "3c5c75535819e405b61cd21c89d514a8d2b0e6afdd75428cc0687745a3314cfe.glb",
"glb_sha256": "2483acf297ce1f35e3ef297ff14e84c33b16bfae885fb04f373a601ad41cfab6",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"a6592779c9460b004be7956123a13f439bef0669dbfdad4bd330b78c44b100d2": {
"source": "Tree/ymir work/tree/b3_umbrellathorn_rt_flowers3.spt",
"source_sha256": "a6592779c9460b004be7956123a13f439bef0669dbfdad4bd330b78c44b100d2",
"glb": "a6592779c9460b004be7956123a13f439bef0669dbfdad4bd330b78c44b100d2.glb",
"glb_sha256": "0d5fe2e83f4621f6cefbbf8e645306af026c540364d05d91e2757d8d4775ceff",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"44ec99e53b3158a6619ec7a5cb4110bfb67ff9943224b068a8bc99fa1d92c0d0": {
"source": "Tree/ymir work/tree/b3_umbrellathorn_rt_flowers4.spt",
"source_sha256": "44ec99e53b3158a6619ec7a5cb4110bfb67ff9943224b068a8bc99fa1d92c0d0",
"glb": "44ec99e53b3158a6619ec7a5cb4110bfb67ff9943224b068a8bc99fa1d92c0d0.glb",
"glb_sha256": "1efd17b58f6dd39b3df3e7de84d20a1d4740e8d53c05a08080a3e335619e6ac8",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"d85676762825b8324ebeba85bc9a0e2242da8d225c5699c042f469abd17d01d7": {
"source": "Tree/ymir work/tree/christmastree1.spt",
"source_sha256": "d85676762825b8324ebeba85bc9a0e2242da8d225c5699c042f469abd17d01d7",
"glb": "d85676762825b8324ebeba85bc9a0e2242da8d225c5699c042f469abd17d01d7.glb",
"glb_sha256": "6836fef2133fbc14a4ac17f3762cfe64b596e2cc1dbae818c80fe4eb421a510f",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"6277b6d6dd747b0a6aa067fc2a27e8e7dad3bd8c9c755d6b124bc474b4107a1d": {
"source": "Tree/ymir work/tree/christmastree2.spt",
"source_sha256": "6277b6d6dd747b0a6aa067fc2a27e8e7dad3bd8c9c755d6b124bc474b4107a1d",
"glb": "6277b6d6dd747b0a6aa067fc2a27e8e7dad3bd8c9c755d6b124bc474b4107a1d.glb",
"glb_sha256": "451bd2edca71c9aa5c658ce8bc1353b7315530220f1d1458c037d5ea007b2794",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"074755f44edc3d836c20b8e254c61af805c3e7f3cbe1e65f76bb35bd3d66ca8f": {
"source": "Tree/ymir work/tree/christmastree3.spt",
"source_sha256": "074755f44edc3d836c20b8e254c61af805c3e7f3cbe1e65f76bb35bd3d66ca8f",
"glb": "074755f44edc3d836c20b8e254c61af805c3e7f3cbe1e65f76bb35bd3d66ca8f.glb",
"glb_sha256": "6445e98dd9760e3371dbcd13673b539b2c6be6450dc883605c50b3d5fac3f825",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"0ce9fe068b0a227cb4ce1c36dbeff5792ae6488f012c028b54ab89d7ef2b2f3b": {
"source": "Tree/ymir work/tree/n1_baobab_rt_winter.spt",
"source_sha256": "0ce9fe068b0a227cb4ce1c36dbeff5792ae6488f012c028b54ab89d7ef2b2f3b",
"glb": "0ce9fe068b0a227cb4ce1c36dbeff5792ae6488f012c028b54ab89d7ef2b2f3b.glb",
"glb_sha256": "d49a2346357486cab5368ecb1a32a8e728bd45318b301b86755c41d481f3712f",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"0d766295707ebd0e6af56bfc49ed1bca8bc11f7b15f79be44e221bbe9e18d53e": {
"source": "Tree/ymir work/tree/n1_beech_rt_winter.spt",
"source_sha256": "0d766295707ebd0e6af56bfc49ed1bca8bc11f7b15f79be44e221bbe9e18d53e",
"glb": "0d766295707ebd0e6af56bfc49ed1bca8bc11f7b15f79be44e221bbe9e18d53e.glb",
"glb_sha256": "031cde20e4c46d5f75cda94fd783834614d31584f7af36e9be2218dabc6a0cbb",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"e5497e163459d6ad06745e4b8a5ab95422fb59143e7aa76cf40ac65d242872b3": {
"source": "Tree/ymir work/tree/n1_beech_rt_winter02.spt",
"source_sha256": "e5497e163459d6ad06745e4b8a5ab95422fb59143e7aa76cf40ac65d242872b3",
"glb": "e5497e163459d6ad06745e4b8a5ab95422fb59143e7aa76cf40ac65d242872b3.glb",
"glb_sha256": "f542b8c33350569b80f2a3c88fc1db22be93ed51fbcfe46654d2b29fccaa413a",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"41997e16e6110e21096ffa2d85f0d2135e7b8a4ec82a869f899a6325776a2225": {
"source": "Tree/ymir work/tree/n1_beech_rt_winter03.spt",
"source_sha256": "41997e16e6110e21096ffa2d85f0d2135e7b8a4ec82a869f899a6325776a2225",
"glb": "41997e16e6110e21096ffa2d85f0d2135e7b8a4ec82a869f899a6325776a2225.glb",
"glb_sha256": "fd2fd865a012ba6f53b0bdd0eea8971c7858826cc4cbb08d3a6db1becf027fc0",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"b60fa3d6329d76d4b85da226e88c36241bf8fb4e3a3fc5e1316f0a64d7e2c6ad": {
"source": "Tree/ymir work/tree/n1_coloradobluespruce_rt_01.spt",
"source_sha256": "b60fa3d6329d76d4b85da226e88c36241bf8fb4e3a3fc5e1316f0a64d7e2c6ad",
"glb": "b60fa3d6329d76d4b85da226e88c36241bf8fb4e3a3fc5e1316f0a64d7e2c6ad.glb",
"glb_sha256": "82d906bb624b0041c3d687e3af51cff8ed524ba75e6432a5f293554d5d4ff137",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"265aef90b037111a8d08bd4f258501872be9fc0b4bacf6ad17820241c5f959a1": {
"source": "Tree/ymir work/tree/n1_coloradobluespruce_rt_02.spt",
"source_sha256": "265aef90b037111a8d08bd4f258501872be9fc0b4bacf6ad17820241c5f959a1",
"glb": "265aef90b037111a8d08bd4f258501872be9fc0b4bacf6ad17820241c5f959a1.glb",
"glb_sha256": "6265bd35d1da9f9daa34a15b34dd9cadc5af9d6346c0b9104b2c3d582c510225",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"2e3fe98b0ca1aae0ad512741f9465e42653b4c4df7345bc915e6b6b3bf80d27d": {
"source": "Tree/ymir work/tree/n1_coloradobluespruce_rt_03.spt",
"source_sha256": "2e3fe98b0ca1aae0ad512741f9465e42653b4c4df7345bc915e6b6b3bf80d27d",
"glb": "2e3fe98b0ca1aae0ad512741f9465e42653b4c4df7345bc915e6b6b3bf80d27d.glb",
"glb_sha256": "cbeb5930c57e2dbefbe9c50987275e7abc2b1ac9aa9cf190f60de71dae75f52b",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"4c3b7f1bc32ca54a581a22eb99a5229c066a2e3db84d73c4a260b41ecb4151d6": {
"source": "Tree/ymir work/tree/n1_commonolive_rt_winter.spt",
"source_sha256": "4c3b7f1bc32ca54a581a22eb99a5229c066a2e3db84d73c4a260b41ecb4151d6",
"glb": "4c3b7f1bc32ca54a581a22eb99a5229c066a2e3db84d73c4a260b41ecb4151d6.glb",
"glb_sha256": "a2fb958788a84301f9008e4efe1ea9994314d28b4b3954649c64b3033bf912c0",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"e63e6fd8aec38f5a4dea10edce507870f834cf1da5fbd8b5e789dc75f773957c": {
"source": "Tree/ymir work/tree/n1_montereycypress_rt_winter_01.spt",
"source_sha256": "e63e6fd8aec38f5a4dea10edce507870f834cf1da5fbd8b5e789dc75f773957c",
"glb": "e63e6fd8aec38f5a4dea10edce507870f834cf1da5fbd8b5e789dc75f773957c.glb",
"glb_sha256": "9dc165cb8f6417f9e27486db714809722b5e85ff4ed827b21a0fe1d52feee89f",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"bd813274fcc6fc3c8bf26a9bc85eb6b3c18d0a4a68dd0a51e13183f4634680da": {
"source": "Tree/ymir work/tree/n1_montereycypress_rt_winter_02.spt",
"source_sha256": "bd813274fcc6fc3c8bf26a9bc85eb6b3c18d0a4a68dd0a51e13183f4634680da",
"glb": "bd813274fcc6fc3c8bf26a9bc85eb6b3c18d0a4a68dd0a51e13183f4634680da.glb",
"glb_sha256": "f64d701e3ec752f566eef179ed62d613fd20d7dcc0bd86dcb95c1e3a0ef56c86",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"1af978f7efd1be5ba7af90056c4b933f74154c60a47a20fe88f4872aaf7bc862": {
"source": "Tree/ymir work/tree/n1_montereycypress_rt_winter_03.spt",
"source_sha256": "1af978f7efd1be5ba7af90056c4b933f74154c60a47a20fe88f4872aaf7bc862",
"glb": "1af978f7efd1be5ba7af90056c4b933f74154c60a47a20fe88f4872aaf7bc862.glb",
"glb_sha256": "f80e080f78042b751116ef056edf98501922de0bb46c6e0efb950e17115afbbe",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"36389501b6156b934f960d826ac2e3ecae86430cf53dc88dd6c30f5a8415aba9": {
"source": "Tree/ymir work/tree/n1_tulip_rt_winter_01.spt",
"source_sha256": "36389501b6156b934f960d826ac2e3ecae86430cf53dc88dd6c30f5a8415aba9",
"glb": "36389501b6156b934f960d826ac2e3ecae86430cf53dc88dd6c30f5a8415aba9.glb",
"glb_sha256": "ce0f23b6d5931ebeea06862a4ffef37b2274dc0b8643ec2f5768f1b0df3ae858",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 1,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"0df26ab085d443bccc61d8ec878b61117f64db3b3ffa31e70fa0fce6d5f59361": {
"source": "Tree/ymir work/tree/n1_whitepine_rt_low_01.spt",
"source_sha256": "0df26ab085d443bccc61d8ec878b61117f64db3b3ffa31e70fa0fce6d5f59361",
"glb": "0df26ab085d443bccc61d8ec878b61117f64db3b3ffa31e70fa0fce6d5f59361.glb",
"glb_sha256": "48bd12ddffdc0aee165a29bfc6fd0dcbfe1366aec5542bcee102bf8d5d82c3e2",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"ac5bb9b19999fe6d20793898edc7b709b1ba792e6cac2cc7ed5fcd64abdfb03f": {
"source": "Tree/ymir work/tree/n1_whitepine_rt_low_02.spt",
"source_sha256": "ac5bb9b19999fe6d20793898edc7b709b1ba792e6cac2cc7ed5fcd64abdfb03f",
"glb": "ac5bb9b19999fe6d20793898edc7b709b1ba792e6cac2cc7ed5fcd64abdfb03f.glb",
"glb_sha256": "fa1fe6e118c91b6d1276b17ed05297eed6349e4c29726e11a51de48252dbf787",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"34314fd26754a94e9215723fd970a09b360eef0f8fa7e93c55c44b6dadd4b4c8": {
"source": "Tree/ymir work/tree/n2_aloevera_rt_flowers_01.spt",
"source_sha256": "34314fd26754a94e9215723fd970a09b360eef0f8fa7e93c55c44b6dadd4b4c8",
"glb": "34314fd26754a94e9215723fd970a09b360eef0f8fa7e93c55c44b6dadd4b4c8.glb",
"glb_sha256": "96d7b310fa8d13e910b11e5524a1a30886e21c9e715290c64d1df2e9b4298d17",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"e0c3f6cfe9200acf3fe66fcc33fa52e2bbab55a48a35815178e74ee80e717c36": {
"source": "Tree/ymir work/tree/n2_aloevera_rt_flowers_02.spt",
"source_sha256": "e0c3f6cfe9200acf3fe66fcc33fa52e2bbab55a48a35815178e74ee80e717c36",
"glb": "e0c3f6cfe9200acf3fe66fcc33fa52e2bbab55a48a35815178e74ee80e717c36.glb",
"glb_sha256": "968300bd31c235557926343e46d7335ae75644aff6d0ca58f3a3e4a1963c5e51",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"f7c94a390c712edae5557d03849373c713d4ce28fba6080d87fc761216c4a26d": {
"source": "Tree/ymir work/tree/n2_bananatree_rt_01.spt",
"source_sha256": "f7c94a390c712edae5557d03849373c713d4ce28fba6080d87fc761216c4a26d",
"glb": "f7c94a390c712edae5557d03849373c713d4ce28fba6080d87fc761216c4a26d.glb",
"glb_sha256": "0b43c6087d26a19e6863a1d488eb6e3e524240793d850b27f844e4c194818afa",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"23b662d2b6d87d268460e4ae100b618ecfac6dd5262f59a2912e6dfe74530436": {
"source": "Tree/ymir work/tree/n2_bananatree_rt_02.spt",
"source_sha256": "23b662d2b6d87d268460e4ae100b618ecfac6dd5262f59a2912e6dfe74530436",
"glb": "23b662d2b6d87d268460e4ae100b618ecfac6dd5262f59a2912e6dfe74530436.glb",
"glb_sha256": "c5bd49676be213f69141b644b109a890f682fcc31ae8bf4fb0d078b72b28d8b8",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"93ded82028c0f2e97d0403ec045b89bbc1ac859ad8b84a2e1930e0503907b354": {
"source": "Tree/ymir work/tree/n2_cinnamonfern_rt_01.spt",
"source_sha256": "93ded82028c0f2e97d0403ec045b89bbc1ac859ad8b84a2e1930e0503907b354",
"glb": "93ded82028c0f2e97d0403ec045b89bbc1ac859ad8b84a2e1930e0503907b354.glb",
"glb_sha256": "027fe6300b18a9608ffe465d1e1c77783a8c05c17e99c2999e058095ed62de05",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 1,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"dc389cbfc25b6e3226e961e7e392ed96c7a789c0dfc1d392b0a1950e81f59f9e": {
"source": "Tree/ymir work/tree/n2_cinnamonfern_rt_02.spt",
"source_sha256": "dc389cbfc25b6e3226e961e7e392ed96c7a789c0dfc1d392b0a1950e81f59f9e",
"glb": "dc389cbfc25b6e3226e961e7e392ed96c7a789c0dfc1d392b0a1950e81f59f9e.glb",
"glb_sha256": "0c537775ba7e98e11b6d5b74a6665dfd5eb74c94f70ad39c80c4f0979b5246ac",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 1,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"82566982c7decbccbbb9e50f0b1d399a86bed94a968e0a78e2ce8b25989d2462": {
"source": "Tree/ymir work/tree/n2_cinnamonfern_rt_03.spt",
"source_sha256": "82566982c7decbccbbb9e50f0b1d399a86bed94a968e0a78e2ce8b25989d2462",
"glb": "82566982c7decbccbbb9e50f0b1d399a86bed94a968e0a78e2ce8b25989d2462.glb",
"glb_sha256": "1d38479f01b5dac599ff8dbc80dab1657671950047da6bae43e7a201562da106",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 1,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"af5abcd685bf92c9597740c9c85168f6104e62c12e30117ca5759bf555f2c6ff": {
"source": "Tree/ymir work/tree/n2_coconutpalm_rt_01.spt",
"source_sha256": "af5abcd685bf92c9597740c9c85168f6104e62c12e30117ca5759bf555f2c6ff",
"glb": "af5abcd685bf92c9597740c9c85168f6104e62c12e30117ca5759bf555f2c6ff.glb",
"glb_sha256": "3344943038999faf012f8d1520b2feaaab367c06a6b24748685a233b89f7ec70",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"e45bdc521daaeb53191a995485513b046b0602b60d55185e465ded20a8242828": {
"source": "Tree/ymir work/tree/n2_coconutpalm_rt_02.spt",
"source_sha256": "e45bdc521daaeb53191a995485513b046b0602b60d55185e465ded20a8242828",
"glb": "e45bdc521daaeb53191a995485513b046b0602b60d55185e465ded20a8242828.glb",
"glb_sha256": "3f46282c3c8d094206d516475cf1e086e87af584c4537c3d48da1e2ee544a248",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"75b89b54281bedd408be91863cfbf1131a7c9fce5c0e153e601db3c7195c8dd6": {
"source": "Tree/ymir work/tree/n2_coconutpalm_rt_03.spt",
"source_sha256": "75b89b54281bedd408be91863cfbf1131a7c9fce5c0e153e601db3c7195c8dd6",
"glb": "75b89b54281bedd408be91863cfbf1131a7c9fce5c0e153e601db3c7195c8dd6.glb",
"glb_sha256": "1e19c9fe65ef02d0277206f4737eeafec5541ba6bc2619dcb015e31bf8d76800",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"8a6d6468b7f97040b08ba70718650abbd0d0c4684fa6e092b0a603717ed0ec28": {
"source": "Tree/ymir work/tree/n2_curlypalm_rt_01.spt",
"source_sha256": "8a6d6468b7f97040b08ba70718650abbd0d0c4684fa6e092b0a603717ed0ec28",
"glb": "8a6d6468b7f97040b08ba70718650abbd0d0c4684fa6e092b0a603717ed0ec28.glb",
"glb_sha256": "a0859095e4b20c71d33e3cde76696584880df357d3642c07d79355cb0d732809",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"de5e22bb1610c61bcb00e3835698b6a9b50b67263a438b9ba7400157dce42b5e": {
"source": "Tree/ymir work/tree/n2_curlypalm_rt_02.spt",
"source_sha256": "de5e22bb1610c61bcb00e3835698b6a9b50b67263a438b9ba7400157dce42b5e",
"glb": "de5e22bb1610c61bcb00e3835698b6a9b50b67263a438b9ba7400157dce42b5e.glb",
"glb_sha256": "c8b1d7ad7faf78e14b0c50f7c33dd9712a2c79dbec54a574945e2b20f7e22d4a",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"97ce0c94956eeafad580424081ae4ab767f01e948ab9461a9b48e5cf8bd824af": {
"source": "Tree/ymir work/tree/n2_curlypalm_rt_03.spt",
"source_sha256": "97ce0c94956eeafad580424081ae4ab767f01e948ab9461a9b48e5cf8bd824af",
"glb": "97ce0c94956eeafad580424081ae4ab767f01e948ab9461a9b48e5cf8bd824af.glb",
"glb_sha256": "700aa9caba46ed1b7141d58a653d77ea9eb96911172d1a4dacb414328c8f18ee",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"6e24a4896134057a1bc495f0f8af32ca74a7b51e0d54baa612f6dcd36a99b54e": {
"source": "Tree/ymir work/tree/n2_datepalm_rt_01.spt",
"source_sha256": "6e24a4896134057a1bc495f0f8af32ca74a7b51e0d54baa612f6dcd36a99b54e",
"glb": "6e24a4896134057a1bc495f0f8af32ca74a7b51e0d54baa612f6dcd36a99b54e.glb",
"glb_sha256": "7c2a38b68b0b33d46db13830c12302a1962584e51e628386d654509998e1795f",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"55de8a592ad3d9294583bcf27c80fba3fe71afdc669c5657ee7cc77a4002d255": {
"source": "Tree/ymir work/tree/n2_datepalm_rt_02.spt",
"source_sha256": "55de8a592ad3d9294583bcf27c80fba3fe71afdc669c5657ee7cc77a4002d255",
"glb": "55de8a592ad3d9294583bcf27c80fba3fe71afdc669c5657ee7cc77a4002d255.glb",
"glb_sha256": "e7391ec2d87aa7df6f87943e4faaf91de5951dccb045e6935844c997f7de1179",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"2007d4ba90f12b8a00d2a0a539954e420c37f2151f438c5ad06eb63a78fe853d": {
"source": "Tree/ymir work/tree/n2_datepalm_rt_03.spt",
"source_sha256": "2007d4ba90f12b8a00d2a0a539954e420c37f2151f438c5ad06eb63a78fe853d",
"glb": "2007d4ba90f12b8a00d2a0a539954e420c37f2151f438c5ad06eb63a78fe853d.glb",
"glb_sha256": "ec41d7cb6c58864cc32bccca5e364d8fa4746501d4bf9a37d3f64c611834e0c9",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 2,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"2bc2d707e302c58d099e64615ae127371c93700f91d52d31e8ec6f63010e2c0f": {
"source": "Tree/ymir work/tree/n2_joshuatree_rt_01.spt",
"source_sha256": "2bc2d707e302c58d099e64615ae127371c93700f91d52d31e8ec6f63010e2c0f",
"glb": "2bc2d707e302c58d099e64615ae127371c93700f91d52d31e8ec6f63010e2c0f.glb",
"glb_sha256": "27937a1b7cb30186a7041b4c8e405e531a3152004d5365082d9e0908e3998670",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 4,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"111cb80abecb5c5a35aaa8315ad6ec4c59772af927729780e0add02c9ba262b5": {
"source": "Tree/ymir work/tree/n2_joshuatree_rt_02.spt",
"source_sha256": "111cb80abecb5c5a35aaa8315ad6ec4c59772af927729780e0add02c9ba262b5",
"glb": "111cb80abecb5c5a35aaa8315ad6ec4c59772af927729780e0add02c9ba262b5.glb",
"glb_sha256": "6082d34315db4cd58e606b4b35cdbd07ab374d7587580512e07b0e27cfdedb11",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 4,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"a89a40ac942ee7a3d701a6835450a513a336e9835d20750036374e27c259b2d5": {
"source": "Tree/ymir work/tree/n2_joshuatree_rt_03.spt",
"source_sha256": "a89a40ac942ee7a3d701a6835450a513a336e9835d20750036374e27c259b2d5",
"glb": "a89a40ac942ee7a3d701a6835450a513a336e9835d20750036374e27c259b2d5.glb",
"glb_sha256": "5964a02431360700dabd0f88cddde3419141f9f2626b48068b80a6da4110ddf6",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 6,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"19685a989e47f21d14fb9c2f2e71fd86930f8841a3dafae42b608988c3317119": {
"source": "Tree/ymir work/tree/n2_palmetto_rt_01.spt",
"source_sha256": "19685a989e47f21d14fb9c2f2e71fd86930f8841a3dafae42b608988c3317119",
"glb": "19685a989e47f21d14fb9c2f2e71fd86930f8841a3dafae42b608988c3317119.glb",
"glb_sha256": "6fc27607202b42a36b0cea01228e05e3ffd36fb21800ed5ba171831c8a4b7ae3",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"e167a7cb1d19c9d118be00d5dff39221695a582ec7caeabfae1f7826aff0d823": {
"source": "Tree/ymir work/tree/n2_palmetto_rt_02.spt",
"source_sha256": "e167a7cb1d19c9d118be00d5dff39221695a582ec7caeabfae1f7826aff0d823",
"glb": "e167a7cb1d19c9d118be00d5dff39221695a582ec7caeabfae1f7826aff0d823.glb",
"glb_sha256": "1597e961990b4bae697f92238b93978910c39d839f8585baaa44cda135197cde",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
},
"d096c9a78ede5fb7db97685b6dab4967f9dad9a24dbf147b762faffdf1281fc4": {
"source": "Tree/ymir work/tree/n2_palmetto_rt_03.spt",
"source_sha256": "d096c9a78ede5fb7db97685b6dab4967f9dad9a24dbf147b762faffdf1281fc4",
"glb": "d096c9a78ede5fb7db97685b6dab4967f9dad9a24dbf147b762faffdf1281fc4.glb",
"glb_sha256": "cb67edad8c58da648f2e7b19e5cce0d1ec665bb7738f0fa91ac0fd7dab1176ec",
"native_extractor": "spt_extract.exe",
"extractor_runs": 2,
"primitives": 5,
"leaves": "static_extracted_cards",
"wind": "not_baked"
}
}
}
+28 -4
View File
@@ -10,10 +10,21 @@ set -euo pipefail
cd "$(dirname "$0")"
REPO="$(pwd)"
CFG="${1:-debug}"
OUT="$REPO/build/export"
OUT="${MT_MAC_OUTPUT_DIR:-$REPO/build/export}"
APP="$OUT/mtgodot-poc.app"
GODOT="${GODOT:-godot}"
MAC_ARCHES="${MT_MAC_ARCHES:-universal}"
ENGINE_OVERRIDE="${MT_MAC_ENGINE:-}"
if [ -n "$ENGINE_OVERRIDE" ]; then
test -x "$ENGINE_OVERRIDE"
ENGINE_INFO="$(lipo -info "$ENGINE_OVERRIDE")"
case "$MAC_ARCHES:$ENGINE_INFO" in
universal:*x86_64*arm64*|universal:*arm64*x86_64*|arm64:*arm64*|x86_64:*x86_64*) ;;
*) echo "错误:自定义引擎架构不匹配:$ENGINE_INFO" >&2; exit 1 ;;
esac
echo "自定义引擎:$ENGINE_OVERRIDE"
"$ENGINE_OVERRIDE" --version
fi
BUILD_TYPE="Debug"
if [ "$CFG" = release ]; then
BUILD_TYPE="Release"
@@ -55,10 +66,23 @@ ditto "$REPO/bgm" "$APP/Contents/Resources/bgm"
test -f "$APP/Contents/Resources/assets/asset_index.txt"
echo "== 4. 签名(默认 ad-hocCODESIGN_IDENTITY 可切正式证书)=="
if [ -n "$ENGINE_OVERRIDE" ]; then
cp "$ENGINE_OVERRIDE" "$APP/Contents/MacOS/mtgodot-poc"
fi
CODESIGN_IDENTITY="${CODESIGN_IDENTITY:--}"
find "$APP/Contents" -type f \( -name '*.dylib' -o -name '*.so' -o -name '*.a' \) -exec \
codesign --force --options runtime --timestamp=none --sign "$CODESIGN_IDENTITY" {} \; 2>/dev/null || true
codesign --force --options runtime --timestamp=none --sign "$CODESIGN_IDENTITY" "$APP" 2>/dev/null || true
ENTITLEMENTS="$REPO/macos.entitlements"
test -f "$ENTITLEMENTS"
# The exported Godot executable loads the GDExtension from Contents/Frameworks.
# Because this package uses hardened runtime, the main executable must carry
# disable-library-validation or macOS rejects the ad-hoc/formally signed dylib
# at runtime even when the bundle passes a static codesign verification.
find "$APP/Contents/Frameworks" -type f \( -name '*.dylib' -o -name '*.so' \) -exec \
codesign --force --options runtime --timestamp=none --sign "$CODESIGN_IDENTITY" {} \;
codesign --force --options runtime --timestamp=none --entitlements "$ENTITLEMENTS" \
--sign "$CODESIGN_IDENTITY" "$APP/Contents/MacOS/mtgodot-poc"
codesign --force --options runtime --timestamp=none --entitlements "$ENTITLEMENTS" \
--sign "$CODESIGN_IDENTITY" "$APP"
xattr -dr com.apple.quarantine "$APP" 2>/dev/null || true
echo
+4 -4
View File
@@ -1,7 +1,7 @@
<!-- generated-by: gsd-doc-writer -->
# Android 真机测试指南
本文说明如何在 Android 真机上安装 `mtgodot-poc`、生成并导入资源包、验证资源挂载,以及进行 40250 客户端测试。
本文说明如何在 Android 真机上安装本客户端(APK 产物名仍是 `mtgodot-poc.apk`,包名 `org.internal.mtgodotpoc`、生成并导入资源包、验证资源挂载,以及进行 40250 客户端测试。
## 1. 当前测试范围与已知前置条件
@@ -10,7 +10,7 @@
当前仓库的资源部署存在一个需要特别注意的限制:
- `export-android.sh --install` 会把资源推到 `/sdcard/Android/data/org.internal.mtgodotpoc/files/assets.zip`
- `project/asset_pack.gd` 首先查找 `user://assets.zip`,当前没有显式把上述外部目录加入候选路径。
- `project/asset_pack.gd` 的候选顺序是 `MT_ASSETS_ZIP` 环境变量 → `user://assets.zip``OS.get_user_data_dir()/assets.zip` → 可执行文件目录 → `res://../assets.zip`(见 `_find_local_zip()`);上述外部目录不在候选路径
- 因此,Debug 真机测试应把外部目录中的压缩包再复制到应用私有 `files/` 目录,确保客户端的 `user://assets.zip` 能命中。
另外,40250 classic 网络栈当前由进程环境变量 `MT_PROTOCOL=classic` 选择。Android 通过 Activity 启动 APK 时不能直接继承 Mac 终端的环境变量;如果 APK 尚未增加 Android 侧的协议配置入口或将 classic 设为移动端默认值,则只能完成 APK、资源和 UI 验证,不能据此宣称已经完成真实 40250 登录验收。
@@ -34,7 +34,7 @@
进入仓库并设置环境变量。路径按本机安装位置调整:
```bash
cd /Users/shenlei/Work/mt/mtgodot-poc
cd /Users/shenlei/Work/mt/metin2-client
export ANDROID_SDK_ROOT="$HOME/Library/Android/sdk"
export ANDROID_HOME="$ANDROID_SDK_ROOT"
@@ -369,7 +369,7 @@ shasum -a 256 build/export/mtgodot-poc.apk \
首次测试按以下顺序执行:
```bash
cd /Users/shenlei/Work/mt/mtgodot-poc
cd /Users/shenlei/Work/mt/metin2-client
./pack-assets.sh
./export-android.sh Debug
+6 -6
View File
@@ -1,4 +1,4 @@
# mtgodot-poc — 未完成工作清单
# metin2-client — 未完成工作清单
> 更新:2026-08-29 · 配套 [`GODOT-POC-PLAN.md`](./GODOT-POC-PLAN.md)(计划)、[`MIDREVIEW.md`](./MIDREVIEW.md)(中期评审)、
> [`SHINSOO-WORLD-RENDERING.md`](./SHINSOO-WORLD-RENDERING.md)(世界渲染实施)、[`PARITY-GAP.md`](./PARITY-GAP.md)(距参考图的保真化差距)。
@@ -55,8 +55,8 @@
| B6 | 贴图采样设置(filter / wrap / mip / 各向异性) | ⬜ | S | 现在 shader 里写死 `filter_linear_mipmap, repeat_enable`。 |
| B7 | 顶点色 | ✅ 排查=不适用 | — | 全资产 `.gr2`PC/monster2/effect)无 `DiffuseColor`/`Color0` 顶点成员(grep assets = 0)。Metin2 逐顶点色 = 地形雾(`MapOutdoorRenderSTP` 运行时动态 VB,非资产,Godot `Environment` 雾已覆盖)+ `.mse` `colorfactor`/粒子色(属 E7/§1.2)。libgr2 / `build_mesh` 无需动。详见 PARITY §2.6。 |
| B8 | 切线帧(若确有法线贴图资产) | ⬜ | M | PNT332 无切线。xrender 时期结论:Metin2 资产**基本不用**法线贴图 —— 先排查有没有,再决定是否做(`SurfaceTool::generate_tangents()` 或从 UV 算)。 |
| B9 | 镜面 / 自发光 | 🟡 | M | Sphere-map 高光管线已端口(`SRC_MIX`/`SRC_SKIN``spec_map`/`spec_power`/`spec_enable`,复现 `Material.cpp:305 __ApplySpecularRenderState`:不透明面、相机空间 `reflect().xy``spheremap.jpg``EMISSION += sphere*tex.a*power`)。`Metin2Model.specular_power` 属性,默认 0 休眠 → 需装备层用 `item_proto bSpecular/100` 逐部位驱动`SRC_ADD` / `SRC_ADD_SKIN` 已覆盖 additive 发光的一种,且 additive 动画面不再漏蒙皮。 |
| B10 | 背面剔除 / 双面 | 🟡 | S | 世界静态对象(W3/B1)已按 `two_sided` + 贴图名切 `CULL_DISABLED`角色 `m2_material` shader 仍写死 `cull_back``MaterialDesc::two_sided` 死字段)。需给角色也按材质切。 |
| B9 | 镜面 / 自发光 | 🟡 | M | Sphere-map 高光管线已端口(`SRC_MIX`/`SRC_SKIN``spec_map`/`spec_power`/`spec_enable`,复现 `Material.cpp:305 __ApplySpecularRenderState`:不透明面、相机空间 `reflect().xy``spheremap.jpg``EMISSION += sphere*tex.a*power`)。`Metin2Model.specular_power` 属性,默认 0 休眠。✅ 装备层已接:`project/ui/equip_model.gd:283-288` `_apply_body()``item_proto``specular` 列并 `set("specular_power", sp / 100.0)`。⬜ 只驱动身体/护甲一个部位(武器 / 发型 / 时装未接),且 power 未按受控参考帧标定(PARITY §0 门禁)`SRC_ADD` / `SRC_ADD_SKIN` 已覆盖 additive 发光的一种,且 additive 动画面不再漏蒙皮。 |
| B10 | 背面剔除 / 双面 | ✅ 首版 | S | 世界静态对象(W3/B1)已按 `two_sided` + 贴图名切 `CULL_DISABLED`✅ 角色也已按材质切:`m2_material.cpp:188-211` `shader_for()``two_sided``cull_back` replace 成 `cull_disabled`opaque/blend × 普通/蒙皮 四个变体各自缓存),`metin2_model.cpp:1182`(身体)/ `:769`(武器)经 `decide_two_sided()` 驱动,`_load_hair()` 直接置 true。⬜ 判据仍是启发式 —— `gr2::MaterialInfo.two_sided` 按材质名(`2side`/`twoside`/`double`/`leaf`/`tree`/`ivy`/`fence`+ 表面名兜底(`hair`/`cloak`/`cape`/`skirt`/`ribbon`/`leaf`),不是真 `ExtendedData` 的 "Two-sided"。 |
| B11 | winding 逐模型验 | ⬜ | S | `flip_winding` 默认 false(4 个骨架验过,未全量)。 |
---
@@ -117,14 +117,14 @@
| ID | 项 | 状态 | 规模 | 说明 |
|---|---|---|---|---|
| F0 | vendored 原生依赖 | ✅ | S | libsodium / libzstd / miniLZO 从 Homebrew 改为**源码内建**`extension/third_party/`pinned submodule + vendored miniLZO)—— 同一棵树可交叉编 NDK / iOS SDK。别名 `mt3p::sodium` / `mt3p::zstd` / `mt3p::minilzo`,无源码改动,macOS 9/9 CTest + 真实 item/mob_proto 解码逐字一致。详见 `docs/THIRD-PARTY.md`。miniLZO 是 GPL —— 自用可接受(同 oodle1.c),发布前须换。 |
| F0 | vendored 原生依赖 | ✅ | S | libsodium / libzstd / miniLZO 从 Homebrew 改为**源码内建**`extension/third_party/`pinned submodule + vendored miniLZO)—— 同一棵树可交叉编 NDK / iOS SDK。2026-09-02 起第四个:**Crypto++ 8.4.0**vendored 源码,`MT_PROTOCOL=classic` 的 DH2/CTR cipher 用,公有领域许可)。别名 `mt3p::sodium` / `mt3p::zstd` / `mt3p::minilzo` / `mt3p::cryptopp`,无源码改动,macOS 9/9 CTest + 真实 item/mob_proto 解码逐字一致。详见 `docs/THIRD-PARTY.md`。miniLZO 是 GPL —— 自用可接受(同 oodle1.c),发布前须换。 |
| F1 | iOSiPhone 16 / Metal | 🟡 | M | ✅ 交叉编译打通:`./build-ios.sh``libmtgodot.ios.template_{debug,release}.a`arm64 / minos 15.0),全依赖(libsodium/zstd/miniLZO/libgr2/formats/mtnet/godot-cpp)随之交叉编过;iOS 强制 extension 为 STATIC(不能 dlopen),`.gdextension` 加了 `[dependencies] ios.*` 归档清单。⬜ 剩 Godot 4.7 iOS 导出模板 + 导出 preset 链 `project/bin/ios/*.a` + 免费 provisioning 签名 + iPhone 16 真机跑 + Release/strip。预留 23 天。详见 `docs/PLATFORMS.md`。 |
| F2 | Android(一加 13 / Vulkan | 🟡 | M | ✅ CMake 已接 `CMAKE_SYSTEM_NAME==Android`SHARED `.so` + `arm64` 名字后缀);`./build-android.sh` 走 NDK 自带 `android.toolchain.cmake``arm64-v8a` / `android-24`)。⬜ 本机无 NDK —— 装 NDK 后 `./build-android.sh``.so`,再 Godot Android 导出 presetGradle+ 一加 13 真机跑。**只验 Vulkan**,不做 Compatibility(GLES3) / 老机。详见 `docs/PLATFORMS.md`。 |
| F3 | 三端一致性 | ⬜ | S | 同工程 / 同 t / 同机位截图,iPhone 16 · 一加 13 · Mac 对齐。 |
| F4 | 移动端性能 / 帧预算 | 🟡 | M | ✅ **GPU 纹理压缩**`extension/src/texture_util.{h,cpp}` `make_color_texture()` —— 移动端(`OS.has_feature("mobile")`)或 `MTGODOT_TEXCOMP=1` 时对颜色贴图运行时 ASTC 8x8~2bpp vs RGBA8 32bpp),编码失败回退 RGBA8;桌面默认关,逐调用点保留原 mipmap 标志 → OFF 时与旧路径逐字节一致。接入:角色皮肤(`_load_dds`)、建筑 albedo`static_object`)、树皮/树冠(`tree_placeholder`);splat 控制图 / 阴影图 / 水 / HUD 保持 RGBA8。macOS+iOS 编过,ctest 9/9,桌面渲染无变化,`MTGODOT_TEXCOMP=1` 角色渲染正常。⬜ 剩:一加 13 / iPhone 16 单 + N 角色 bench`MTGODOT_GPUSKIN=1` 人群)、构建成本(~170ms/角色,A5/G2)、移动端画质核对。 |
| F5 | App 生命周期 | 🟡 | SM | ✅ `project/app_lifecycle.gd`AppLifecycle 节点):把 `NOTIFICATION_APPLICATION_PAUSED/RESUMED` / `FOCUS_IN/OUT` / `OS_MEMORY_WARNING` / `WM_GO_BACK_REQUEST` 收成信号,默认动作 = 后台暂停树 + 停 BGM + 降 `max_fps`,前台还原;`bind(m2client, audio)` 一键接。`M2Client`C++):`_notification` 收 PAUSED/RESUMED → `suspend()/resume()`(挂起时不 pump socket),`reconnect()` 用存好的凭据重登;新信号 `suspended`/`resumed`。已接 `login.gd`(回前台若在游戏中自动重连)+ `world_demo.gd`。headless smokesuspend/resume/reconnect 绑定 + 幂等 + 信号)通过,ctest 9/9,iOS 编过。渲染上下文丢失由 Godot Vulkan 自恢复,GPU 资源全 RenderingServer 托管,无需处理。⬜ 剩真机验证后台 30s→掐连接→回前台重连、iOS 内存告警实际触发。 |
| F6 | 打包 | ⬜ | SM | macOS / iOS / Android export preset、资产打包策略、iOS 签名。自用,无商店上架。 |
| F8 | 实体点选精度 | ✅ 首版 | S | `player_controller._ray_pick_t()`:射线 vs 每个 pickable 下所有 `MeshInstance3D` 的**世界 AABB 合并 + slab 相交**(逐实体精确,大怪 / 小怪不再被固定 1.4 m 半径误判;`grow(0.15)` 宽容度)。无网格回退旧胶囊近似。对齐 `EterGrnLib/ModelInstanceCollisionDetection::Intersect` 的**结果**,不复刻逐三角。`eterngrn_polish_test.gd` 覆盖(命中 t / 未命中 -1 / 擦边 / 无网格回退)。⬜ 逐三角 / 骨骼包围体、`GetHeight`。 |
| F9 | 实体点选精度 | ✅ 首版 | S | `player_controller._ray_pick_t()`:射线 vs 每个 pickable 下所有 `MeshInstance3D` 的**世界 AABB 合并 + slab 相交**(逐实体精确,大怪 / 小怪不再被固定 1.4 m 半径误判;`grow(0.15)` 宽容度)。无网格回退旧胶囊近似。对齐 `EterGrnLib/ModelInstanceCollisionDetection::Intersect` 的**结果**,不复刻逐三角。`eterngrn_polish_test.gd` 覆盖(命中 t / 未命中 -1 / 擦边 / 无网格回退)。⬜ 逐三角 / 骨骼包围体、`GetHeight`。 |
| F7 | 触屏输入 / 手势相机 | ✅ | S | `game_camera.gd`:单指拖拽环绕(8px deadzone,低于视作点击)/ 双指捏合缩放;`player_controller.gd`:单指轻点(≤12px 位移 & ≤0.35s)= 点地 / 点选,拖拽或双指手势时不触发。`project.godot``emulate_mouse_from_touch`(避免与轻点重复触发),桌面鼠标路径不变。两脚本 `--check-only` + 项目导入无错。 |
| F8 | `godot-cpp` 钉稳定版 | ✅ | S | gitlink 精确锁定 `101ae38034304346a46ea9ea84ae156d3e860496``.gitmodules` 已移除移动的 `master` branch 配置;目标 API 固定为 Godot 4.7。 |
@@ -183,7 +183,7 @@ UI / Python 脚本层、多角色 AI、网络、玩法逻辑、eterpackPhase
### Sprint 1 —— 小项收尾(~1 周)
已完成:`A3` · `A6` · `F8` · `I1`,并补完 `A9`/`A10` 正确性问题。
已完成:`A3` · `A6` · `F8``godot-cpp` 钉版)· `I1`,并补完 `A9`/`A10` 正确性问题。
剩余:`A5`CPU 路径原地更新)· `A7`(前台 Metal stress 基线)· `A8`+`H1`(桥接层畸形用例与全语料 draw 冒烟)。
### Sprint 2 —— 组合基础(~2 周)
+154 -11
View File
@@ -10,7 +10,7 @@
> **[待读参考]** 并给出应读的参考文件,而不是臆造。
>
> 路径约定:`REF/` = `/Users/shenlei/Work/mt/40250/ClientVS22/source/`
> `POC/` = `/Users/shenlei/Work/mt/mtgodot-poc/`
> `POC/` = `/Users/shenlei/Work/mt/metin2-client/`
>
> 创建 / 修订日期:2026-09-04
@@ -355,8 +355,10 @@
普攻 `_swing_skill=0`),被击退目标记 `_victim_flush``_emit_swing()` 改为**只有拿不到
命中窗**(headless / 缺资源)才在挥击时立即发 `CG_ATTACK``_flush_victim_list()`(对齐
`PythonPlayerEventHandler.cpp:194`)帧末把 `_victim_flush`(≤16)打包 `client.sync_positions()`
几何判定为近似:`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面 `±60°`
`(reach+20+45) cm` 弧内;真实动态圆柱扫掠需骨骼矩阵 + defending sphere(随 §3.1 留桩)。
~~几何判定为近似:`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面 `±60°`
`(reach+20+45) cm` 弧内;真实动态圆柱扫掠需骨骼矩阵 + defending sphere(随 §3.1 留桩)。~~
**增量 130 已替换**为 `.msa` 刀尖扫掠 Z 圆柱 vs `.msm` 防御球的 `detect_z_cylinder` 判定(见 §3.5 修改 4 /
C.5 增量 130)。
`combat_fx_test.gd``FakeNetWorld` + 6 段断言(`CG_ATTACK` 延后 / 窗内命中 / `CG_SYNC_POSITION` /
COMBO 单次 / 正面弧 / 越窗收起)。C++ 21/21 + 7 个 GDScript 回归全绿。留给增量 84:武器种类 →
`combo_motion_mode` 映射。
@@ -700,7 +702,7 @@
新增 `_attack_ctx()``client.get_duel()` / `get_pvp_relations()` / `get_guild_wars()` /
`get_party()` 装配 ctxobserver / main_vid / pk_mode / duel_mode / duel_opponents /
pvp_pairs / gvg_pairs / party_vids)。5 处调用点(含 `__ReserveProcess_ClickActor` 第 3 步、
`_can_shot``_hit_geometry`)无签名变化。
`_can_shot``_hit_geometry`;增量 130 起后者并入 `_normal_attack_process`)无签名变化。
④ 自检 `project/entity_rules_test.gd`(第 15 个 canonical25 组断言);`combat_fx_test` /
`netplay_test` / `fly_test` 的怪物夹具补 `ch_type: 2`(此前无 `ch_type``_entity_kind` 归 PC
桩按 PC 也「可打」;1:1 规则下和平同帝国 PC 不可打 → 夹具需明确怪类别,与真实服务器一致)。
@@ -2352,9 +2354,23 @@ OnHit(uSkill, victimActor, isSendPacket):
**只有拿不到命中窗数据**(headless / 缺资源)才在挥击时立即发 `CG_ATTACK`,有窗时交给
`_attack_process``_flush_victim_list()`(对齐 `PythonPlayerEventHandler.cpp:194`)在
`_process` 帧末把 `_victim_flush`(≤ `SYNC_POSITION_LIMIT` 16)打包 `client.sync_positions()`
几何判定是 **近似**`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面
~~几何判定是 **近似**`reach = max(WeaponLength, 窗内采样最大水平偏移)`,命中 = 目标落在正面
`±60°``(reach + 20 + 45) cm` 弧内;真实的动态圆柱-圆柱扫掠需要骨骼矩阵 + defending
sphere 数据(同 §3.1 actor 碰撞一起留桩)。`combat_fx_test.gd``FakeNetWorld` +
sphere 数据(同 §3.1 actor 碰撞一起留桩)。~~
**增量 130 升级为逐行判定**:删除 `_hit_deduped` / `_hit_geometry`,新增 `_normal_attack_process()`
对齐 `__NormalAttackProcess`
- 距离门 `v3Distance = (dX, dZ, dZ)`:出货代码平面 Y 不参与、高度差算两次,原样保留;上限 300 cm,
`IS_HUGE_RACE` 为 500 cm。
- 命中窗 `[start,end]``[t-dt, t]` 相交后查 `m_HitDataMap`COMBO 同窗不再判,其余等 `fInvisibleTime`
- 窗内每个采样的刀尖 `last_pos → pos` 按攻击者朝向转到世界 cm,作为半径 20 的动态 Z 圆柱,
对受击方每个防御球做 `HitCollision.detect_z_cylinder()`(逐行移植
`DetectCollisionDynamicZCylinderVSDynamicZCylinder` + `IntersectLineSegments`)。
- 碰上后 `_register_hit()``_process_attack_success()`
- `_register_hit` 修正为 `map::insert` 语义:已有目标不覆盖冷却,插入后超上限才返回 FALSE。
- 防御球取 `.msm``CollisionType 3``HitCollision.parse_msm_defending`):PC 读
`root/msm/<class>_<m|w>.msm`,怪物读模型目录 `.msm`。上一帧球心缓存在 `_def_last`
- 判定时间改为 `_attacking_elapsed() = (_local_time - _motion_start_t) * _atk_speed_factor`
`GetAttackingElapsedTime`);`_local_time``_advance_local_time()` 先扣 `m_fDelay` 再推进。`combat_fx_test.gd``FakeNetWorld` +
命中窗几何判定 / `CG_ATTACK` 延后 / `CG_SYNC_POSITION` / COMBO 单次 / 正面弧 / 越窗收起 6 段断言。
5. `OnAttack` 事件(动作真正起手时)发 `CG_CHARACTER_MOVE{FUNC_COMBO, wMotionIndex}`
`CG_ATTACK` 是**两个不同时刻**的两个包,不能合并。**[已在增量 78 `_do_attack_swing` 落地]**
@@ -2395,8 +2411,10 @@ OnHit(uSkill, victimActor, isSendPacket):
修改 6 完成(增量 84`motion_mode_for` + `_refresh_motion_mode` 按装备武器 subtype / 骑乘 / 变身
`combo_motion_mode`,对齐 `RefreshState`);修改 7 完成(增量 85`_clickable_distance()` 弓箭
`__GetBowRange()` 分支 + `_bow_range_cm()` + `POINT_BOW_DISTANCE` 加成)。C++ 21/21 + 7 GDScript
全绿。仍待:几何判定升级为真实动态圆柱扫掠(需骨骼矩阵 + defending sphere,随 §3.1 actor 碰撞)、
服务端合法性校准。§3.6 箭矢 `MOTION_EVENT_TYPE_FLY` 事件已在增量 86 落地(见 §3.6)。
全绿。~~仍待:几何判定升级为真实动态圆柱扫掠(需骨骼矩阵 + defending sphere,随 §3.1 actor 碰撞)、
服务端合法性校准。~~ —— 增量 130 已落地:Z 圆柱 vs `.msm` 防御球,以及 `__ProcessDataAttackSuccess`
的完整效果(见修改 4 / §3.7 / C.5 增量 130)。仍待:服务端合法性校准;防御球当前是模型本地近似,
未跟骨骼矩阵走。§3.6 箭矢 `MOTION_EVENT_TYPE_FLY` 事件已在增量 86 落地(见 §3.6)。
---
@@ -2512,6 +2530,35 @@ handler 由 `__OnPressActor` 通过
`netplay_test` / `netbridge_test` / `gamescene_test` / `player_motion_test`
全绿。
**增量 130(受击方反应 1:12026-09-12**
- 新增 `project/ui/hit_reaction.gd`,由 PlayerView / MobView 共用。`good()` / `greate()` / `stone()`
逐分支对齐 `__HitGood` / `__HitGreate` / `__HitStone`
- 击倒中不再换受击动作,Greate 在起身中也不换。
- 晕眩时:Good → `Die`(死亡动作由服务端驱动,seam);Greate 只播 `DAMAGE_FLYING(_BACK)`
并置 `m_isRealDead`,不起身。
- `isLock`(普攻 / 连击 / 技能 / 表情 / 钓鱼等)只抖动不换动作;Greate 在 `IsUsingSkill` 时也只抖。
- `scalar = dot(攻击方朝向, 受击方朝向)`:小于 0 走正面 `DAMAGE` / `DAMAGE_FLYING → STAND_UP`
否则先试 `_BACK` 版,缺资源退回正面版。
- 动作链 = `InterceptOnceMotion` + `PushOnceMotion` + 链尾 `PushLoopMotion`,回到受击前的
wait / walk / run。
- `__Shake(100)`100 ms 内模型位置每帧偏移 ±`rand()%10` cm,结束复位。
- `InsertDelay(fStiffenTime)``time_scale = 0` 冻结,到期恢复该动作的 `fSpeedRatio`;冻结期间新绑定的
动作同样保持冻结。
- PC 受击文件按 `playersettingmodule` 注册表取,同名多份等权随机:
- `damage` / `damage_1`BACK 版 `damage_2` / `damage_3`
- `damage_flying` / `back_damage_flying`
- `falling_stand` / `back_falling_stand`
- 怪物在 `STATE_MOTIONS` 新增 `BACK_DAMAGE` / `FRONT_KNOCKDOWN` / `BACK_KNOCKDOWN` / `FRONT_STANDUP` /
`BACK_STANDUP`
- `is_in_hit_reaction()` 改为返回 `_hit.active`:整条链播完才清 `net_play._knock_down`。原来的
`_state == "damage"` 分支并入链尾处理。
- 击退:`net_world.push_victim()``physics_push.gd`(逐行移植 `CPhysicsObject` +
`CEaseOutInterpolation`),每帧叠加位移。
- `blending_position()` = `GetBlendingPosition`,供 `OnHit``CG_SYNC_POSITION` 使用。
- 推动期间服务端位置变化:以新位置为基准继续叠加。
- 推动结束后服务端位置一变,即丢弃偏移。
---
### 3.8 技能缺少统一合法性检查
@@ -7880,7 +7927,7 @@ dx² + dy² > s_fLimitDistance → 不播
project/inventory_ui_test.gd —— FakeProto.item() 补 sub_type=0 / anti_flags=0 /
wear_flags=1<<4 / limits=[] 让 iv.use(5)vnum 19 武器)仍过新前置门
测试:M2_ASSETS=/Users/shenlei/Work/mt/mtgodot-poc/assets ctest --test-dir build → 21/21
测试:M2_ASSETS=/Users/shenlei/Work/mt/metin2-client/assets ctest --test-dir build → 21/21
(无 C++ 改动);56 个 GDScript 回归全 PASS(新增 equip_rules_testinventory_test /
inventory_ui_test 保持绿)。
@@ -7961,7 +8008,7 @@ dx² + dy² > s_fLimitDistance → 不播
weapon_gr2 / shield_gr2 / hair_gr2、脱时装武器重解析、婚纱(桩返回 201)遮武器保头发、
婚纱换捧花(vnum 50201_last_weapon_vnum 恢复为 50201
测试:M2_ASSETS=/Users/shenlei/Work/mt/mtgodot-poc/assets ctest --test-dir build → 21/21
测试:M2_ASSETS=/Users/shenlei/Work/mt/metin2-client/assets ctest --test-dir build → 21/21
(无 C++ 改动);57 个 GDScript 回归全 PASS(新增 part_hiding_testequip_model_test /
equip_rules_test / inventory_ui_test 保持绿)。
@@ -9083,7 +9130,7 @@ dx² + dy² > s_fLimitDistance → 不播
🔎 真机目视:鼠标悬停某 NPC / 怪 → 其头顶名字立即强显(即便未开「总显名字」且超 3500 距)、
移开 → 复原;鼠标压 UI / 悬停地面物品时不强显任何角色名。
参考(sole baseline = /Users/shenlei/Work/mt/40250/ClientVS22/source/;运行时 python =
mtgodot-poc/assets/root/game.py):
metin2-client/assets/root/game.py):
root/game.py:1519 GameWindow.OnRender()
root/game.py:1531 textTail.UpdateAllTextTail()
root/game.py:1533 if True == wndMgr.IsPickedWindow(self.hWnd) —— 鼠标在 3D game 窗口上才拾取
@@ -10145,3 +10192,99 @@ dx² + dy² > s_fLimitDistance → 不播
seam ⑨ 名字与气泡合并为单尾标链表;完整 Quest / fishing / dungeon / mount / observer 玩法与
复杂 UI 分支;真实服务器、真机、纯 pack、双架构和正式签名验收。
```
### C.5 批次记录 · 增量 130W2 / §3.5 · §3.7 战斗与 40250 对齐:命中判定、命中效果、受击反应、攻速、连击类型、技能包)
```text
批次:增量 130 —— 逐项修正与 40250 参考端战斗实现的差异(2026-09-12)。每项先写失败断言再改实现。
1. 连击类型:
net_play.gd 监听 client.combo_changed,由 _on_combo_changed() 处理,对齐
CPythonPlayer::SetComboSkillFlag
· 查连击技能 122 的等级;缺槽或等级 <= 0 直接返回,不改 combo type。
· 否则 _combo_type = enabled ? MIN(level, 2) : 0。
· 不在本地伪造技能等级。
2. 命中判定:
删除 _hit_deduped / _hit_geometryreach + 正面 ±60° 近似)。
· 新增 project/hit_collision.gd,逐行移植:
IntersectLineSegments / FindNearestPointOnLineSegment / FindNearestPointOfParallelLineSegments /
AdjustNearestPoints / DetectCollisionDynamicZCylinderVSDynamicZCylinder
另含 .msm CollisionType 3 防御球解析与 IS_HUGE_RACE。
· net_play._normal_attack_process() 流程:
(dX, dZ, dZ) 距离门 300 / 500 cm → 命中窗相交 → m_HitDataMap →
刀尖采样半径 20 的 Z 圆柱 vs 受击方防御球(上一帧球心存 _def_last)。
· PlayerView 取 root/msm/<class>_<m|w>.msmMobView 取模型目录 .msm,都经 get_defending_spheres() 提供。
· 没有 .msm 的占位节点退化为单球 HIT_FALLBACK_SPHEREseam)。
3. _register_hit
改为 map::insert 语义:已登记的目标不刷新冷却,插入后超上限才返回 FALSE。
4. 攻速:
_atk_speed_factorbAttackSpeed/100>1100 → 0)同时用于两处:
· _attacking_elapsed() = (_local_time - _motion_start_t) * fSpeedRatio
· PlayerView.play_attack_motion(mode, index, speed_ratio) 的 time_scale
play_attack_motion 按 MOTION_MODE 目录绑定 attack(_1) / combo_0N,模式目录缺该段时回退 general。
5. 连击清零:
删除本地连击超时清空。PlayerView / MobView 新增 motion_bound(state) 信号(__SetMotion 尾),
由 net_play._on_motion_bound() 处理:
· 新动作带 AttackingData → 清 m_HitDataMap。
· 连击中换到无 ComboInputData 的动作 → _combo_index = 0。
6. __ProcessDataAttackSuccess_process_attack_success):
· InsertDelay(fStiffenTime):攻击者写 _delay_advance_local_time 先扣),视图 time_scale 冻结;
受击方同样 insert_delay,并记 m_fInvisibleTime_victim_invisible_until,期间 AttackingProcess 跳过它)。
· 击退门 __CanPushDestActor_can_push):
建筑 / 门 / 石头 / NPC / 巨型不推;晕眩必推;否则要求 owner 为主角且 owner 时间 <= 3 s。
通过后 net_world.push_victim() 推动(physics_push.gd 逐行移植 CPhysicsObject + CEaseOutInterpolation)。
OnHit 时目标仍在推动中 → GetBlendingPosition 进 CG_SYNC_POSITION 列表。
· 命中特效 blow_1_low.mse
建筑 / 门放在攻击者身前 30 cm、不转向;巨型放在命中点;其余放在受击方。
game_scene 装配 net_play.fx = fx、fx_parent = Entities。
· 受击反应:石头 / 门 → hit_stoneHIT_TYPE_GOOD → hit_goodHIT_TYPE_GREAT → hit_greate
scalar = cos(攻击方 yaw 受击方 yaw)。状态机 ui/hit_reaction.gd 见 §3.7 增量 130。
· 最后 _on_hitSetTarget + CG_ATTACK。
7. 技能包:
ClassicSession::send_use_skill 删除夹带的 CG_FLY_TARGETING,只发 TPacketCGUseSkill(对齐
SendUseSkillPacket)。飞行目标仍由施法方的 CG_ADD_FLY_TARGETING / OnSetFlyTarget 发送。
net_classic_session_test 新增 "use skill wire: no extra fly targeting" 断言。
8. 既有测试随行为调整:
受击 damage 按 playersettingmodule 注册为 damage / damage_1 等权随机。
· gpu_pose_bounds_testCPU / GPU 两个视图绑定前用同一 seed,保证选中同一段。
· race_motion_assembly_testdamage 允许任一注册变体。
这两个测试在本批次改动后曾失败(CPU / GPU 选到不同段、断言写死 damage.msa),调整后通过。
回归(godot --headless --path project --script <test>.gd):
新增 PASShit_collision_test、physics_push_test、hit_view_test、net_world_push_test。
PASScombat_fx_test、gamescene_test、netplay_test、skill_test、player_move_test、input_key_test、
weapon_attach_test、mob_view_test、player_motion_test、fly_test、remote_player_test、
net_world_vis_test、forest_mob_render_test、p2b_test、playable_harness_test、playable_combat_test、
gpu_pose_bounds_test、race_motion_assembly_test。
已知失败(与本批次无关):equip_model_test "armor 11000 -> values[3]=0 回退 =vnum"(夹具期望过期)。
forest_map_render_test 是 extends Node 的 MT_TEST_MODE 入口,不能用 --script 直跑,本批次未运行。
ctest --test-dir build22/23 PASS。net.classic_session 仍是既有 25 条失败
quest / duel / messenger / exchange / guild / safebox / mall / sync / ground item / despawn),
都与 use_skill 无关;新增的 use skill 断言通过。
验收边界:
已验证(离线):Z 圆柱几何、.msm 防御球解析、命中表 / 上限、攻速缩放、连击类型与清零、
硬直 / 击退 / 同步位置 / 特效挂载、受击动作链 / 抖动 / 冻结、技能包线格式。
未验证:真实服务器下的命中与击退手感、真机视觉。
seam
· 击退没有地形 / 角色碰撞,也无 AdjustCollisionWithOtherObjects、溅射、IsResistFallen。
· owner 时间取客户端观测时刻。
· 防御球按模型本地坐标、不跟骨骼矩阵;占位节点退化为单球。
· 远端角色不跑 AttackProcess;晕眩 → Die 不在本地播。
· 攻击 / 受击动作变体等权随机;受击动作固定取 general 目录。
· 抖动用模型位置偏移,InsertDelay 用 time_scale 冻结。
· 受击链期间的循环动作请求延后到链尾。
· 推动结束后服务端位置一变即丢弃偏移。
· 本地 GC 伤害包仍直接走 set_anim_state("damage")。
改动 C++ 后已重建 mtgodot 扩展;脚本改动需要重新导出 PCK 才进包。
下一批次:
击退世界碰撞与 IsResistFallen;防御球跟骨骼矩阵;远端 AttackProcess;真实服务器战斗验收。
```
+116 -65
View File
File diff suppressed because one or more lines are too long
+16 -7
View File
@@ -1,6 +1,12 @@
# 客户端移植 —— 从素材渲染器到能联机的客户端
> 起点:`mtgodot-poc` 的渲染纵切「差不多能用」(见 [`PARITY-GAP.md`](./PARITY-GAP.md))。
> **适用范围(2026-09-08 复核)**:本文描述的是 **m2dev fork 协议**后端。该后端**已不是默认**——
> `project/client_main.gd:14-15` 在 `MT_PROTOCOL` 为空时无条件兜底成 `classic`,当前默认走
> 40250 经典协议。迁移方案与 classic 后端的协议细节见 [`CLIENT-40250-PORT.md`](./CLIENT-40250-PORT.md)。
> 本文仍然有效的部分:m2dev 后端自身的握手 / 封包 / 相位编排(`MT_PROTOCOL=m2dev` 时仍可切回),
> 以及 §2 记录的移植进度。**不要**把本文的封包号或 `Packet.h` 依据用到 classic 后端上。
> 起点:本仓库(`metin2-client`)的渲染纵切「差不多能用」(见 [`PARITY-GAP.md`](./PARITY-GAP.md))。
> 本文是把它变成**能连服务端玩**的客户端的分层计划。分水岭是**网络层**——别的玩家、
> 物品、战斗、聊天都以封包为数据源。
>
@@ -9,8 +15,8 @@
## 0. 目标服务端
- LAN`192.168.21.203`(这台 Mac 能路由到,但握手端口目前 **connection refused** ——
服务端进程没起 / 端口和默认不同,需确认)
- LAN`192.168.21.203`。~~握手端口 connection refused~~ —— **已解决(2026-08-30**:真机
端到端已打通到 PHASE_GAME,见本文 §3
- 默认端口(`assets/root/serverinfo.py`):auth `11000`,频道/游戏 `11011/11021/11031/11041`
- 测试账号:`admin` / `123456789`
@@ -217,7 +223,10 @@ AEAD 加密 session token。
## 4. 下一步
1. 打通到 `.203` 的连通性(见上)。可达后立即能测:
> **本节 14 项已于 2026-08-30 全部完成**(见 §3 与 [`CLIENT-ROADMAP.md`](./CLIENT-ROADMAP.md)
> 的 P0–P10)。保留为当时的计划记录,不要再当作待办执行。
1. ~~打通到 `.203` 的连通性~~ ✅。可达后立即能测:
- `./build/extension/net_probe 192.168.21.203 11000 admin 123456789` → 完整握手 + `CGLogin3``login_key`
- GDScript 里 `M2Client.connect_to_server(...)` → 一路到 `char_list` / `entered_game`
2. **`GCLoginSuccess3` 布局对真实字节校准**`net_probe` / `M2Client` 收到后 dump 原始字节,
@@ -228,15 +237,15 @@ AEAD 加密 session token。
`Metin2World` 当场景。里程碑「角色连上、站在地图里、别的实体在动」。
4. 输入(点地移动 → `CG_MOVE`)、聊天(`CG_CHAT`/`GC_CHAT` 接 HUD)。
## 4. 风险
## 5. 风险
- **协议版本漂移**`Packet.h` 结构体要和服务端 build 一致。冻结一对 client/server commit。
- **端到端测试阻塞**:目前握手端口 refused,走不到 `CGLogin3` 之前的验证。
- ~~**端到端测试阻塞**:目前握手端口 refused,走不到 `CGLogin3` 之前的验证。~~ 已解除(§3)。
- **移动端 libsodium**Android/iOS 要静态 vendor(本身可移植,非阻塞)。
- 自用范围可跳过:商城 / GM 工具 / 部分社交系统。**反作弊(`CG_HACK` / `ProcessCRC` /
`ProcessScanner`)已在 `CLIENT-GAP.md`「不做清单」标注 ❌ 不做**,除非服务端强制校验。
## 5. 移植面(客户端源码)
## 6. 移植面(客户端源码)
`EterBase/SecureCipher` · `EterLib/{NetStream,ControlPackets,NetPacketHeaderMap}` ·
`UserInterface/{Packet.h, AccountConnector, PythonNetworkStream, PythonNetworkStreamPhase*}` ·
+9 -1
View File
@@ -7,6 +7,14 @@
> 标记:🌐 需服务器端到端 · 💻 离线可做(mock server / 合成封包 / 假 client
> 更新日期:2026-08-30 · 自用,无发布门禁
> **P0P10 首版全部完成**`192.168.21.203` 已联通,`net_e2e` 端到端到 PHASE_GAME。里程碑见文末。
>
> **适用范围(2026-09-08 复核)**:本文整篇以 **m2dev fork 协议**为基线。该后端已不是默认——
> `project/client_main.gd:14-15` 默认走 40250 `classic`(见 [`CLIENT-40250-PORT.md`](./CLIENT-40250-PORT.md))。
> 玩法拆解、UI 与阶段规划仍然适用;但文末「通用做法」第 4 条「封包号 / 结构以
> `m2dev-client-src-main/src/UserInterface/Packet.h` 为准」**只适用于 `MT_PROTOCOL=m2dev`**。
> classic 后端的封包依据是 `40250/ClientVS22/source/UserInterface/Packet.h` +
> `extension/src/net/classic/wire_classic.h`。上面「已联通」也是指 m2dev 联调服;
> 40250 真服(FreeBSD + MySQL)尚未搭起,见 `CLIENT-40250-PORT.md` §7.1。
## 已就绪的地基(这些不重复做)
@@ -348,5 +356,5 @@ PHASE_GAME —— 收到 121 个实体、HP/SP/等级、20 格背包、好友列
1. **每个 P 先做 💻 部分**`wire.h` 加封包 + `EntityStore` 解析 + 合成封包单测(`net_entity_test` 风格)+ 假 client 驱动 UI`netbridge_test` 风格)。
2. 🌐 端到端用 `build/extension/net_e2e`(真机跑 auth→game→选人→进游戏);`MT_NET_TRACE=1` 逐包 header/len/consumed、`MT_NET_DUMP=1` 逐包 hex,对字节用。
3. UI 一律走 P1 的 uiscript 装载器,不手搓布局。
4. 封包号 / 结构以 `../m2dev-client-src-main/src/UserInterface/Packet.h` 为准,`static_assert` 尺寸;**真机字节和参考头文件不符时以真机为准**(已踩:`GC_LOGIN_SUCCESS4``bType``CG_CLIENT_VERSION`)。
4. 封包号 / 结构`MT_PROTOCOL=m2dev``../m2dev-client-src-main/src/UserInterface/Packet.h` 为准,**默认的 `classic` 后端**以 `../40250/ClientVS22/source/UserInterface/Packet.h` + `extension/src/net/classic/wire_classic.h` 为准,`static_assert` 尺寸;**真机字节和参考头文件不符时以真机为准**(已踩:`GC_LOGIN_SUCCESS4``bType``CG_CLIENT_VERSION`)。
5. 玩法逻辑尽量**服务器驱动、客户端只渲染**(尤其 quest / 商店 / 公会),少搬业务规则。
+275
View File
@@ -0,0 +1,275 @@
# 首个 Mac 联网内测版:开发实施文档
日期:2026-09-11。范围:Apple Silicon / arm64 Release。
本文将联网闭环、基础战斗、首测地图和稳定性四项需求拆为可开发任务。本文新增的文件、API、环境变量均为**待实现设计**,不是已存在功能;列为“现有”的入口已按当前工作树核对。本轮只编写文档,不执行联网测试或修改运行时代码。
## 1. 基线与交付边界
现有候选包:`build/export-native-trees-20260911/mtgodot-poc.app`。已通过离线包内加载及干净退出;树木专项覆盖 87 种资源、六图 2092 棵树,不能代替联网或树怪验收。
首版放行不是“渲染全部完成”。必须交付:固定哈希的候选包、实际联网闭环证据、基础战斗矩阵、首测地图截图、稳定性报告和已知问题。风动、逐叶朝向、SpeedTree 原生 LOD、40250 全环境像素一致性可延期。
首测地图拟为出生城镇及练级区、鬼木林、赤鬼木林。开发前通过资源和实际服务器确定 map key、进入坐标、合法路线、角色槽位与怪物刷新;不得凭中文名硬编码地图路径。测试角色等级、装备、技能、掉落条件需由测试环境负责人准备;没有条件时报告 BLOCKED,禁止伪造事件或偷偷修改服务器。
## 2. 已有实现与明确缺口
| 现有文件 / API | 可复用部分 | 本轮必须补齐 |
| --- | --- | --- |
| `script/live_smoke_test.sh` | 指定 MT_SMOKE_APP、凭据输入、报告与进程码 | 地址预检写死;无完整退出错误门禁;缺墙钟超时和战斗流程 |
| `project/live_smoke_test.gd` | setup、状态机、世界读取、移动、聊天、reconnect | 发包成功不是服务端结果;无完整打怪拾取链 |
| `project/client_main.gd` | 包内 MT_TEST_MODE=smoke/render 分流 | 增加 playable、地图视觉入口;普通启动不启用测试 |
| `project/app_flow.gd` | start、state、登录/选角/进场和断线生命周期 | 提供只读场景快照,避免测试遍历私有节点 |
| `project/net_play.gd` | set_attack_key、pick_ground_item、skill_context | 提取公共选目标入口,沿用真实战斗逻辑 |
| `project/ui/quickbar.gd` | activate、assign、restore_from_serverskill_cast_started、skill_rejected | 输入、服务端结果与视觉证据关联 |
| `project/player_skill.gd` | use_skill、可用性与目标检查 | 新增负向/生命周期覆盖,不另造施法实现 |
| `project/ui/mob_view.gd` | build、set_anim_state、motlist 映射 | 树怪矩阵及禁止静默动作回退的验收 |
| `project/rendering_scenario_test.gd` | 离线 CPU/GPU 多角色、帧时间、内存计数 | P99、事件关联、RSS、长期联网负载;现有假客户端不是实网证据 |
| `script/package_render_test.sh` | 进程退出后扫描完整日志 | 抽取通用门禁供联网/稳定性使用 |
当前 `mob_winding_test.gd` 只覆盖 101、110、20001,不覆盖树怪。`native_tree_test.gd` 测的是静态场景树,不能复用其 PASS 证明怪物蒙皮正确。
## 3. 开发拆分与依赖
每个任务需单独可审查 diff,先添加失败用例再修复;工作树已有修改必须保留,不把无关修改混进任务结论。
| ID | 开发内容 | 依赖 | 完成产物 |
| --- | --- | --- | --- |
| INF-01 | 场景清单、报告协议、事件采集、配置校验 | 无 | 配置负测及报告协议测试 |
| INF-02 | 通用包运行器、墙钟超时、退出门禁 | INF-01 | 假进程门禁测试 |
| NET-01 | 包内真实联网状态机、只读诊断接口 | INF-01/02 | 3 次真实闭环 |
| CBT-01 | 技能/普攻输入路径与证据关联 | NET-01 | 四职业矩阵 |
| MAP-01 | 地图/树怪资源审计与离线 Metal 截图 | INF-01,可与 NET 并行 | 资源清单、动作截图 |
| MAP-02 | 实际地图定位、战斗、落地检查 | NET-01、MAP-01 | 首测地图验收 |
| STB-01 | 采样、循环场景、故障与窗口测试 | NET-01、CBT-01、MAP-02 | 2 小时报告 |
| REL-01 | 重打包、聚合门禁、已知问题 | 全部 | 可追溯内测包 |
实际运行缺陷的修复范围由证据决定;不能预先将未知 bug 标成已定位。缺陷修复卡必须写出:失败用例 ID、资源/角色/地图、原因、修改文件、修复前后证据、回归集合。
## 4. INF-01:测试基础设施的数据契约
### 4.1 新增文件
- `test/playable/scenario.example.json`:无账号密码的模板,未解析的地图/技能字段留空并拒绝运行。
- `project/testing/playable_config.gd`:验证配置、场景与资源前置条件。
- `project/testing/playable_report.gd`:统一检查、事件记录与最终报告。
- `project/testing/playable_probe.gd`:只读订阅生产对象,不发包、不写实体数据。
- `project/playable_harness_test.gd`:假客户端验证状态机和证据判定,不作为实网验收。
配置采用 schema_version=1;包含 scenario_id、protocol、serverinfo 中选定地址、character_slot、map_key、waypoints_cm、allowed_mob_vnums、allowed_drop_vnums、skill_cases、resolution、loops、timeout_seconds。所有坐标字段显式带单位,网络到世界换算沿用生产代码;不把 Vector2/Vector3 强转用于凑通过。路线必须预先人工确认安全可达。
账号密码仅通过现有 MT_ACCOUNT/MT_PASSWORD 或安全交互输入提供;禁止写入 JSON、命令行参数、截图或报告。报告不复制进程环境。启动脚本不能使用 set -x。日志在落盘前脱敏,测试账号名也不写入最终公开报告。
### 4.2 报告契约
`report.json` 必须具有如下字段:
```json
{
"schema_version": 1,
"run_id": "unique-run-id",
"suite": "playable",
"status": "BLOCKED",
"build": {"engine_sha256": "...", "extension_sha256": "...", "pck_sha256": "...", "arch": "arm64"},
"environment": {"os": "...", "renderer": "...", "resolution": [1280, 720]},
"cases": [{"id": "NET-ATTACK-01", "status": "BLOCKED", "duration_ms": 0, "reason": "fixture_not_ready", "evidence": []}],
"failures": [],
"blocked": ["fixture_not_ready"],
"coverage": {"required": 1, "passed": 0},
"exit_gate": {"checked": false, "process_code": null, "errors": []}
}
```
状态限定 PASS/FAIL/BLOCKED/SKIP;必测项 BLOCKED、SKIP 均不得放行。未知 schema、空 cases、缺哈希、证据文件不存在、required 与清单不一致均失败。客户端只能写 `client-report.json`,父运行器在真实退出后生成最终 `report.json`,防止退出前 PASS 掩盖泄漏。
事件写 `events.jsonl`,每条含 monotonic_us、run_id、case_id、connection_epoch、stage、kind、actor_vid、target_vid、payload(白名单字段)。重连后 VID 可变化,按角色身份与新 epoch 重新绑定,禁止等待旧 VID。检测“来自服务端”的依据必须是接收端分发,而非本地预测信号。
### 4.3 必须先有的负向测试
无凭据/坏配置、空覆盖、旧报告、错 run_id、报告 PASS 但进程失败、PASS 后输出 RID 警告、只有本地动画无服务端事件、重连后旧事件、超时不退出,均应返回非零。任何缺测都不能降级成 PASS。
## 5. INF-02:统一运行与退出门禁
新增 `script/run_client_gate.sh``script/validate_playable_report.mjs`,由 `script/playable_test.sh` 调用;复用 `package_render_test.sh` 的错误匹配规则,不弱化现有门禁。
1. 每次创建唯一输出目录,记录本次 run_id 和包哈希;不复用历史报告。
2. 校验包可执行、架构和签名;清除 MT_ASSETS,保证验收用包内资源。
3. 预检地址与 `project/net/serverinfo.gd` 的实际选中配置一致。新增测试配置覆盖只能在测试模式生效;删除冒烟脚本预检地址与客户端连接地址分叉的风险。
4. 只管理本运行器启动的子进程 PID;超时先 TERM,宽限 10 秒后 KILL,再 wait 收集状态。不得 pkill 所有客户端或修改全机网络。
5. 读取完整标准输出/错误,检查 SCRIPT ERROR、ERROR、leaked at exit、shaders never freed。无过滤 RID 警告的允许名单。
6. 同时满足进程 0、报告协议正确、全部必测 PASS、完整日志门禁通过,才写最终 PASS。
建议退出码:0=PASS,1=断言或退出门禁失败,2=配置/前置条件阻塞,124=墙钟超时。进程原始退出码另存报告,不丢失。
门禁单测使用脚本生成的假子进程输出,不需游戏服务器。确认超时分支、信号退出和 stdout 管道不会绕过真实进程码。
## 6. NET-01:真实联网闭环状态机
### 6.1 修改点与接口
- 新增 `project/playable_live_test.gd`,实现 `setup(flow: Node, client: Node, config: Dictionary)`
- `client_main.gd` 增加 `MT_TEST_MODE=playable` 分支,在 AppFlow 建立后挂载该 Node;不得导出后用 --script 替代包内入口。
- `app_flow.gd` 新增只读 `get_playable_snapshot() -> Dictionary`,返回 stage、角色身份摘要、scene_ready 和当前地图。测试对象引用通过明确的测试 adapter 获取,不把 Node 放入 JSON。
- `game_scene.gd` 新增只读 `get_playable_context() -> Dictionary`,在测试模式提供现有 net_play、quickbar、net_world、world、player 的引用;场景销毁时失效。
- `net_play.gd` 新增公共 `select_target(vid: int) -> bool`,复用现有 `_change_target_to_picked_instance``_on_pick` 的共同目标选择逻辑;保留距离、死亡和可攻击检查,不直接改私有字段。
先核实 M2Client 信号签名与 classic 收包源;缺少来源可辨识证据时,向现有接收分发处增加测试诊断通知,不添加假业务事件,不改变协议格式。
### 6.2 状态及判定
| 状态 | 动作 | 成功证据 | 默认超时 |
| --- | --- | --- | --- |
| LOGIN | 复用 AppFlow 自动登录 | char_list 接收且指定槽位存在 | 30s |
| SELECT | 现有选角流程 | entered_game + 主实体有效 | 45s |
| WORLD_READY | 等待场景加载 | map key 符合、地图与角色就绪、HUD 可用 | 60s |
| MOVE | 通过玩家控制器走配置路线 | 本地到达 + 无服务端纠正;重登/服务端观察验证落点 | 20s/点 |
| TARGET | select_target 选择允许范围怪物 | 当前 epoch 中存活实体、race 合法 | 30s |
| ATTACK | set_attack_key(true),结束/失败必释放 | 指定目标接收血量/伤害变化与死亡结果 | 90s |
| DROP | 观察真实掉落 | 新地面物品、允许 vnum、可拾取归属和邻近位置 | 30s |
| PICKUP | pick_ground_item(iid) | 地面消失 + 背包对应 vnum 总量增加 | 15s |
| EXIT | 正常退出入口 | 父运行器检查真实退出和完整日志 | 20s |
MOVE 不要求服务器必然回显主角每一个移动包;classic 若无回显,使用重登位置或服务器/第二观察客户端确认,不把本地 position 变化叫做服务端确认。若没有可用证据,标记该断言 BLOCKED。
拾取比较所有背包格的 vnum 总量(支持堆叠),并关联时间窗和地面 iid;只看到地面删除不能证明由自己拾取。金钱掉落另测点数,不套背包断言。无掉落的随机战斗不能无限重试:测试环境准备确定可拾取掉落,最多 3 次允许怪物击杀,无条件则 BLOCKED。
每个状态入口记录快照,失败保存截图;禁止跨状态复用旧计数。断线立即取消输入、释放目标和待处理操作。统一 teardown 断开监听,场景 free 后 probe 不再访问节点。
### 6.3 用例与完成条件
- NET-01:完整闭环 3 次,每次独立进程与报告。
- NET-02:无效账号和不存在槽位,明确错误且有界退出(限制尝试,避免账号锁定)。
- NET-03:进场期间断线,不能留下半加载操作对象。
- NET-04:目标在攻击前消失,取消操作而非继续攻击旧 VID。
- NET-05:掉落被他人拾取、背包已满,不能误报成功;需专用夹具或离线负测,实网缺条件须标出。
自动调用业务入口不等于鼠标命中正确;另做一次真实鼠标/键盘闭环,记录输入设备与截图,作为手动必测项。
## 7. CBT-01:基础战斗和技能
新增 `project/playable_combat_test.gd`(离线事件与输入断言)、`test/playable/skill-cases.json`(实际角色技能矩阵)。联网用例由 playable_live_test 调度,不再创建第二条发包路径。
### 7.1 实现与定位
1. 通过 Quickbar.assign/restore_from_server 构造测试账号实际槽位;需要持久化变更时仅对专用账号执行,记录原值并在可行时恢复。无服务端技能等级不得本地伪造。
2. 调用 activate 复用 PlayerSkill.use_skill,监听现有 skill_cast_started(skill_id,target_vid) 与 skill_rejected(skill_id,code)。cast_started 只是本地施法开始,不是服务端命中证明。
3. case_id 绑定施法时目标;施法中换目标后,定点效果仍使用原目标快照,跟随效果按技能定义更新。
4. probe 关联接收端资源/状态变化、目标血量或伤害事件;不同技能配置 required_evidence,不强制所有技能都有伤害或资源消耗。
5.`game_scene.gd``fx/skill_fx.gd``fx/effect_player.gd` 的实际生成/结束边界记录测试事件。不可只用节点数量增加判定特效正确。
6. 自动截图覆盖施法前、触发帧、峰值、结束;短命特效按事件时间采样,不统一等一秒。
诊断路由:图标/点击区域→quickbar;拒绝条件/目标→player_skill;动作触发→net_play 与玩家视图;挂点/生命周期→game_scene、skill_fx、effect_player;骨骼坐标→metin2_model/metin2_anim。只在复现后修改对应层。
### 7.2 测试矩阵
每职业从真实已学技能中选择适用类型:单体、范围、自身增益、飞行效果;不存在的类型标注不适用并经清单确认,不擅自补技能 ID。每技能成功施放 10 次;另覆盖无目标、超距、冷却、资源不足、目标死亡、自身死亡、切图/断线、目标切换。
断言包括:拒绝不发有效施法请求;正常请求数无重复;服务端效果可观察;角色持续可见且包围盒有限;特效在定义寿命+1秒宽限内清理;挂点相对预期骨骼/目标坐标误差默认不超过 0.1m(有资源偏移需记录校准值)。
`skill_test.gd``player_skill_test.gd``combat_fx_test.gd``target_effect_test.gd` 扩展相应失败用例,并纳入 rendering_batch_test。截图必须人工复核一次,像素非空不能证明技能外观正确。
完成条件:必测职业/技能覆盖完整、功能阻断清零、首次施法与重复施法均通过;高级特效差异写入已知问题,不改成假 PASS。
## 8. MAP-01/02:鬼木林、赤鬼木林与树怪
### 8.1 资源审计
新增 `script/audit_playable_maps.mjs`,输出 `map-assets.json`。读取实际 map 配置、AreaData/Property、npclist、MSM、motlist 和引用文件,分别列出静态 SPT、动态 GR2、贴图、动作及缺失项。没有服务端刷新数据时只报告候选树怪,不声称已确定地图所有怪物。
已核实 `assets/root/npclist.txt`23012307 对应 ent_trent/ent_guru/ent_hu/ent_red/ent_black/ent_huge/ent_elder23112315 对应前五种。共享模型仍保留独立 race 用例,避免漏掉 proto/外观配置差异。实际 map key 与刷新关联由前置审计填入配置。
### 8.2 单模型 GPU 验收
新增 `project/forest_mob_render_test.gd`,沿用 MobView.build、set_anim_state 和现有 Metin2Model/Metin2AnimPlayer。读取 motlist 的具体文件,报告 requested_state、resolved_motion、fallback_reason;“调用 dead 却播放 wait”不能通过。
对 12 个候选 race × CPU/GPU × wait/run/attack/damage/dead 生成基准集合;资源确无动作时据旧客户端回退规则单独判定,不能静默跳过。动作每个采样起点、25%、50%、75%、末段;固定随机种子或明确选择变体。死亡动作不能为了截图强行循环。
自动断言:构建成功;贴图实际加载;顶点/包围盒有限;无非法材质;同一姿态 CPU/GPU 包围盒一致(初始容差 1cm 或高度0.5%,取较大值);截图非空。容差变更需记录理由,不通过放宽到无限解决失败。
输出每 race 的正面、侧面、动作 contact sheet 和 JSON。检查材质双面/裁剪应依资源,不对所有树怪强制双面;蒙皮异常先检查骨骼索引、权重、bind pose、坐标,再修改 C++ 公共路径。`mob_winding_test.gd` 增加这些 race,若影响全怪物必须回归原 101/110/20001。
### 8.3 地图内验收
新增 `project/forest_map_render_test.gd` 和包内 `MT_TEST_MODE=forest_render` 分支,加载配置指定的生产地图。新测试模式不存在于旧包,必须重新构建后执行。
在经确认的平地、坡地、密林和传送点保存固定机位截图,记录 world.sample_height、怪物世界位置、ground offset、材质和实际动作路径。平地不允许持续悬空/埋地超过校准阈值;坡地需人工检查脚/根部接触,AABB 底部不是精确足部,不能用它宣布足部 IK 完成。
实网追加逐地图进入、选中树怪、攻击、死亡、拾取、离开。静态地图截图与动态服务器刷新分别记录证据,不混为一次测试。
完成条件:首测区域关键资源无未解释缺失、主要动作和目标交互正常、Metal 截图人工签核;其余地图不在本轮覆盖中。
## 9. STB-01:稳定性与长帧
新增 `project/testing/playable_metrics.gd``script/playable_soak.sh`,扩展 rendering_scenario_test 的 P99 与事件时间点,但保留“离线合成”和“真实联网”两种 suite 标签。
### 9.1 指标实现
- 每帧记录 monotonic 间隔;按 map/phase/actor_count/gpu_skin/动作切换标记。加载与游玩分段统计。
- 固定大小环形缓冲,按时间窗口输出分位数和 >50ms/>100ms 样本;不得无限数组增长制造测试自身内存泄漏。
- 默认关闭逐帧打印,只保留长帧事件;采样开启/关闭各跑短基线,量化探针开销。
- 外部采样父运行器所持 PID 的 RSS,每秒一次,记录单位 KiBGodot MEMORY_STATIC 单独记录。GPU 内存拿不到写 null/unavailable,不填写 0 或据此宣称无显存泄漏。
- 缓存预热后比较重复路线每轮结束并静置30秒的低水位;记录前5轮与后5轮中位数。连续5轮增长且后5轮中位数高出 max(50MiB,5%) 时门禁失败并分析;阈值以下也不能证明长期无泄漏。
### 9.2 执行场景与边界
2小时实网循环移动/战斗/拾取;20次经合法传送入口切图;10次重连;3种受支持逻辑分辨率各切10次;10次正常退出。窗口物理像素与逻辑尺寸都记入报告,不能把 Retina 缩放当成分辨率错误。
主动 reconnect() 与真实传输故障分别计数。真实故障至少覆盖“连接被关闭”和“短时不可达”;仅测试专用连接,可使用经确认的局部代理/测试网络条件,不改全机路由、防火墙、不关生产服务。缺少该条件标为 BLOCKED,不用主动重连替代断网验收。
时间采用墙钟期限,不用固定帧数冒充2小时。重连后重新获取主 VID、场景和 UI 对象,检测重复信号、重复实体、旧目标/技能残留。跨服切图需核实测试服务器支持,失败不能通过直接传送客户端节点掩盖。
### 9.3 长帧处理流程
先标记同步模型 build、动作 reload、GR2解析、材质初次使用和实体批量生成;记录缓存命中。确认瓶颈后才做路径级修改:动画缓存→metin2_anim;实例建立→net_world/game_scene;材质预热→实际渲染建立路径。后台工作只做线程安全的解析,不把 SceneTree/RenderingServer 操作随意搬入工作线程。
相同机器/路线对比修改前后3轮,保留最大帧而非只报平均。正常游玩目标60FPS;同一交互3次重复中至少2次出现 >100ms 视为阻断,任何 >50ms 样本需归因。首次加载独立预算在基线采集后固定,不临时扩大预算让门禁通过。
完成条件:零崩溃/永久卡死、循环后功能正常、无达到上述阈值的未解释内存增长、无重复严重长帧、每次退出干净。冷启动报告注明进程冷启动/系统缓存状态未知,不执行系统缓存清空来影响用户其他程序。
## 10. 执行命令与验证顺序
以下命令在仓库根目录执行。账号通过安全交互提供,禁止放入命令历史。新脚本落地前不得宣称这些未来命令已可运行。
现有可执行回归:
```bash
bash script/rendering_batch_test.sh
MT_RENDER_APP="$PWD/build/export-native-trees-20260911/mtgodot-poc.app" bash script/package_render_test.sh
```
待实现 CLI 契约(各脚本必须有 --help、配置校验、非零失败码):
```bash
# 离线资源/蒙皮/报告门禁,不连接服务器
node script/audit_playable_maps.mjs --config test/playable/scenario.local.json --output build/playable/map-assets.json
godot --headless --path project --script playable_harness_test.gd
godot --path project --script forest_mob_render_test.gd
# 重新构建带新包内测试入口的候选包,不能用旧包冒充
MT_MAC_ARCHES=arm64 MT_MAC_ENGINE="$PWD/build/godot-particle-diagnostic/bin/godot.macos.template_release.arm64" MT_MAC_OUTPUT_DIR="$PWD/build/export-playable-rc1" bash build-macos-client.sh release
# 明确显式允许专用账号上的游戏操作;默认仅校验配置,不发移动/战斗请求
bash script/playable_test.sh --app "$PWD/build/export-playable-rc1/mtgodot-poc.app" --config test/playable/scenario.local.json --suite full --allow-gameplay --repeat 3
bash script/playable_soak.sh --app "$PWD/build/export-playable-rc1/mtgodot-poc.app" --config test/playable/scenario.local.json --allow-gameplay --duration-seconds 7200
node script/validate_playable_report.mjs --release-dir build/playable/rc1
```
新脚本参数还需支持 --output;聚合器只接受显式列出的本次 run_id 与相同包哈希,不扫描历史 PASS 凑覆盖。测试模式、测试账号均禁止自动聊天、交易、出售、丢弃物品或创建/删除角色;不沿用旧 smoke 的聊天副作用。
配置 local.json、凭据文件、运行产物加入适当忽略规则;提交无敏感信息的 example/schema。最终修复 C++ 后必须重建同步扩展、重新签名和打包;脚本变更也必须重新导出 PCK。
## 11. REL-01:放行检查表
- [ ] INF-01/02 负向用例通过,必测项缺失不能 PASS。
- [ ] 同一候选包3轮联网闭环通过,另有鼠标键盘流程证据。
- [ ] 四职业基础战斗、技能及异常路径清单完整。
- [ ] 首测地图和树怪资源/动作/Metal画面验收通过。
- [ ] 2小时、切图、故障恢复、窗口缩放、退出门禁达到配置次数。
- [ ] P0(崩溃/数据异常/无法进场)与 P1(核心操作失效/主要模型丢失/严重错位/重复严重卡顿)清零。
- [ ] 所有 BLOCKED 明确处理;首测必测项不得以“已知问题”绕过。
- [ ] 包哈希、签名、引擎来源、设备信息与报告一致。
- [ ] 已知 P2、未覆盖地图/装备/技能及未验收平台明确列出。
官方 Release 模板仍有粒子退出警告;必须显式使用验证过的自编译 arm64 引擎,并对最终包重新跑退出门禁。不将当前方案表述为已修复所有官方模板或 Intel 平台。
完成文档不等于完成上述开发。开发起点是 INF-01/02;它们完成后 NET-01 与 MAP-01 可并行推进。工期在首次真实闭环和树怪截图产生后根据实际缺陷估算。
+115
View File
@@ -0,0 +1,115 @@
# 首个 Mac 联网内测版:实施状态与已知问题(REL-01)
日期:2026-09-11。对应计划:`docs/FIRST-MAC-PLAYABLE-IMPLEMENTATION.md`
后续审查:提交 `f39a55fd` 的观察器缓存保留、运行取消清理、外部内存门禁与异常 JSON 处理问题,见 [审查与修复记录](REVIEW-f39a55fd.md)。以下环境数据是原实施时的快照。
本文只记录已核对的事实。**当前没有可放行的候选包,也没有任何实网证据**;所有依赖服务器、测试账号、环境负责人确认或人工签核的条目均为 BLOCKED。离线/假进程测试 PASS 只证明门禁和状态机按契约工作,不能代替联网验收。
## 1. 结论
| 项 | 状态 | 说明 |
| --- | --- | --- |
| INF-01 契约与配置校验 | 离线 PASS | `playable_harness_test.gd` 负向用例、`validate_playable_report.mjs` 静态夹具全部通过 |
| INF-02 运行器与退出门禁 | 离线 PASS | `script/playable_gate_test.sh` 用假签名 arm64 包覆盖 PASS/退出码/信号/超时 TERM→10s→KILL/残留 FIFO/RID 与泄漏警告/凭据脱敏/聚合 |
| NET-01 真实联网闭环 | **BLOCKED** | 包内入口已实现;缺 `scenario.local.json`、测试账号、环境负责人确认的路线/怪物/掉落,未联网运行 |
| CBT-01 四职业技能矩阵 | **BLOCKED** | `test/playable/skill-cases.json` 全部 `unconfirmed`;脚本不补技能 ID |
| MAP-01 树怪资源与离线 Metal 截图 | 离线 PASS**待人工签核** | 12 个候选树怪 × CPU/GPU × wait/run/attack/damage/dead 全部组装通过;截图需人工看过 |
| MAP-02 实际地图定位与落地 | **BLOCKED** | `forest-viewpoints.example.json` 机位均 `unconfirmed`;坡地脚/根接触需人工检查 |
| STB-01 2 小时稳定性 | 工具已实现,**未实跑**,BLOCKED | `script/playable_soak.sh` 可运行(见 §4);真实断网与切图需环境确认 |
| REL-01 放行 | **BLOCKED** | 无重建候选包;上述必测项均未产生实网证据 |
## 2. 候选包与环境
- 计划中的候选包 `build/export-native-trees-20260911/mtgodot-poc.app` 和自编译引擎 `build/godot-particle-diagnostic/bin/godot.macos.template_release.arm64` 在当前工作树**不存在**。仅有 `build/export/mtgodot-poc.app`2026-09-08),早于本轮全部 playable 代码,**不能**作为候选包。
- 本轮修改了 GDScript(包内测试入口、流程、指标、窗口适配),**必须重新导出 PCK**;若最终修复涉及 C++,还需 `./build.sh Release` 重建扩展、重新签名打包。重建后对最终包重新跑退出门禁。
- 官方 Release 模板的粒子退出警告未修复;必须显式使用经过验证的自编译 arm64 引擎。不宣称已修复官方模板或 Intel 平台。
- 数据盘剩余约 7.5 GiB(99% 已用)。2 小时 soak 的 `events.jsonl``rss.jsonl`、截图与日志会持续写盘,实跑前需由用户腾出空间;脚本不会自行清理任何文件或系统缓存。
## 3. 已知问题与限制
### 3.1 树怪动作(MAP-01
- `ent_trent`2301/2311)的 motlist 只有 WAIT、WAIT1、NORMAL_ATTACK、NORMAL_ATTACK1、FRONT_DAMAGE、FRONT_DEAD**没有 RUN/WALK**。按旧客户端 `CRaceData::GetMotionKey` 未命中→`SetLoopMotion` 直接返回的规则,移动时继续播放当前循环(`reference_keep_current_motion`)。`forest_mob_render_test` 报告记为 4 条 fallback2 个 race × CPU/GPU),标注“需实网确认:服务器移动该种族时位置变化且保持原循环”。未经实网确认前不能宣称行走表现与原版一致。
- 七种 ent 目录的 motlist 均只有 FRONT_DEAD,没有 BACK_DEAD。背后击杀时的死亡动作按旧客户端回退规则判定,需实网对照确认。死亡姿态、材质、轮廓已进入接触表截图,**必须人工签核**;AABB 底部不是脚部接触,离线测试不能证明坡地脚/根贴地。
- `mob_winding_test.gd` 仍固定覆盖 101/110/20001;树怪由 `forest_mob_render_test.gd` 单独覆盖。
### 3.2 帧时间(离线合成 suite,非实网)
`rendering_scenario_test.gd`Metalsuite=`offline_synthetic`FakeClient + 真实资源)本轮结果:
- 689 帧,>50ms 14 帧,全部已归因(unattributed=0);>100msloading 6、gameplay 2。
- gameplay 的 2 帧(119.5ms、237.0ms)都带 `viewport_resize` 标记,发生在测试主动切换窗口尺寸时。它们不属于正常游玩的重复交互,但在实网 soak 中要继续按“同一交互 3 次中 2 次 >100ms 即阻断”规则判定。
- loading 段:同步模型构建 447746ms,切图 1861ms 与 4489ms。首次加载预算尚未固定(计划要求基线采集后再定,不临时放宽)。
- CPU 蒙皮 8/16 角色时 p99 分别为 70.1ms/50.5ms90 帧窗口,p99 即最大值);GPU 蒙皮 1/8/16 角色 p99 ≤ 26.1ms。
- 视口 1280×720、1920×1080、2560×1440 均按请求尺寸生效(screen_scale=1);Retina 缩放下的物理/逻辑换算由 `testing/playable_window.gd` 记录,未在 Retina 屏实测。
- 冷启动:进程冷启动/系统缓存状态未知,测试不清空系统缓存。
- 显存:`gpu_memory_kib=null`unavailable)。Godot RenderingDevice 计数 843,616 KiB 不是系统显存,不据此判断显存泄漏。探针开销:每次采样 CPU 约 1.26µs;开/关探针 p50 差 −0.018ms,处于帧节奏噪声内。
### 3.3 稳定性工具的边界(STB-01)
- **切图**`soak.warp.status` 默认 `unconfirmed`STB-WARP-01 为 BLOCKED。只有环境负责人确认合法传送入口(`portal_cm``return_portal_cm``destination_map_key``cross_server`、偶数且 ≥20 的往返次数)后才会走传送门;不以直接改客户端节点位置代替。跨服切图须核实测试服支持。
- **真实断网**`soak.faults.status` 默认 `unconfirmed`STB-DISCONNECT-01 为 BLOCKED;主动 `reconnect()`STB-RECONNECT-01)与真实传输故障分开计数,不能互相替代。确认后由 `run_client_gate.sh` 在 127.0.0.1 上启动 `script/playable_fault_proxy.mjs`,只代理本测试客户端的连接(`server.*` 与 serverlist 必须指向代理端口,`soak.faults.upstream` 为真实测试服)。不修改全局路由、防火墙,也不关闭任何服务。
- **已知限制**:如果服务器传送时下发了直连的 game 地址,之后的连接会**绕过代理**,故障注入对切图后的连接无效。需要在确认故障条件时一并核实,或在同一地图服务器内完成故障用例。
- **窗口**:headless、全屏、最大化窗口不可切换尺寸;请求尺寸(含标题栏)超出屏幕可用区域时如实记为 BLOCKED,不缩小目标冒充通过。
- **内存**:父运行器每秒采样子进程 RSS(KiB),在每轮静置 30s 后取低水位;必须 ≥ warm-up + 10 轮才判定。连续 5 轮增长,且后 5 轮中位数比前 5 轮高出 max(50MiB, 5%) 时 FAIL。低于阈值也不能证明长期无泄漏。
- 10 次正常退出由 10 个独立进程完成,每次都经过完整退出门禁;任何一次失败,后续运行(包括 2 小时 soak)不会启动,记为 BLOCKED。
### 3.4 人工项(不能自动化替代)
- 真实鼠标/键盘闭环(点击移动、选怪、普攻、技能栏、拾取)需要单独的人工证据。
- MAP-01/MAP-02 Metal 截图人工签核(`forest_map_render_test` 报告里 `manual_signoff.status` 恒为 `PENDING`,自动 PASS 不代表视觉签核)。
- CBT-01 技能特效截图需人工看过一次。
## 4. 已可运行的命令
以下命令在 `metin2-client/` 下执行。账号只通过 `MT_ACCOUNT`/`MT_PASSWORD` 环境变量或隐藏输入提供,禁止写进配置、参数或命令历史。
离线(不连服务器,本轮均 PASS):
```bash
bash script/playable_gate_test.sh
node script/playable_soak_metrics_test.mjs
node script/playable_fault_proxy_test.mjs
node script/audit_playable_maps_test.mjs
(cd project && godot --headless --path . --script playable_harness_test.gd)
(cd project && godot --path . --script forest_mob_render_test.gd)
bash script/rendering_batch_test.sh
```
联网(需要重建候选包、`test/playable/scenario.local.json` 与测试账号;**本轮未运行**):
```bash
bash script/playable_test.sh --app "$PWD/build/export-playable-rc1/mtgodot-poc.app" --config test/playable/scenario.local.json --suite full --allow-gameplay --repeat 3
bash script/playable_soak.sh --app "$PWD/build/export-playable-rc1/mtgodot-poc.app" --config test/playable/scenario.local.json --allow-gameplay --duration-seconds 7200
node script/validate_playable_report.mjs --release-dir <本批次输出目录>
```
`playable_soak.sh` 行为:
- 先用 soak suite 校验配置和资源。缺少 `--allow-gameplay` 时只做校验,返回 2,不启动客户端。
- 依次运行 `exit-1..exit-N`suite playableN ≥ 10)和 `soak-1`suite soak,墙钟时长 = `soak.duration_seconds`,超时 ≥ 时长 + 900s)。
- 写出只列本批次运行的 `release-manifest.json`required_runs `{playable:N, soak:1}`),再聚合。
- 退出码:0 PASS / 1 FAIL / 2 BLOCKED / 124 超时。
## 5. 放行检查表对照(计划 §11)
| 检查项 | 当前 |
| --- | --- |
| INF-01/02 负向用例通过,必测项缺失不能 PASS | ✅ 离线通过 |
| 同一候选包 3 轮联网闭环 + 鼠标键盘证据 | ❌ BLOCKED(无候选包、无账号/夹具、无人工证据) |
| 四职业基础战斗、技能及异常路径 | ❌ BLOCKED(技能矩阵未确认) |
| 首测地图和树怪资源/动作/Metal 画面 | ⚠️ 离线组装通过;机位未确认、人工签核未做、实网未跑 |
| 2 小时、切图、故障恢复、窗口、退出次数 | ❌ BLOCKED(工具就绪,未实跑;切图/断网条件未确认) |
| P0/P1 清零 | ❌ 未知:没有实网运行,无法判定 |
| 所有 BLOCKED 明确处理 | ⚠️ 本文已列出;首测必测项不得以“已知问题”绕过 |
| 包哈希、签名、引擎来源、设备信息一致 | ❌ 需重建候选包后由运行器生成 |
| 已知 P2、未覆盖地图/装备/技能及未验收平台列出 | ⚠️ 本文 §3;未覆盖:首测三图以外的地图、Intel、官方模板 |
## 6. 需要环境负责人提供
1. 自编译 arm64 引擎和重新导出的候选包(含本轮脚本),以及足够的磁盘空间。
2. `test/playable/scenario.local.json`:由 `scenario.example.json` 复制,填写 serverinfo 中的地址、角色槽位、map_key、安全路线、怪物/掉落 vnumsoak 需 `timeout_seconds ≥ duration_seconds + 900`
3. 专用测试账号(等级、装备、已学技能、掉落条件由负责人准备),以及 `skill-cases.local.json` 中各职业的确认记录。
4. 如需 STB-WARP/STB-DISCONNECT:合法传送入口的确认;确认可在 127.0.0.1 代理后运行,并核实传送是否下发直连地址。
5. 树怪机位(平地/坡地/密林/传送点)的确认,以及 Metal 截图人工签核人。

Some files were not shown because too many files have changed in this diff Show More