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.
This commit is contained in:
shen
2026-09-08 08:52:30 +08:00
parent 8092ff44e2
commit c3a6fb973e
11 changed files with 620 additions and 23 deletions
+2 -3
View File
@@ -305,9 +305,8 @@ func _rebuild_model() -> void:
# Expansion armor textures live beside their GR2, outside the base PC pack.
var body_path := String(pv.model.get("gr2_path"))
pv.model.set("texture_dir", body_path.get_base_dir())
# Shape zero uses the head/hair already included in the armor mesh.
if int(c.get("hair_part", 0)) == 0:
pv.model.set("hair_gr2", "")
pv.model.set("lod_enabled", false)
pv.model.set("flip_winding", true)
pv._ground_model()
var intro := pv.motion_dir.get_base_dir().path_join("intro/wait.msa")
if pv.anim and FileAccess.file_exists(intro):
+13 -4
View File
@@ -29,7 +29,7 @@ var _forward := ["weapon_gr2", "shield_gr2", "gr2_path", "hair_gr2", "hair_skin"
# 它(= M2Client.net_poll),免得整段阻塞几秒漏 PONG 被服务器踢。
func build(assets_root: String, race: int, pump := Callable()) -> bool:
_assets_root = assets_root
if not ClassDB.class_exists("Metin2Model"):
if race < 0 or race > 7 or not ClassDB.class_exists("Metin2Model"):
return false
var cls: String = CLASS_OF[race & 3]
var pc_dir := "pc2/ymir work/pc2/%s" % cls if race in FEMALE_RACES else "PC/ymir work/pc/%s" % cls
@@ -46,9 +46,18 @@ func build(assets_root: String, race: int, pump := Callable()) -> bool:
model.set("texture_dir", base)
if pump.is_valid(): pump.call()
var body := _first_existing([base.path_join("%s_novice.gr2" % cls), base.path_join("%s.gr2" % cls)])
if body != "":
model.set("gr2_path", body) # 重:解 gr2 + 建网格 + 扫 3 级 LOD
if pump.is_valid(): pump.call()
if body == "":
model.free()
model = null
return false
model.set("gr2_path", body) # 重:解 gr2 + 建网格 + 扫 3 级 LOD
# This view is built off-tree: set_gr2_path only stores the path until ready.
# Validate the source explicitly rather than reading a not-yet-loaded report.
if String(model.call("probe_gr2", body)).contains("FAILED"):
model.free()
model = null
return false
if pump.is_valid(): pump.call()
var hair := _first_existing([base.path_join("hair/hair_1_1.gr2"), base.path_join("hair/hair_01.gr2")])
if hair != "":
model.set("hair_gr2", hair) # 重:解 hair gr2 + 折进网格