227 lines
9.8 KiB
GDScript
227 lines
9.8 KiB
GDScript
extends SceneTree
|
|
|
|
const Controller = preload("res://p10_2/runtime_controller.gd")
|
|
const BattleService = preload("res://p10_2/mid_contract_battle_service.gd")
|
|
const ResolutionService = preload("res://p10_2/mid_contract_resolution_service.gd")
|
|
const SaveV8 = preload("res://p10_2/save_v8_service.gd")
|
|
const Fixture = preload("res://tests/p10_2/mid_contract_fixture.gd")
|
|
|
|
const VIEWPORTS := [
|
|
Vector2i(760, 360),
|
|
Vector2i(844, 390),
|
|
Vector2i(1280, 720),
|
|
Vector2i(390, 844),
|
|
]
|
|
const LOCALES := ["zh_CN", "en"]
|
|
const TEXT_SCALES := [1.0, 1.3]
|
|
const ACCESSIBILITY := [
|
|
{"reduced": false, "low_power": false},
|
|
{"reduced": true, "low_power": true},
|
|
]
|
|
|
|
var failures: Array[String] = []
|
|
var matrix_cases: Array[Dictionary] = []
|
|
var experience
|
|
var save_cases := 0
|
|
var layout_cases := 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
root.content_scale_mode = Window.CONTENT_SCALE_MODE_CANVAS_ITEMS
|
|
root.content_scale_aspect = Window.CONTENT_SCALE_ASPECT_EXPAND
|
|
matrix_cases = build_matrix_cases()
|
|
verify_save_phase_matrix()
|
|
await verify_layout_matrix()
|
|
if is_instance_valid(experience):
|
|
experience.queue_free()
|
|
await settle_frames(45)
|
|
if failures.is_empty():
|
|
print("ABG_P10_2_CANDIDATE_MATRIX_OK save_cases=%d phases=market2,market3,market4,prepared2,prepared3,prepared4,recovery,recovered,market5 layout_cases=%d states=9 viewports=760x360,844x390,1280x720,390x844 locales=zh_CN,en text=100,130 reduced_motion=true low_power=true mute=true controls=48 panels=0 cards=0 offline_rewards=0" % [save_cases, layout_cases])
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func build_matrix_cases() -> Array[Dictionary]:
|
|
var market_2 = Fixture.new().market_service_at(2)
|
|
var market_3 = Fixture.new().market_service_at(3)
|
|
var market_4 = Fixture.new().market_service_at(4)
|
|
var prepared_2 = Fixture.new().prepared_service(2, 2)
|
|
var prepared_3 = Fixture.new().prepared_service(3, 2)
|
|
var prepared_4 = Fixture.new().prepared_service(4, 2)
|
|
var market_5 = Fixture.new().market_service_at(5)
|
|
for service in [market_2, market_3, market_4, prepared_2, prepared_3, prepared_4, market_5]:
|
|
if service == null:
|
|
failures.append("P10.2 candidate matrix: fixture construction failed")
|
|
return []
|
|
|
|
var battle: Dictionary = BattleService.new(prepared_4.state.snapshot()).run_to_terminal("hold_line")
|
|
if not battle.ok or String(battle.snapshot.outcome) != "defeat":
|
|
failures.append("P10.2 candidate matrix: recovery fixture did not produce the declared defeat")
|
|
return []
|
|
var resolver = ResolutionService.new(prepared_4.state.snapshot())
|
|
var settled: Dictionary = resolver.settle(
|
|
"candidate-matrix-defeat",
|
|
resolver.state.revision,
|
|
battle.snapshot
|
|
)
|
|
if not settled.ok:
|
|
failures.append("P10.2 candidate matrix: recovery fixture settlement rejected")
|
|
return []
|
|
var recovery_snapshot: Dictionary = resolver.state.snapshot()
|
|
var recovered: Dictionary = resolver.recover(
|
|
"candidate-matrix-recover",
|
|
resolver.state.revision,
|
|
String(battle.snapshot.snapshot_hash),
|
|
int(battle.snapshot.recovery_coin)
|
|
)
|
|
if not recovered.ok:
|
|
failures.append("P10.2 candidate matrix: recovered Market fixture rejected")
|
|
return []
|
|
|
|
return [
|
|
matrix_row("market2", market_2.state.snapshot(), "market", 2, Controller.P102_SCREEN_MARKET),
|
|
matrix_row("market3", market_3.state.snapshot(), "market", 3, Controller.P102_SCREEN_MARKET),
|
|
matrix_row("market4", market_4.state.snapshot(), "market", 4, Controller.P102_SCREEN_MARKET),
|
|
matrix_row("prepared2", prepared_2.state.snapshot(), "prepared", 2, Controller.P102_SCREEN_PLAN),
|
|
matrix_row("prepared3", prepared_3.state.snapshot(), "prepared", 3, Controller.P102_SCREEN_PLAN),
|
|
matrix_row("prepared4", prepared_4.state.snapshot(), "prepared", 4, Controller.P102_SCREEN_PLAN),
|
|
matrix_row("recovery", recovery_snapshot, "recovery", 4, Controller.P102_SCREEN_RECOVERY),
|
|
matrix_row("recovered", resolver.state.snapshot(), "market", 4, Controller.P102_SCREEN_MARKET),
|
|
matrix_row("market5", market_5.state.snapshot(), "market_5", 5, Controller.P102_SCREEN_MARKET_5),
|
|
]
|
|
|
|
|
|
func matrix_row(name: String, snapshot: Dictionary, phase: String, market_index: int, screen: String) -> Dictionary:
|
|
return {
|
|
"name": name,
|
|
"snapshot": Dictionary(snapshot).duplicate(true),
|
|
"phase": phase,
|
|
"market_index": market_index,
|
|
"screen": screen,
|
|
}
|
|
|
|
|
|
func verify_save_phase_matrix() -> void:
|
|
expect(matrix_cases.size() == 9, "nine declared Save-v8 states exist")
|
|
for index in range(matrix_cases.size()):
|
|
var row: Dictionary = matrix_cases[index]
|
|
var sequence := 200 + index
|
|
var encoded: Dictionary = SaveV8.new().encode_snapshot(row.snapshot, sequence)
|
|
expect(encoded.ok, "%s Save v8 encodes" % row.name)
|
|
if not encoded.ok:
|
|
continue
|
|
var decoded: Dictionary = SaveV8.new().decode_text(String(encoded.text), sequence)
|
|
expect(decoded.ok and decoded.snapshot == row.snapshot, "%s Save v8 roundtrip is exact" % row.name)
|
|
expect(decoded.offline_delta == SaveV8.OFFLINE_ZERO, "%s reload grants zero offline value" % row.name)
|
|
if decoded.ok:
|
|
expect(String(decoded.snapshot.current_run.phase) == String(row.phase), "%s reload preserves phase" % row.name)
|
|
expect(int(decoded.snapshot.current_run.market_index) == int(row.market_index), "%s reload preserves Market index" % row.name)
|
|
save_cases += 1
|
|
var market_5: Dictionary = matrix_cases[-1].snapshot if not matrix_cases.is_empty() else {}
|
|
if not market_5.is_empty():
|
|
expect(
|
|
bool(market_5.current_run.reed_ferry_restored)
|
|
and int(market_5.current_run.cleared_rounds) == 4
|
|
and int(market_5.current_run.line_capacity) == 6,
|
|
"Market 5 retains the restored Ferry, four clears and capacity six"
|
|
)
|
|
|
|
|
|
func verify_layout_matrix() -> void:
|
|
if matrix_cases.size() != 9:
|
|
return
|
|
var state_index := 0
|
|
var state_hits := {}
|
|
for viewport in VIEWPORTS:
|
|
for locale in LOCALES:
|
|
for text_scale in TEXT_SCALES:
|
|
for accessibility in ACCESSIBILITY:
|
|
var row: Dictionary = matrix_cases[state_index % matrix_cases.size()]
|
|
await mount(
|
|
row.snapshot,
|
|
viewport,
|
|
locale,
|
|
text_scale,
|
|
bool(accessibility.reduced),
|
|
bool(accessibility.low_power)
|
|
)
|
|
verify_layout(row, viewport, locale, text_scale, accessibility)
|
|
state_hits[String(row.name)] = int(state_hits.get(String(row.name), 0)) + 1
|
|
layout_cases += 1
|
|
state_index += 1
|
|
expect(layout_cases == 32, "layout matrix covers all 32 environment combinations")
|
|
expect(state_hits.size() == 9, "layout matrix renders every declared runtime state")
|
|
|
|
|
|
func mount(snapshot: Dictionary, viewport: Vector2i, locale: String, text_scale: float, reduced: bool, low_power: bool) -> void:
|
|
if is_instance_valid(experience):
|
|
experience.queue_free()
|
|
await settle_frames(12)
|
|
root.size = viewport
|
|
root.content_scale_size = viewport
|
|
experience = Controller.new()
|
|
experience.persistence_enabled = false
|
|
experience.battle_auto_advance = false
|
|
experience.locale = locale
|
|
experience.text_scale = text_scale
|
|
experience.reduced_motion = reduced
|
|
experience.muted = true
|
|
experience.low_power = low_power
|
|
experience.size = Vector2(viewport)
|
|
experience.configure_entry(Dictionary(snapshot).duplicate(true))
|
|
root.add_child(experience)
|
|
await settle_frames(8)
|
|
|
|
|
|
func verify_layout(row: Dictionary, viewport: Vector2i, locale: String, text_scale: float, accessibility: Dictionary) -> void:
|
|
var label := "%s/%sx%s/%s/%s" % [row.name, viewport.x, viewport.y, locale, text_scale]
|
|
expect(experience.screen == String(row.screen), "%s restores the expected screen" % label)
|
|
expect(experience.locale == locale and is_equal_approx(experience.text_scale, text_scale), "%s preserves locale and text scale" % label)
|
|
expect(experience.reduced_motion == bool(accessibility.reduced), "%s preserves reduced-motion state" % label)
|
|
expect(experience.low_power == bool(accessibility.low_power) and experience.muted, "%s preserves low-power and mute state" % label)
|
|
if viewport.y > viewport.x:
|
|
expect(experience.find_child("P97RotateNotice", true, false) != null, "%s shows landscape guidance" % label)
|
|
return
|
|
|
|
var bounds := Rect2(Vector2.ZERO, Vector2(viewport))
|
|
expect(experience.find_child("P97RotateNotice", true, false) == null, "%s renders the landscape world" % label)
|
|
expect(float(experience.get_meta("visible_world_ratio", 0.0)) >= 0.80, "%s remains world-first" % label)
|
|
expect(experience.find_children("*", "Panel", true, false).is_empty(), "%s adds no app Panel surfaces" % label)
|
|
for node in experience.find_children("*", "Control", true, false):
|
|
var control := node as Control
|
|
if not control.visible or control.is_queued_for_deletion():
|
|
continue
|
|
var lowered := String(control.name).to_lower()
|
|
expect("card" not in lowered and "modal" not in lowered and "dialog" not in lowered, "%s adds no card/modal surface: %s" % [label, control.name])
|
|
expect(is_finite(control.position.x) and is_finite(control.position.y), "%s control %s has finite geometry" % [label, control.name])
|
|
if control is Button:
|
|
expect(control.size.x >= 48.0 and control.size.y >= 48.0, "%s target %s is at least 48px" % [label, control.name])
|
|
expect(bounds.encloses(control.get_global_rect()), "%s target %s stays inside the viewport" % [label, control.name])
|
|
|
|
match String(row.phase):
|
|
"market":
|
|
expect(experience.find_children("P102RouteTarget_*", "Button", true, false).size() == 3, "%s keeps three direct route targets" % label)
|
|
"prepared":
|
|
expect(experience.find_children("P102PlanTarget_*", "Button", true, false).size() == 4, "%s keeps four direct plan targets" % label)
|
|
"recovery":
|
|
expect(experience.find_child("P102RecoverButton", true, false) != null, "%s keeps the recovery command visible" % label)
|
|
"market_5":
|
|
expect(experience.find_child("P102ReedFerryRestored", true, false) != null, "%s keeps the restored Ferry visible" % label)
|
|
|
|
|
|
func settle_frames(count := 5) -> void:
|
|
for _index in count:
|
|
await process_frame
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("P10.2 candidate matrix: %s" % message)
|