extends SceneTree const EntryShell = preload("res://entry/entry_shell.gd") const SettingsStore = preload("res://entry/settings_store.gd") const DraftService = preload("res://entry/initial_company_draft_service.gd") const DRAFT_PATH := "user://hub001_layered_draft.json" const COMPANY_PATH := "user://hub001_layered_company.json" const SEED := "abcdef0123456789abcdef0123456789" const SIZES := [Vector2i(760, 360), Vector2i(844, 390), Vector2i(1280, 720)] var failures: Array[String] = [] func _initialize() -> void: call_deferred("run_test") func run_test() -> void: _cleanup() var company := _create_company() var viewport := SubViewport.new() viewport.size = SIZES[0] viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS root.add_child(viewport) var shell := EntryShell.new() shell.auto_start = false shell.initial_company_service.path = DRAFT_PATH shell.initial_company_service.company_path = COMPANY_PATH viewport.add_child(shell) await _frames(3) var settings := SettingsStore.new().defaults() settings.setup_completed = true settings.locale = "zh_CN" settings.text_scale = 1.3 shell.settings = settings shell.show_guild_handoff(company) await _frames(4) for viewport_size in SIZES: viewport.size = viewport_size await _frames(3) _verify_layered_page(shell, viewport_size) shell.queue_free() await _frames(3) _cleanup() if failures.is_empty(): print("ABG_HUB001_LAYERED_OK sizes=760x360,844x390,1280x720 composite=false layers=world,rails,actors,board fixed_caps=true center_stretch=true touch_min=48 text=engine") quit(0) else: for failure in failures: push_error(failure) quit(1) func _verify_layered_page(shell: Control, viewport_size: Vector2i) -> void: expect(shell.find_child("GuildMasterBComposite", true, false) == null, "%s removes the coordinate-master composite" % viewport_size) for node_name in [ "GuildMasterLayeredWorld", "GuildTopRail", "GuildTopRailLeft", "GuildTopRailCenter", "GuildTopRailRight", "GuildBottomRail", "GuildBottomRailLeft", "GuildBottomRailCenter", "GuildBottomRailRight", "GuildMasterKioskTexture", "GuildMemberPortraitTexture_0", "GuildMemberPortraitTexture_1", "GuildKioskCopyArea", "GuildProgressStatusIcon", "GuildCoinStatusIcon", "GuildAnchorCompanyStatusIcon", ]: expect(shell.find_child(node_name, true, false) != null, "%s has layered node %s" % [viewport_size, node_name]) var top_left := shell.find_child("GuildTopRailLeft", true, false) as TextureRect var top_center := shell.find_child("GuildTopRailCenter", true, false) as TextureRect var top_right := shell.find_child("GuildTopRailRight", true, false) as TextureRect var bottom_left := shell.find_child("GuildBottomRailLeft", true, false) as TextureRect var bottom_center := shell.find_child("GuildBottomRailCenter", true, false) as TextureRect var bottom_right := shell.find_child("GuildBottomRailRight", true, false) as TextureRect if top_left != null and top_center != null and top_right != null and bottom_left != null and bottom_center != null and bottom_right != null: expect(top_left.stretch_mode == TextureRect.STRETCH_KEEP_ASPECT_CENTERED and top_right.stretch_mode == TextureRect.STRETCH_KEEP_ASPECT_CENTERED, "%s top caps keep aspect" % viewport_size) expect(bottom_left.stretch_mode == TextureRect.STRETCH_KEEP_ASPECT_CENTERED and bottom_right.stretch_mode == TextureRect.STRETCH_KEEP_ASPECT_CENTERED, "%s bottom caps keep aspect" % viewport_size) expect(top_center.stretch_mode == TextureRect.STRETCH_SCALE and bottom_center.stretch_mode == TextureRect.STRETCH_SCALE, "%s only clean centers stretch" % viewport_size) expect(top_center.anchor_left <= 0.056 and top_center.anchor_right >= 0.953 and is_equal_approx(top_center.anchor_right, top_right.anchor_left), "%s top center meets both native-width fixed caps" % viewport_size) expect(bottom_center.anchor_right >= 0.90 and bottom_right.anchor_left >= 0.90, "%s bottom stretch center meets the fixed Compass endcap" % viewport_size) for button_name in ["GuildTitle", "GuildRosterButton", "GuildEquipment", "GuildParty", "GuildRisk", "GuildSettings"]: var button := shell.find_child(button_name, true, false) as Button expect(button != null, "%s has %s" % [viewport_size, button_name]) if button != null: var rect := button.get_global_rect() expect(rect.size.x >= 47.9 and rect.size.y >= 47.9, "%s %s meets 48px touch minimum: %s" % [viewport_size, button_name, rect.size]) if button_name in ["GuildRosterButton", "GuildEquipment", "GuildParty", "GuildRisk"]: expect(rect.size.x <= 76.1 and rect.size.y <= 76.1, "%s %s stays inside the 76px responsive cap: %s" % [viewport_size, button_name, rect.size]) var chrome := button.find_child("Chrome", true, false) as Control if button_name == "GuildTitle": expect(chrome == null and String(button.get_meta("art_owned_control", "")) == "GuildTopRailLeft", "%s GuildTitle reuses the approved crest instead of adding a fifth bottom medallion" % viewport_size) else: expect(chrome != null, "%s %s has an authored semantic icon" % [viewport_size, button_name]) if chrome != null: var expected_variant := "guild_status_icon" if button_name == "GuildSettings" else "guild_nav_icon" expect(String(chrome.get("variant")) == expected_variant, "%s %s uses %s instead of shared icon geometry" % [viewport_size, button_name, expected_variant]) var nav_buttons := ["GuildRosterButton", "GuildEquipment", "GuildParty", "GuildRisk"] for index in range(nav_buttons.size() - 1): var left_button := shell.find_child(nav_buttons[index], true, false) as Button var right_button := shell.find_child(nav_buttons[index + 1], true, false) as Button if left_button != null and right_button != null: var spacing := right_button.get_global_rect().position.x - left_button.get_global_rect().end.x expect(spacing >= 3.9 and spacing <= 8.1, "%s bottom nav spacing remains compact: %.2f" % [viewport_size, spacing]) for label_name in ["GuildAnchorTitle", "GuildProgressStatus", "GuildCoinStatus", "GuildAnchorCompanyStatus", "GuildKioskText", "GuildContractReason", "GuildMarketReason"]: expect(shell.find_child(label_name, true, false) is Label, "%s engine-renders %s" % [viewport_size, label_name]) for status_name in ["GuildProgressStatus", "GuildCoinStatus", "GuildAnchorCompanyStatus"]: expect(shell.find_child("%sPill" % status_name, true, false) is Panel, "%s %s uses an inlaid value pill" % [viewport_size, status_name]) expect(shell.find_child("%sFrame" % status_name, true, false) == null, "%s %s has no opaque outer resource card" % [viewport_size, status_name]) var title_plate := shell.find_child("GuildTitlePlate", true, false) as Panel var design_canvas := shell.find_child("GuildMasterDesignCanvas", true, false) as Control if title_plate != null and design_canvas != null: expect(title_plate.get_global_rect().size.x <= design_canvas.size.x * 0.151, "%s Guild identity stays within the approved compact width" % viewport_size) for command_label in ["GuildTitleCommandLabel", "GuildRosterCommandLabel", "GuildEquipmentCommandLabel", "GuildPartyCommandLabel", "GuildRouteStatusLabel"]: var label := shell.find_child(command_label, true, false) as Label expect(label != null and not label.visible, "%s %s stays tooltip-owned instead of overlaying the approved icon rail" % [viewport_size, command_label]) var title := shell.find_child("GuildAnchorTitle", true, false) as Label if title != null: var title_size := title.get_theme_font_size("font_size") expect(title_size >= (19 if viewport_size.y >= 700 else 11), "%s Guild title follows canvas-aware typography: %d" % [viewport_size, title_size]) func _create_company() -> Dictionary: var service := DraftService.new() service.path = DRAFT_PATH service.company_path = COMPANY_PATH var draft: Dictionary = service.begin_or_load(SEED) service.set_selection([String(draft.draft.offers[0].offer_id), String(draft.draft.offers[2].offer_id)]) return service.commit_company().company func _cleanup() -> void: for base in [DRAFT_PATH, COMPANY_PATH]: for suffix in ["", ".bak", ".candidate", ".bak.candidate", ".corrupt"]: var file_path := "%s%s" % [base, suffix] if FileAccess.file_exists(file_path): DirAccess.remove_absolute(file_path) func _frames(count: int) -> void: for frame in range(count): await process_frame func expect(condition: bool, message: String) -> void: if not condition: failures.append("HUB-001 layered reconstruction: %s" % message)