303 lines
13 KiB
GDScript
303 lines
13 KiB
GDScript
extends SceneTree
|
|
|
|
const BattleService = preload("res://p10_3/deep_channels_battle_service.gd")
|
|
const Controller = preload("res://p10_3/runtime_controller.gd")
|
|
const GrowthService = preload("res://p10_3/growth_service.gd")
|
|
const PreparationService = preload("res://p10_3/deep_channels_preparation_service.gd")
|
|
const ResolutionService = preload("res://p10_3/deep_channels_resolution_service.gd")
|
|
const SaveV9 = preload("res://p10_3/save_v9_service.gd")
|
|
const Fixture = preload("res://tests/p10_3/deep_channels_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_3_CANDIDATE_MATRIX_OK save_cases=%d phases=market5,prepared5,market6,prepared6,artifact,artifact_deferred,promotion,promotion_deferred,prepared7,recovery,recovered,market8 layout_cases=%d states=12 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_5 := Fixture.new().migrated_snapshot()
|
|
var prepared_5 = Fixture.new().prepared_service(5, 0)
|
|
var market_6 := Fixture.new().progressed_market_snapshot(6)
|
|
var prepared_6 = Fixture.new().prepared_service(6, 1)
|
|
var artifact := Fixture.new().market_7_snapshot(true)
|
|
var artifact_deferred := artifact_deferred_snapshot(artifact)
|
|
var promotion := promotion_snapshot(artifact, false)
|
|
var promotion_deferred := promotion_snapshot(artifact, true)
|
|
var prepared_7 = Fixture.new().prepared_service(7, 2, "AR-012", "PF-A13", true)
|
|
var recovery_pair := recovery_snapshots(prepared_7)
|
|
var market_8 := market_eight_snapshot()
|
|
if (
|
|
market_5.is_empty()
|
|
or prepared_5 == null
|
|
or market_6.is_empty()
|
|
or prepared_6 == null
|
|
or artifact.is_empty()
|
|
or artifact_deferred.is_empty()
|
|
or promotion.is_empty()
|
|
or promotion_deferred.is_empty()
|
|
or prepared_7 == null
|
|
or recovery_pair.is_empty()
|
|
or market_8.is_empty()
|
|
):
|
|
failures.append("P10.3 candidate matrix: fixture construction failed")
|
|
return []
|
|
return [
|
|
matrix_row("market5", market_5, Controller.P103_SCREEN_MARKET),
|
|
matrix_row("prepared5", prepared_5.state.snapshot(), Controller.P103_SCREEN_PLAN),
|
|
matrix_row("market6", market_6, Controller.P103_SCREEN_MARKET),
|
|
matrix_row("prepared6", prepared_6.state.snapshot(), Controller.P103_SCREEN_PLAN),
|
|
matrix_row("artifact", artifact, Controller.P103_SCREEN_ARTIFACT),
|
|
matrix_row("artifact_deferred", artifact_deferred, Controller.P103_SCREEN_MARKET),
|
|
matrix_row("promotion", promotion, Controller.P103_SCREEN_PROMOTION),
|
|
matrix_row("promotion_deferred", promotion_deferred, Controller.P103_SCREEN_MARKET),
|
|
matrix_row("prepared7", prepared_7.state.snapshot(), Controller.P103_SCREEN_PLAN),
|
|
matrix_row("recovery", recovery_pair.recovery, Controller.P103_SCREEN_RECOVERY),
|
|
matrix_row("recovered", recovery_pair.recovered, Controller.P103_SCREEN_MARKET),
|
|
matrix_row("market8", market_8, Controller.P103_SCREEN_MARKET_8),
|
|
]
|
|
|
|
|
|
func matrix_row(name: String, snapshot_value: Dictionary, screen: String) -> Dictionary:
|
|
return {
|
|
"name": name,
|
|
"snapshot": Dictionary(snapshot_value).duplicate(true),
|
|
"phase": String(snapshot_value.current_run.phase),
|
|
"market_index": int(snapshot_value.current_run.market_index),
|
|
"screen": screen,
|
|
}
|
|
|
|
|
|
func artifact_deferred_snapshot(snapshot_value: Dictionary) -> Dictionary:
|
|
var growth := GrowthService.new(snapshot_value)
|
|
var result := growth.defer_artifact(
|
|
"candidate-artifact-defer",
|
|
growth.state.revision,
|
|
String(growth.state.current_run.artifact_draft.draft_hash)
|
|
)
|
|
return growth.state.snapshot() if result.ok else {}
|
|
|
|
|
|
func promotion_snapshot(snapshot_value: Dictionary, deferred: bool) -> Dictionary:
|
|
var growth := GrowthService.new(snapshot_value)
|
|
var selected := growth.select_artifact(
|
|
"candidate-promotion-artifact-%s" % deferred,
|
|
growth.state.revision,
|
|
String(growth.state.current_run.artifact_draft.draft_hash),
|
|
"AR-012"
|
|
)
|
|
if not selected.ok:
|
|
return {}
|
|
var opened := growth.open_promotion_draft(
|
|
"candidate-promotion-open-%s" % deferred,
|
|
growth.state.revision
|
|
)
|
|
if not opened.ok:
|
|
return {}
|
|
if deferred:
|
|
var result := growth.defer_promotion(
|
|
"candidate-promotion-defer",
|
|
growth.state.revision,
|
|
String(growth.state.current_run.promotion_draft.draft_hash)
|
|
)
|
|
if not result.ok:
|
|
return {}
|
|
return growth.state.snapshot()
|
|
|
|
|
|
func recovery_snapshots(prepared) -> Dictionary:
|
|
if prepared == null:
|
|
return {}
|
|
var route_id := String(prepared.state.current_run.pending_encounter.route_id)
|
|
var battle := BattleService.new(prepared.state.snapshot()).run_to_terminal("focus_source")
|
|
if not battle.ok or String(battle.snapshot.outcome) != "defeat":
|
|
return {}
|
|
var resolver := ResolutionService.new(prepared.state.snapshot())
|
|
var settled := resolver.settle("candidate-recovery", resolver.state.revision, battle.snapshot)
|
|
if not settled.ok:
|
|
return {}
|
|
var recovery := resolver.state.snapshot()
|
|
var recovered := resolver.recover(
|
|
"candidate-recovered",
|
|
resolver.state.revision,
|
|
String(battle.snapshot.snapshot_hash),
|
|
int(battle.snapshot.recovery_coin)
|
|
)
|
|
if not recovered.ok or route_id != "RO-R2-M07-Q2":
|
|
return {}
|
|
return {"recovery": recovery, "recovered": resolver.state.snapshot()}
|
|
|
|
|
|
func market_eight_snapshot() -> Dictionary:
|
|
var prepared = Fixture.new().prepared_service(7, 2, "AR-012", "PF-A13", true)
|
|
if prepared == null:
|
|
return {}
|
|
var route_id := String(prepared.state.current_run.pending_encounter.route_id)
|
|
var battle := BattleService.new(prepared.state.snapshot()).run_to_terminal(String(BattleService.PREFERRED_PLAN[route_id]))
|
|
if not battle.ok or String(battle.snapshot.outcome) != "victory":
|
|
return {}
|
|
var resolver := ResolutionService.new(prepared.state.snapshot())
|
|
var settled := resolver.settle("candidate-market8", resolver.state.revision, battle.snapshot)
|
|
return resolver.state.snapshot() if settled.ok else {}
|
|
|
|
|
|
func verify_save_phase_matrix() -> void:
|
|
expect(matrix_cases.size() == 12, "twelve declared Save-v9 states exist")
|
|
for index in range(matrix_cases.size()):
|
|
var row: Dictionary = matrix_cases[index]
|
|
var sequence := 500 + index
|
|
var encoded := SaveV9.new().encode_snapshot(row.snapshot, sequence)
|
|
expect(encoded.ok, "%s Save v9 encodes" % row.name)
|
|
if not encoded.ok:
|
|
continue
|
|
var decoded := SaveV9.new().decode_text(String(encoded.text), sequence)
|
|
expect(decoded.ok and decoded.snapshot == row.snapshot, "%s Save v9 roundtrip is exact" % row.name)
|
|
expect(decoded.offline_delta == SaveV9.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 final_state: Dictionary = matrix_cases[-1].snapshot if not matrix_cases.is_empty() else {}
|
|
if not final_state.is_empty():
|
|
expect(
|
|
String(final_state.current_run.phase) == "market_8"
|
|
and int(final_state.current_run.cleared_rounds) == 7
|
|
and int(final_state.current_run.line_capacity) == 7
|
|
and final_state.current_run.active_artifact_ids == ["AR-012"],
|
|
"Market 8 retains seven clears, capacity seven and the selected Artifact"
|
|
)
|
|
|
|
|
|
func verify_layout_matrix() -> void:
|
|
if matrix_cases.size() != 12:
|
|
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() == 12, "layout matrix renders every declared runtime state")
|
|
|
|
|
|
func mount(snapshot_value: 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_value).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.screen):
|
|
Controller.P103_SCREEN_MARKET:
|
|
expect(experience.find_children("P102RouteTarget_*", "Button", true, false).size() == 3, "%s keeps three direct route targets" % label)
|
|
Controller.P103_SCREEN_ARTIFACT:
|
|
expect(experience.find_children("P103ArtifactTarget_*", "Button", true, false).size() == 3, "%s keeps three Artifact targets" % label)
|
|
expect(experience.find_child("P103ArtifactDeferButton", true, false) != null, "%s keeps Artifact defer visible" % label)
|
|
Controller.P103_SCREEN_PROMOTION:
|
|
expect(experience.find_children("P103PromotionTarget_*", "Button", true, false).size() == 2, "%s keeps two promotion targets" % label)
|
|
expect(experience.find_child("P103PromotionDeferButton", true, false) != null, "%s keeps promotion defer visible" % label)
|
|
Controller.P103_SCREEN_PLAN:
|
|
expect(experience.find_children("P102PlanTarget_*", "Button", true, false).size() == 4, "%s keeps four direct plan targets" % label)
|
|
Controller.P103_SCREEN_RECOVERY:
|
|
expect(experience.find_child("P102RecoverButton", true, false) != null, "%s keeps recovery visible" % label)
|
|
Controller.P103_SCREEN_MARKET_8:
|
|
expect(experience.find_child("P103DeepFerry", true, false) != null, "%s keeps the transformed ferry visible" % label)
|
|
expect(experience.find_child("P103ArtifactShrine", true, false) != null and experience.find_child("P103PromotionAnvil", true, false) != null, "%s keeps both growth workspaces 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.3 candidate matrix: %s" % message)
|