Godot writes one beside every script and the repo tracks them; these are the
ones for the files added across #99-#107 and the ability seam.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#105 shipped with interceptors as the only garrisonable vessel, and the
reason was a design fault rather than a decision: firing the laser and the
gas bomb lived in the player's fleet controllers, so a stationed laser would
have held its position and never shot.
The shot is now a thing in the world rather than something a controller
draws. LaserBeamEffect and GasBombEffect are self-contained, self-animating
and self-freeing, added to the tree root - the same shape EnemyBigLaserBeam
and ArtilleryShell already had on the enemy side - and they outlive the
vessel that fired them.
Above them sits one seam, on VesselCooldownFillComponent: ability_range(),
ability_ammo_resource(), ability_ammo_cost() and fire_ability(). A beam and a
bomb are now the same shape of thing to whoever pulls the trigger, so the
garrison AI never names an ability - the test asserts that literally, by
reading the AI's own source. A third armed vessel needs to implement the
seam and nothing else.
Ammunition is the CALLER's business, deliberately: the fleet spends from its
hold, a garrison from its outpost's store, and neither concern belongs in
the component. A garrison with an empty magazine holds fire rather than
falling back on the fleet's hold.
Outpost storage: 50 gas per stationed bomber and 50 energy per stationed
laser, off the VESSEL definitions, so an outpost never learns which vessel
needs what. It is a capacity, not a stock - hub transporters fill it exactly
as they fill a gas burner's buffer, because building_logistics_deficit now
takes the larger of the definition's static buffer and the garrison's
magazine. Every other resource is capped at zero: an outpost is not a depot,
and ore parked at one has nothing to do there and no transporter coming for
it.
Also restores two properties the Godot editor dropped from
GasBurnerBuildingDefinition.tres when it re-saved the file:
collected_outputs and capabilities. Without the first, nothing ever collects
the burner's energy - test_gas_burner caught it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing can attack a structure that cannot be hurt, so this is the floor the
rest of #97 stands on.
max_hp is per definition - an extractor bolted to a rock is not the
mothership - and the current value lives on the RECORD in GameState, not on
the Building node. Main.tscn is torn down and rebuilt on every trip to a
planet surface, so damage kept on a node would heal itself the moment the
player landed.
A record written before this carries no hp field at all. That reads as
undamaged rather than as zero, or an existing save would open on a field of
wrecks.
The mothership cannot be destroyed - it floors at 1hp. The bank, the hangar
and research all live there and a wiped fleet rebuilds there, so losing it
would be a soft-lock with no route back. Keyed on the "bank" capability
rather than on the type: whatever building holds the bank is the one that
cannot be lost.
Destroying a hub retires its transporters. Everything else keyed on a
building id already copes with the building being gone - a transporter
mid-delivery puts its cargo back rather than destroying it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The shot was one flat line that appeared at full length and faded. Nothing
happened at the ship, nothing moved, and a hit looked exactly like a miss.
The blade now runs out of the hull over the first 30% of the flash on a
cubic ease-out, holds white-hot, then falls away, with a hum in its width,
a bright cap at the leading end, a flare at the emitter, and sparks off the
hull that fired it. MuzzleSparkVFX takes an optional colour so the laser
throws cyan where the ordinary gun (#87) keeps its gold.
The extra light is a white-hot rod down the middle at 42% of the width,
deliberately not a wider halo: the note at BEAM_WIDTH_SCALE records that the
drawn beam spans exactly what the beam hits, and a halo past that would put
the "it visibly missed" complaint back the other way round.
The damage is untouched - dealt in full, along the whole beam, on the frame
of the shot. A beam that had to arrive would change how the weapon plays,
which the ticket did not ask for, so the test pins it down: a target at the
far end of the range must be hit before the drawn blade has visibly reached
it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#85 - on a computer the sticks and the pivot pair are dead weight: WASD
steers, the mouse aims, Q/E pivot. All four go together the first time a
keyboard or a mouse is used, and only where there is no touchscreen. A touch
device with a keyboard attached keeps everything, because a finger is still a
way to play it.
#86 - the default gun shoots faster (500 -> 850) and further (320 -> 620).
The interesting half is that a bolt now STOPS where the gun's range ends.
Projectile lifetime is derived from fire_range at launch rather than set
independently, so the distance a shot covers and the distance the aim guide
draws are one number. They were two, and they disagreed: the guide promised
320 while a bolt actually flew speed * lifetime = 750, so shots landed on
things well past the line saying where they would stop.
#87 - the ship that fires throws sparks, so a shot is visibly attached to the
vessel it came from; with a whole fleet shooting there was nothing tying the
two together, which started mattering once the player had to aim. Enemy deaths
gained a shockwave ring and roughly double the debris - a kill used to read as
a small red puff that was easy to miss in a fight.
#88 - everything moves 1.5x faster: fleet cruise, every vessel's max_speed and
acceleration, and every enemy's. Scaling the enemies too keeps it a change of
TEMPO rather than a difficulty cut, and scaling acceleration alongside speed
keeps the handling from #77's tuning intact.
Three tests carried assertions that were really about one balance pass:
test_enemy_mothership pinned a literal 35.0 and now compares against the
chaser, which is what "never reaches ramming speed" actually means;
test_planet_atmosphere_transition flew far enough at the new speed to reach
the landed garrison, and now clears it, since that test is about the handoff
and not about a fight it never meant to pick.
Also repaired real data loss, caught only because test_gas_burner failed:
Godot had silently re-serialised GasBurnerBuildingDefinition.tres and dropped
collected_outputs, capabilities, map_radius and map_phase, and had recreated a
stale copy of it at the pre-#84 root path. Without collected_outputs no
transporter ever collects a burner's energy. Both files restored from git.
It had also resurrected pre-#84 SNAPSHOTS of Main.tscn, Planet.tscn and
lunar_surface.gdshader at their old root paths - the root Main.tscn still
carried 27 references to the flat layout. They are dead weight the engine
never loads, since project.godot and every preload point into the folders, but
they are exactly the kind of thing that wastes an hour later when someone
edits the wrong copy. Deleted, and the root is now down to ui_theme.tres,
icon.svg, web_shell.html and the two config files.
Suite 119/119.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>