spaceship/tests/_sniper_vs_chaser_preview.gd
Loïc Wisniewski 2f161621c3 #84: move 215 root files into a folder hierarchy
Everything lived in one flat directory, so a diff gave no clue what area it
touched and finding the file you wanted meant knowing its exact name. Twelve
folders now, grouped by what a thing IS rather than what type it is - a
vessel's definition, its component scenes and its upgrade resources sit
together, because that is the set you read when you change one of them.

  buildings  core  controllers  enemies  logistics  planets
  resources  shaders  surface  ui  vessels  vfx  world

332 files moved and every res:// reference rewritten - 306 files touched,
including the autoload and main-scene paths in project.godot. Nothing renamed:
this is purely where files sit, so the diff is a pile of pure moves and no
behaviour is buried in it. ui_theme.tres, icon.svg and web_shell.html stay at
the root, where Godot and the export preset expect them.

Verified three ways, because a suite alone would not have caught all of it:
the full 117 pass, the web export builds clean, and the game renders.

The export is why that matters. The FIRST export after the move failed with
"Attempt to open script 'res://GameState.gd'" - the old path - while
project.godot on disk plainly said res://core/GameState.gd. That is this
project's documented Android FUSE mtime trap: Godot served a cached copy of a
file whose content had changed. A `touch` fixed it. Nothing in the test suite
would have found that, because the suite runs from a copy on a native
filesystem - the one place the bug cannot happen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 19:44:14 +02:00

76 lines
2.8 KiB
GDScript

extends Node2D
# Setup is deferred to the first _process() tick rather than done in
# _ready(): PlanetAtmosphereController auto-spawns a 5-ship default fleet
# in ITS _ready() on any fresh (non-returning) Main.tscn load, and since
# this scene is itself added to the tree from inside _screenshot_tool.gd's
# own _initialize() (a nested, not-yet-settled context), there's no
# guaranteed ordering between the two _ready() calls - clearing the fleet
# here could run BEFORE that auto-spawn and then get silently overwritten
# by it. Waiting one frame guarantees it has already run first.
#
# Even after clearing, physics ticks can run several times before this
# script's own next _process() tick (softpipe rendering is slow, and
# physics is decoupled from it), so an auto-spawned ship can still get one
# shot off in flight toward these enemies before the clearing code above
# ever runs. Pausing the whole tree once everything is placed (last thing
# this script does) freezes any such shot in transit for good, rather than
# letting it arrive a few physics ticks later while the screenshot is
# still waiting out its frame count.
var main: Node
var setup_done: bool = false
func _ready() -> void:
GameState.buildings.clear()
GameState.extractors.clear()
GameState.transporters.clear()
main = load("res://world/Main.tscn").instantiate()
add_child(main)
main.get_node("BackgroundLayer").visible = false
main.get_node("AsteroidField").visible = false
main.get_node("PlanetField").visible = false
main.get_node("BuildingField").visible = false
main.get_node("EnemySpawner").set_process(false)
main.get_node("AsteroidField").set_process(false)
func _process(_delta: float) -> void:
if setup_done:
return
setup_done = true
var fleet: Node = main.get_node("Fleet")
for ship in fleet.ships:
if is_instance_valid(ship):
ship.set_physics_process(false)
fleet.spawn_ships(0, Vector2.ZERO)
# spawn_ships(0, ...) still isn't a truly empty fleet: it falls back to
# one default interceptor whenever the requested composition would
# otherwise be empty (Fleet.gd's own "the fleet is never really empty"
# rule). Clear that forced ship too, and halt it the same way.
for ship in fleet.ships:
if is_instance_valid(ship):
ship.set_physics_process(false)
ship.queue_free()
fleet.ships.clear()
for projectile in get_tree().get_nodes_in_group("player_projectiles"):
projectile.queue_free()
var chaser: Node = load("res://enemies/Enemy.tscn").instantiate()
chaser.enemy_id = "chaser"
chaser.position = Vector2(-160, -80)
main.add_child(chaser)
chaser.speed = 0.0
var sniper: Node = load("res://enemies/Enemy.tscn").instantiate()
sniper.enemy_id = "sniper"
sniper.position = Vector2(160, -80)
main.add_child(sniper)
sniper.speed = 0.0
var camera: Camera2D = main.get_node("Camera2D")
camera.zoom = Vector2(1.4, 1.4)
get_tree().paused = true