234 lines
9.3 KiB
GDScript
234 lines
9.3 KiB
GDScript
extends SceneTree
|
|
|
|
const BattleService = preload("res://p10_3/deep_channels_battle_service.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")
|
|
|
|
var failures: Array[String] = []
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("run_test")
|
|
|
|
|
|
func run_test() -> void:
|
|
verify_three_round_progression()
|
|
verify_defeat_casualty_and_recovery()
|
|
verify_atomic_boundaries()
|
|
if failures.is_empty():
|
|
print("ABG_P10_3_DEEP_CHANNELS_RESOLUTION_OK rounds=5,6,7 rewards=30,39,48 xp=20,26,32 capacity=6,7,7 markets=5,6,7,8 artifact=AR-012 promotion=PF-A13 casualty=permanent recovery=bounded save_v9=roundtrip duplicate=true")
|
|
quit(0)
|
|
else:
|
|
for failure in failures:
|
|
push_error(failure)
|
|
quit(1)
|
|
|
|
|
|
func verify_three_round_progression() -> void:
|
|
var snapshot_value := Fixture.new().progressed_market_snapshot(5)
|
|
expect(not snapshot_value.is_empty(), "progression starts at stable Market 5")
|
|
if snapshot_value.is_empty():
|
|
return
|
|
var starting_coin := int(snapshot_value.current_run.coin)
|
|
var starting_company: Array = Array(snapshot_value.current_run.company).duplicate()
|
|
var expected := {
|
|
5: {"risk": 0, "reward": 30, "xp": 20, "next_market": 6, "capacity": 6},
|
|
6: {"risk": 1, "reward": 39, "xp": 26, "next_market": 7, "capacity": 7},
|
|
7: {"risk": 2, "reward": 48, "xp": 32, "next_market": 8, "capacity": 7},
|
|
}
|
|
var promoted_recruit_id := ""
|
|
for market_index in [5, 6, 7]:
|
|
if market_index == 7:
|
|
var growth := GrowthService.new(snapshot_value)
|
|
var artifact_hash := String(growth.state.current_run.artifact_draft.draft_hash)
|
|
var selected := growth.select_artifact(
|
|
"progress-artifact",
|
|
growth.state.revision,
|
|
artifact_hash,
|
|
"AR-012"
|
|
)
|
|
expect(selected.ok, "Round 6 settlement opens exact Artifact selection")
|
|
var opened := growth.open_promotion_draft(
|
|
"progress-promotion-open",
|
|
growth.state.revision
|
|
)
|
|
expect(opened.ok, "real Round-5/6 growth opens the first promotion")
|
|
if not opened.ok:
|
|
return
|
|
var draft: Dictionary = opened.receipt.draft
|
|
promoted_recruit_id = String(draft.recruit_id)
|
|
var promoted := growth.confirm_promotion(
|
|
"progress-promotion-confirm",
|
|
growth.state.revision,
|
|
String(draft.draft_hash),
|
|
promoted_recruit_id,
|
|
String(draft.current_profession_id),
|
|
"PF-A13"
|
|
)
|
|
expect(promoted.ok, "Fieldmender promotion commits before Round 7")
|
|
snapshot_value = growth.state.snapshot()
|
|
var row: Dictionary = expected[market_index]
|
|
var preparation := PreparationService.new(snapshot_value)
|
|
var route: Dictionary = preparation.state.current_run.market_board.battle_offers[int(row.risk)]
|
|
var prepared := preparation.commit_route(
|
|
"progress-route-%d" % market_index,
|
|
preparation.state.revision,
|
|
String(route.offer_id),
|
|
String(preparation.state.current_run.market_board.board_hash)
|
|
)
|
|
expect(prepared.ok, "Round %d route prepares" % market_index)
|
|
if not prepared.ok:
|
|
return
|
|
var plan_id := String(BattleService.PREFERRED_PLAN[String(route.route_id)])
|
|
var battle := BattleService.new(preparation.state.snapshot()).run_to_terminal(plan_id)
|
|
expect(
|
|
battle.ok and battle.snapshot.outcome == "victory",
|
|
"Round %d preferred response wins" % market_index
|
|
)
|
|
var resolution := ResolutionService.new(preparation.state.snapshot())
|
|
var revision_before := int(resolution.state.revision)
|
|
var settled := resolution.settle(
|
|
"progress-settle-%d" % market_index,
|
|
revision_before,
|
|
battle.snapshot
|
|
)
|
|
expect(
|
|
settled.ok and resolution.state.revision == revision_before + 1,
|
|
"Round %d settlement applies once" % market_index
|
|
)
|
|
if not settled.ok:
|
|
return
|
|
expect(
|
|
settled.receipt.reward_coin == row.reward and settled.receipt.xp_award == row.xp,
|
|
"Round %d reward and XP are exact" % market_index
|
|
)
|
|
expect(
|
|
resolution.state.current_run.market_index == row.next_market
|
|
and resolution.state.current_run.line_capacity == row.capacity,
|
|
"Round %d opens Market %d at capacity %d" % [market_index, row.next_market, row.capacity]
|
|
)
|
|
var after := resolution.state.snapshot()
|
|
var duplicate := resolution.settle(
|
|
"progress-settle-%d" % market_index,
|
|
revision_before,
|
|
battle.snapshot
|
|
)
|
|
expect(
|
|
duplicate.ok and duplicate.duplicate and resolution.state.snapshot() == after,
|
|
"Round %d settlement retry is idempotent" % market_index
|
|
)
|
|
snapshot_value = resolution.state.snapshot()
|
|
|
|
var run: Dictionary = snapshot_value.current_run
|
|
expect(run.phase == "market_8" and run.market_index == 8, "Round 7 reaches stable Market 8")
|
|
expect(run.company == starting_company, "three victories preserve the complete Company")
|
|
expect(run.coin == starting_coin + 117, "three victories grant exact total Coin")
|
|
expect(run.route_history.size() == 7 and run.cleared_rounds == 7, "route history advances to seven exactly")
|
|
expect(run.active_artifact_ids == ["AR-012"], "Market 8 preserves the selected Artifact")
|
|
expect(
|
|
String(run.recruit_records[promoted_recruit_id].profession_id) == "PF-A13",
|
|
"Market 8 preserves the committed promotion"
|
|
)
|
|
expect(
|
|
run.market_board.handoff_kind == "p10.3.market_8_continuation"
|
|
and String(run.market_board.board_hash).length() == 64,
|
|
"Market 8 is an explicit source-bound continuation"
|
|
)
|
|
var encoded := SaveV9.new().encode_snapshot(snapshot_value, 40)
|
|
var decoded := SaveV9.new().decode_text(String(encoded.text), 40)
|
|
expect(encoded.ok and decoded.ok and decoded.snapshot == snapshot_value, "Market 8 roundtrips through Save v9")
|
|
|
|
|
|
func verify_defeat_casualty_and_recovery() -> void:
|
|
var preparation = Fixture.new().prepared_service(7, 2, "AR-012")
|
|
expect(preparation != null, "defeat fixture prepares Market 7 elite route")
|
|
if preparation == null:
|
|
return
|
|
var before: Dictionary = preparation.state.snapshot()
|
|
var before_company: Array = Array(before.current_run.company).duplicate()
|
|
var before_coin := int(before.current_run.coin)
|
|
var battle := BattleService.new(before).run_to_terminal("focus_source")
|
|
expect(
|
|
battle.ok and battle.snapshot.outcome == "defeat"
|
|
and battle.snapshot.fallen_recruit_ids.size() == 1,
|
|
"wrong deep-channel response defeats with one casualty"
|
|
)
|
|
var fallen_id := String(battle.snapshot.fallen_recruit_ids[0])
|
|
var resolution := ResolutionService.new(before)
|
|
var settled := resolution.settle(
|
|
"defeat-settle",
|
|
resolution.state.revision,
|
|
battle.snapshot
|
|
)
|
|
expect(settled.ok and resolution.state.current_run.phase == "recovery", "defeat enters explicit recovery")
|
|
expect(
|
|
fallen_id not in resolution.state.current_run.company
|
|
and fallen_id not in resolution.state.current_run.recruit_records,
|
|
"Fallen Recruit is permanently removed"
|
|
)
|
|
expect(
|
|
resolution.state.current_run.replacement_credit > 0
|
|
and resolution.state.current_run.recruits_fallen_total == 1,
|
|
"casualty grants bounded Credit and increments loss count"
|
|
)
|
|
expect(
|
|
resolution.state.current_run.coin == before_coin
|
|
and resolution.state.current_run.cleared_rounds == 6,
|
|
"defeat grants no Coin or clear"
|
|
)
|
|
expect(
|
|
resolution.state.current_run.active_artifact_ids == ["AR-012"],
|
|
"defeat preserves the selected run Artifact"
|
|
)
|
|
var recovery_save := SaveV9.new().encode_snapshot(resolution.state.snapshot(), 41)
|
|
var recovery_decoded := SaveV9.new().decode_text(String(recovery_save.text), 41)
|
|
expect(recovery_save.ok and recovery_decoded.ok, "recovery state roundtrips through Save v9")
|
|
var recovered := resolution.recover(
|
|
"defeat-recover",
|
|
resolution.state.revision,
|
|
String(battle.snapshot.snapshot_hash),
|
|
15
|
|
)
|
|
expect(recovered.ok and resolution.state.current_run.phase == "market", "bounded recovery returns to Market 7")
|
|
expect(
|
|
resolution.state.current_run.company.size() == before_company.size() - 1
|
|
and fallen_id not in resolution.state.current_run.company,
|
|
"recovery never resurrects or substitutes the Fallen Recruit"
|
|
)
|
|
expect(
|
|
resolution.state.current_run.coin == before_coin - 15
|
|
and resolution.state.battle_checkpoints.is_empty(),
|
|
"recovery charges exact cost and clears the checkpoint"
|
|
)
|
|
|
|
|
|
func verify_atomic_boundaries() -> void:
|
|
var preparation = Fixture.new().prepared_service(5, 0)
|
|
var route_id := String(preparation.state.current_run.pending_encounter.route_id)
|
|
var battle := BattleService.new(preparation.state.snapshot()).run_to_terminal(
|
|
String(BattleService.PREFERRED_PLAN[route_id])
|
|
)
|
|
var resolution := ResolutionService.new(preparation.state.snapshot())
|
|
var before := resolution.state.snapshot()
|
|
var stale := resolution.settle("stale-settle", resolution.state.revision + 1, battle.snapshot)
|
|
expect(
|
|
not stale.ok and stale.code == "stale_revision" and resolution.state.snapshot() == before,
|
|
"stale settlement rejects without mutation"
|
|
)
|
|
var tampered: Dictionary = battle.snapshot.duplicate(true)
|
|
tampered.reward_coin = 999
|
|
var rejected := resolution.settle("tampered-settle", resolution.state.revision, tampered)
|
|
expect(
|
|
not rejected.ok and rejected.code == "battle_snapshot_mismatch"
|
|
and resolution.state.snapshot() == before,
|
|
"tampered battle snapshot rejects against deterministic replay"
|
|
)
|
|
|
|
|
|
func expect(condition: bool, message: String) -> void:
|
|
if not condition:
|
|
failures.append("P10.3 deep-channel resolution: %s" % message)
|