Files
aetherbound-guild/runtime/tests/p10_3/deep_channels_resolution_test.gd
T

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)