extends SceneTree const AppServices = preload("res://foundation/app_services.gd") const ScreenContext = preload("res://foundation/screen_context.gd") const ScreenStack = preload("res://foundation/screen_stack.gd") const Tokens = preload("res://foundation/ui_tokens.gd") const SharedButton = preload("res://ui/shared/shared_button.gd") var failures: Array[String] = [] func _initialize() -> void: call_deferred("run_test") func run_test() -> void: verify_context_round_trip() verify_screen_stack() verify_service_bundle() await verify_shared_ui_contract() if failures.is_empty(): print("ABG_M15_FOUNDATION_OK context=true stack=true services=true ui_tokens=true shared_button=true") quit(0) else: for failure in failures: push_error(failure) quit(1) func verify_context_round_trip() -> void: var context := ScreenContext.new({ "screen_id": "settings", "caller_id": "battle_pause_controls", "return_screen_id": "battle", "return_focus_path": "BattleLive/Settings", "presentation_mode": "overlay", "metadata": {"locale": "zh_CN", "operation_id": "op-1"}, }) expect(context.is_overlay(), "overlay context reports its presentation mode") var restored := ScreenContext.new(context.to_dictionary()) expect(restored.screen_id == "settings" and restored.return_screen_id == "battle", "context preserves route identity") expect(restored.return_focus_path == "BattleLive/Settings" and restored.metadata.locale == "zh_CN", "context preserves focus and metadata") func verify_screen_stack() -> void: var stack = ScreenStack.new() stack.push(ScreenContext.new({"screen_id": "title"})) stack.push(ScreenContext.new({"screen_id": "settings", "caller_id": "title"})) expect(stack.depth() == 2 and stack.contains("settings"), "stack pushes independent screen contexts") var current = stack.current() expect(current != null and current.screen_id == "settings", "stack returns the top context") stack.replace(ScreenContext.new({"screen_id": "credits", "caller_id": "settings"})) expect(stack.depth() == 2 and stack.current().screen_id == "credits", "stack replaces only the top context") var popped = stack.pop() expect(popped != null and popped.screen_id == "credits" and stack.current().screen_id == "title", "stack pops to the previous route") stack.clear() expect(stack.depth() == 0 and stack.current() == null, "stack clears without leaving stale context") func verify_service_bundle() -> void: var sentinel := RefCounted.new() var services := AppServices.new({"settings": sentinel}) expect(services.has_service(&"settings") and services.resolve_service(&"settings") == sentinel, "service bundle registers explicit dependencies") services.register_service(&"audio", sentinel) expect(services.has_service(&"audio") and services.service_snapshot().screen_stack, "service bundle exposes screen stack and services") services.register_service(&"audio", null) expect(not services.has_service(&"audio"), "service bundle removes a cleared dependency") func verify_shared_ui_contract() -> void: expect(Tokens.touch_size(Vector2(10, 60)) == Vector2(48, 60), "shared touch token enforces the minimum target") expect(Tokens.font_size("body", 1.0) == 16 and Tokens.font_size("body", 1.3) >= 20, "shared typography token scales predictably") var button := SharedButton.new() button.configure("Apply", "primary") button.set_view_state(true, true, false) root.add_child(button) await process_frame expect(button.semantic_variant == "primary" and button.selected_state and not button.disabled, "shared button exposes semantic view state") expect(button.custom_minimum_size.x >= 48.0 and button.custom_minimum_size.y >= 48.0, "shared button keeps the touch target") button.queue_free() await process_frame func expect(condition: bool, message: String) -> void: if not condition: failures.append("M15 Foundation: %s" % message)