Files
aetherbound-guild/runtime/tools/m55_readiness_result_visual_probe.gd
T

111 lines
5.8 KiB
GDScript

extends SceneTree
const Readiness = preload("res://entry/views/readiness_page_view.gd")
const InkCheck = preload("res://tools/ui_ink_check.gd")
# Single authoritative size (owner policy).
const SIZES := [Vector2i(1280, 720)]
const LOCALES := ["zh_CN", "en"]
var ink_scanned := 0
var ink_violations: Array = []
func _initialize() -> void: call_deferred("_run")
func _run() -> void:
var out := OS.get_environment("ABG_EVIDENCE_DIR")
if out.is_empty(): quit(1); return
DirAccess.make_dir_recursive_absolute(out)
var ink_self := InkCheck.self_test(root)
if ink_self.size() < 3:
push_error("M62 ink self-test expected at least 3 violations, got %d" % ink_self.size())
quit(1)
return
print("ABG_M62_INK_SELF_TEST_OK violations=%d" % ink_self.size())
ink_scanned = 0
ink_violations = []
for size in SIZES:
for locale in LOCALES:
for mode in ["static", "dynamic", "final"]:
await _capture(Readiness, _readiness_data(locale), size, locale, "readiness", out, mode)
var report_path := OS.get_environment("ABG_EVIDENCE_DIR").path_join("ink-violations-%s.json" % _probe_id())
var file := FileAccess.open(report_path, FileAccess.WRITE)
if file != null:
file.store_string(JSON.stringify(ink_violations, " "))
file.close()
if ink_violations.is_empty():
print("ABG_M55_READINESS_INK_OK scanned=%d violations=0" % ink_scanned)
else:
for violation in ink_violations:
push_error("M55 ink violation %s %s %s %s: %s" % [
violation.get("node", "?"), violation.get("locale", "?"),
violation.get("fixture", "?"), violation.get("kind", "?"),
violation.get("detail", ""),
])
print("ABG_M55_READINESS_INK_FAIL scanned=%d violations=%d" % [ink_scanned, ink_violations.size()])
quit(1)
return
print("ABG_M55_READINESS_VISUAL_PROBE_OK sizes=1 locales=2 pages=readiness scans=%d strict_result=m67" % ink_scanned)
quit(0)
func _probe_id() -> String:
return "m55-readiness-result"
func _scan_ink(view: Control, locale: String, label: String) -> void:
# Legacy-chrome pages (pre-M62) record their ink defects as a baseline for
# the W5 migration; migrated pages (Market, Readiness once moved) must be
# strict. The report file is the machine-readable work list.
var violations := InkCheck.scan(view)
ink_scanned += 1
for violation in violations:
violation["locale"] = locale
violation["fixture"] = label
ink_violations.append(violation)
func _capture(script: Script, data: Dictionary, size: Vector2i, locale: String, page: String, out: String, mode := "final") -> void:
var viewport := SubViewport.new(); viewport.size = size; viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS; root.add_child(viewport)
var view: Control = script.new(); view.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT); view.configure_presentation({"safe_area":24,"text_scale":1.0,"locale":locale}); viewport.add_child(view); view.render(data)
if mode != "final": _apply_capture_mode(view, mode)
for _i in range(3): await process_frame
_scan_ink(view, locale, "%s-%dx%d-%s" % [page, size.x, size.y, mode])
var image := viewport.get_texture().get_image(); if image != null: image.save_png(out.path_join("%s-%dx%d-%s-%s.png" % [page,size.x,size.y,locale,mode]))
view.queue_free(); viewport.queue_free(); await process_frame
func _apply_capture_mode(view: Control, mode: String) -> void:
# Contract views: static-only is the authored master with no runtime
# content; dynamic-only is the runtime content on a neutral field, which is
# where slot overflow, contrast and alignment defects are visible. This
# lives in the probe so product code carries no test-only switches.
var overlay := view.find_child("ReadinessMasterOverlay", true, false)
var master := view.find_child("ReadinessStaticMaster", true, false)
var matte := view.find_child("ReadinessMasterMatte", true, false)
if mode == "static":
if overlay != null: overlay.visible = false
return
if matte != null: matte.visible = false
if master != null: master.visible = false
if master != null and master.get_parent() != null:
var neutral := ColorRect.new()
neutral.name = "CaptureNeutralField"
neutral.color = Color("#5b6165")
neutral.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
neutral.mouse_filter = Control.MOUSE_FILTER_IGNORE
master.get_parent().add_child(neutral)
master.get_parent().move_child(neutral, 0)
func _readiness_data(locale: String) -> Dictionary:
# Real profession and localized-name data: the probe must exercise four
# distinct members, not four copies of the fallback art. Empty-data probes
# cannot validate the party layout.
var professions := ["PF-B01", "PF-B05", "PF-B07", "PF-B03"]
var zh_names := ["阿里", "布兰", "塞拉", "戴恩"]
var en_names := ["Ari", "Bram", "Cera", "Dain"]
var rows: Array = []
for i in range(4):
rows.append({
"position": i,
"recruit_id": "r-%d" % (i + 1),
"recruit": {
"recruit_id": "r-%d" % (i + 1),
"profession_id": professions[i],
"rendered_name_snapshot": {"zh_CN": zh_names[i], "en": en_names[i]},
},
})
return {"ok":true,"locale":locale,"company":{"owned_count":4,"line_capacity":4},"contract":{"difficulty":"stormbound","line_capacity":4},"formation":{"coverage":{"protection":2,"long_reach":2,"control":1}},"order":{"front_recruit":rows[0].recruit,"ordered_recruits":rows},"warnings":{"rows":[{"text":"NO GUARD","severity":"warning","owner":"coverage"},{"text":"Outcome not guaranteed","severity":"notice","owner":"risk"},{"text":"Reserve: none","severity":"info","owner":"formation"}],"count":3},"commands":{"accept_enabled":true,"risk_enabled":false}}
func _result_data(locale: String) -> Dictionary:
return {"ok":true,"locale":locale,"result":{"outcome":"victory"},"economy":{"coin_before":24,"coin_after":38,"reward_coin":14,"xp_award":20,"xp_per_recruit":5},"knockouts":{"ids":[]},"decisive_event":{"caption_id":"first_hit"},"diagnosis":{"open":true},"commands":{"diagnosis_enabled":true,"continue_enabled":true}}