extends SceneTree ## godot --headless --path . --script res://tests/ui/test_pixel_tower.gd ## Camera coverage uses actual Runtime construction and World occupants: a ## misplaced pointer must not select the next cell, another floor, or BACK. var _checks := 0 var _failures := 0 var _clock: Variant var _state: Variant var _runtime: Variant var _preview: WorldPreview var _last_facility: StringName = &"" var _last_construction: StringName = &"" var _last_cell: Variant = null func _initialize() -> void: call_deferred("_run") func _run() -> void: _clock = root.get_node("GameClock") _state = root.get_node("GameState") _runtime = root.get_node("SimulationRuntime") _clock.reset(1, 6, 0) _state.reset() _runtime.initialize(_clock, _state, 724242) _clock.set_paused(true) root.size = Vector2i(1280, 720) _preview = WorldPreview.new() _preview.size = Vector2(1100, 600) _preview.set_runtime(_runtime) var pixel_theme := load("res://assets/themes/japanese_fantasy/visual-theme-v1.tres") as VisualThemeProfile _expect(pixel_theme != null and pixel_theme.pixel_dungeon_tiles != null and pixel_theme.pixel_town_tiles != null, "serialized theme loads both real Kenney atlases for actors and scenery") if pixel_theme != null: _preview.set_visual_theme(pixel_theme) if pixel_theme.pixel_dungeon_tiles != null: _expect(pixel_theme.pixel_dungeon_tiles.get_size() == Vector2(192, 176), "packed actor atlas matches the 12-column source regions") root.add_child(_preview) _preview.set_process(false) _preview.facility_selected.connect(_on_facility_selected) _preview.construction_selected.connect(_on_construction_selected) _preview.cell_clicked.connect(_on_cell_clicked) await process_frame await process_frame _test_zoom_anchor_and_reset() _test_construction_and_facility_clicks() _test_expanded_world_round_trips() _test_grid_preserves_hit_testing() _test_animation_pause_and_simulation_isolation() _preview.queue_free() await process_frame if _failures == 0: print("PASS: %d pixel tower checks" % _checks) else: push_error("FAIL: %d of %d pixel tower checks failed" % [_failures, _checks]) quit(_failures) func _test_zoom_anchor_and_reset() -> void: _preview.reset_view() var overview := _preview.get_map_rect() var anchor_cell := Vector3i(-4, 2, WorldPreview.LayerMode.FRONT) var anchor := _preview.cell_to_canvas(anchor_cell) _preview.set_zoom(2.0, anchor) _expect(_preview.canvas_to_cell(anchor) == anchor_cell, "zoom anchored at a room keeps that room under the pointer") _expect(_preview.cell_to_canvas(anchor_cell).distance_to(anchor) <= 2.0, "cursor-anchored zoom keeps the same location within the room") _expect(_preview.get_cell_rect(anchor_cell).size.y > _preview.get_cell_rect(anchor_cell).size.x, "tower floors retain the rectangular cell aspect used by hit testing") _preview.pan_offset += Vector2(137, -73) _preview.reset_view() var restored := _preview.get_map_rect() _expect(restored.position.is_equal_approx(overview.position) and restored.size.is_equal_approx(overview.size), "reset returns to the original full overview after both pan and zoom") _expect(_preview.canvas_to_cell(_preview.cell_to_canvas(anchor_cell)) == anchor_cell, "reset keeps authoritative world coordinates selectable") func _test_construction_and_facility_clicks() -> void: var origin := Vector3i(-6, 0, WorldPreview.LayerMode.FRONT) var result: Variant = _runtime.submit_command( &"build_facility", {&"definition_id": &"reception", &"origin": origin, &"rotation": 0}, &"player", &"", 50, -1, &"pixel.camera.reception" ) _expect(result != null and result.is_success(), "camera fixture builds a reception through Runtime") var jobs: Array = _runtime.construction_service.get_jobs() _expect(jobs.size() == 1, "fixture creates exactly one real construction site") if jobs.size() != 1: return var job: Variant = jobs[0] _preview.layer_mode = WorldPreview.LayerMode.FRONT _preview.set_zoom(2.0) _center_on(origin) _click(_preview.cell_to_canvas(origin)) _expect(_last_construction == StringName(job.job_id) and _last_facility.is_empty(), "zoomed construction click resolves the real job rather than a finished facility") _clock.advance_minutes(120) var facilities: Array = _runtime.construction_service.get_facilities() _expect(facilities.size() == 1, "the clicked construction completes in simulation time") if facilities.size() != 1: return var facility_id := StringName(facilities[0].get(&"instance_id", &"")) var occupied: Array[Vector3i] = [] for x: int in range(4): for y: int in range(2): occupied.append(origin + Vector3i(x, y, 0)) for zoom: float in [1.0, 2.0, 3.0]: _preview.set_zoom(zoom) _center_on(origin + Vector3i(1, 1, 0)) for cell: Vector3i in occupied: var rect := _preview.get_cell_rect(cell) # Sample near opposite corners to catch square-cell conversions that # look correct at the center but select a different floor near edges. for fraction: Vector2 in [Vector2(0.15, 0.15), Vector2(0.85, 0.85)]: var point := rect.position + rect.size * fraction _expect(_preview.canvas_to_cell(point) == cell, "zoom %.1f maps interior point to occupied cell %s" % [zoom, cell]) _expect(_preview.facility_at_canvas(point) == facility_id, "zoom %.1f resolves reception footprint at %s" % [zoom, cell]) _click(point) _expect(_last_facility == facility_id and _last_construction.is_empty(), "zoom %.1f emits facility selection for the clicked interior" % zoom) var empty_neighbor := origin + Vector3i(4, 0, 0) var empty_point := _preview.cell_to_canvas(empty_neighbor) _click(empty_point) _expect(_last_facility.is_empty() and _last_cell == empty_neighbor, "zoom %.1f does not extend the reception hit area into the adjacent cell" % zoom) var front_point := _preview.cell_to_canvas(origin) _preview.layer_mode = WorldPreview.LayerMode.BACK _click(front_point) _expect(_last_facility.is_empty() and _last_cell == Vector3i(origin.x, origin.y, WorldPreview.LayerMode.BACK), "zoom %.1f cannot select the FRONT reception through the BACK layer" % zoom) _preview.layer_mode = WorldPreview.LayerMode.FRONT _click(_preview.cell_to_canvas(origin)) _expect(_last_facility == facility_id, "zoom %.1f restores reception selection after returning to FRONT" % zoom) func _test_expanded_world_round_trips() -> void: var reputation: Variant = _runtime.submit_command( &"update_progression_metrics", {&"metrics": {&"reputation": 25}}, &"player", &"", 100, -1, &"pixel.camera.rank.reputation" ) var capital: Variant = _runtime.submit_command( &"change_resources", {&"deltas": {&"gold": 10000}}, &"player", &"", 100, -1, &"pixel.camera.rank.capital" ) var promoted: Variant = _runtime.submit_command( &"set_world_tree_rank", {&"rank": 2}, &"player", &"", 100, -1, &"pixel.camera.rank.promote" ) _expect(reputation.is_success() and capital.is_success() and promoted.is_success(), "public progression commands expand the camera fixture above and below ground") if not promoted.is_success(): return var bounds: Dictionary = _runtime.world_service.get_bounds() var cells: Array[Vector3i] = [ Vector3i.ZERO, Vector3i(0, -1, 0), Vector3i(int(bounds[&"min_x"]), int(bounds[&"min_y"]), 0), Vector3i(int(bounds[&"max_x"]), int(bounds[&"max_y"]), 0), Vector3i(-3, 4, 0), ] for layer: int in [WorldPreview.LayerMode.FRONT, WorldPreview.LayerMode.BACK]: _preview.layer_mode = layer as WorldPreview.LayerMode for zoom: float in [1.0, 2.0, 3.0]: _preview.set_zoom(zoom) for cell: Vector3i in cells: cell.z = layer _center_on(cell) _preview.pan_offset += Vector2(31, -17) var point := _preview.cell_to_canvas(cell) _expect(_preview.canvas_to_cell(point) == cell, "zoom %.1f round trip survives pan, bounds, and layer at %s" % [zoom, cell]) var rect := _preview.get_cell_rect(cell) _expect(rect.has_point(point), "cell center remains inside its rendered rectangle") _preview.reset_view() var overview := _preview.get_map_rect() _expect(_preview.canvas_to_cell(overview.position - Vector2.ONE * 2.0) == null, "outside-world pixels cannot become buildable cells after overview reset") func _test_grid_preserves_hit_testing() -> void: _preview.layer_mode = WorldPreview.LayerMode.FRONT var cell := Vector3i(-5, 1, 0) _center_on(cell) var point := _preview.cell_to_canvas(cell) var expected := _preview.facility_at_canvas(point) _expect(not expected.is_empty(), "grid fixture points at a real reception cell") for show: bool in [false, true, false]: _preview.show_grid = show _click(point) _expect(_last_facility == expected and _preview.canvas_to_cell(point) == cell, "grid visibility never changes facility or floor hit testing") func _test_animation_pause_and_simulation_isolation() -> void: var before_digest := SimulationDigest.compute(_runtime.create_snapshot()) var before_minute: int = _clock.absolute_minute var stopped_at := float(_preview.get("_animation_time")) _preview.call("_process", 0.5) _expect(is_equal_approx(float(_preview.get("_animation_time")), stopped_at), "paused simulation freezes actor animation even when rendering receives a frame") _clock.set_paused(false) _preview.call("_process", 0.25) var animated_at := float(_preview.get("_animation_time")) _expect(animated_at > stopped_at, "unpausing resumes the pixel actor animation clock") _clock.set_paused(true) _preview.call("_process", 0.5) _expect(is_equal_approx(float(_preview.get("_animation_time")), animated_at), "pausing again freezes the exact resumed animation position") var cell := Vector3i(-5, 1, 0) _center_on(cell) var anchor := _preview.cell_to_canvas(cell) var wheel := InputEventMouseButton.new() wheel.button_index = MOUSE_BUTTON_WHEEL_UP wheel.pressed = true wheel.position = anchor _preview.call("_gui_input", wheel) _expect(_preview.canvas_to_cell(anchor) == cell, "wheel input preserves the room under the cursor while paused") var press := InputEventMouseButton.new() press.button_index = MOUSE_BUTTON_MIDDLE press.pressed = true _preview.call("_gui_input", press) var drag := InputEventMouseMotion.new() drag.relative = Vector2(43, 27) _preview.call("_gui_input", drag) press.pressed = false _preview.call("_gui_input", press) var moved_point := _preview.cell_to_canvas(cell) _expect(moved_point.distance_to(anchor + drag.relative) <= 2.0, "middle-button drag translates the visible room by the pointer movement") _click(moved_point) _expect(not _last_facility.is_empty(), "a room remains selectable immediately after dragging") _preview.reset_view() _expect(int(_clock.absolute_minute) == before_minute, "animation frames and camera input do not advance simulation minutes") _expect(SimulationDigest.compute(_runtime.create_snapshot()) == before_digest, "animation, zoom, pan, and visual selection preserve the authoritative simulation digest") func _center_on(cell: Vector3i) -> void: _preview.pan_offset += _preview.size * 0.5 - _preview.cell_to_canvas(cell) func _click(position: Vector2) -> void: _last_facility = &"" _last_construction = &"" _last_cell = null var event := InputEventMouseButton.new() event.button_index = MOUSE_BUTTON_LEFT event.pressed = true event.position = position event.global_position = _preview.global_position + position _preview.call("_gui_input", event) func _on_facility_selected(instance_id: StringName) -> void: _last_facility = instance_id func _on_construction_selected(job_id: StringName) -> void: _last_construction = job_id func _on_cell_clicked(cell: Vector3i) -> void: _last_cell = cell func _expect(condition: bool, message: String) -> void: _checks += 1 if not condition: _failures += 1 push_error(message)