Commit graph spaceship/tests/test_lunar_field.gd
Author SHA1 Message Date
Loïc Wisniewski
f5a16a7b95 Share one backdrop between game and editor, and fix three input/economy bugs
The setup editor and the landed surface drew different backgrounds for the
same planet. Each scene carried its own hand-written ShaderMaterial, and only
the game side ever swapped in a profile's shader, so a gas giant authored in
the editor showed recoloured craters while the game showed cloud bands. Both
now instance one SurfaceBackground.tscn and configure it through one method,
and every profile names its own shader - the old "null means keep whatever
the scene already had" convention was the drift, since each scene "already
had" something different. The shared material is resource_local_to_scene, or
two live surfaces would repaint each other.

The gas giant's bands broke into disjoint segments once per cell: the second
turbulence octave doubled only the INTEGER half of a lattice coordinate,
which is a different point in every cell rather than the same point at twice
the frequency. Measured, the old form steps 0.153 at a cell boundary against
0.0002 now. A second discontinuity went with it - the camera cell index
arrives pre-wrapped, so hashing it raw rearranged the whole sky every 32
cells; hashes are now wrapped per octave and the field is exactly periodic.
Bands also gained low-frequency octaves and per-row differential drift.

WorldMap swallowed every touch in _input(), which runs before the GUI system,
so CloseButton never saw one: the map could be opened and not closed. Android
and desktop hid it behind Godot's emulated mouse event, which this never
swallowed; a phone browser gives no such second chance. Its test had been
closing the map by calling the method the button was failing to reach.

Construction now draws on the fleet hold as well as the bank. With an empty
starting bank and ore bankable only at a mothership, charging construction to
the bank alone made the first mothership cost ore that could not yet exist.

Also: a conquered planet no longer re-spawns its garrison (guarded on having
capture points, since a setup with none registers as vacuously conquered);
planets keep the map they drew, with the candidate list sorted rather than
left in filesystem order and assignments pruned rather than cleared on
rescan; and the map editor lists setups at a tappable size and can delete
one, as an explicit armed mode rather than a tap away from destroying work.

Full suite 106/106.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:16:38 +02:00
Loïc Wisniewski
657ca9d035 Make the lunar backdrop cheap: 3x3 field, then bake and tile it (#68)
The crater shader ran on every pixel of a fullscreen rect every frame and
was by far the most expensive thing in the surface scene. Nothing else came
close: RockField keeps 14 rocks alive, Rock/Ship/Enemy already avoid
per-frame queue_redraw() for transform-only changes, and LunarBackground.gd
is a handful of scalar ops.

Three reductions to the field itself:

- 5x5 -> 3x3 neighbour scan. That width was never the geometric answer;
  it was insurance against craters seen clipping at cell boundaries
  on-device, i.e. a crater reaching past its nominal radius because a hash
  returned a value outside [0,1] and extrapolated mix(). Clamping the
  crater CENTRE into its own cell, alongside the radius clamp already
  there, bounds the reach whatever the hash returns - so a crater two cells
  out is >= 1.0 cells away, a normalized distance of >= 2.63, past every
  threshold the shading reacts to (the last saturates at 1.15). The outer
  ring provably cannot change a pixel. This removes the clipping bug's
  cause instead of hiding it behind 16 extra samples.
- One hash42 per cell instead of hash21 twice plus hash22, decorrelating
  on output lanes rather than input offsets.
- Squared distances in the loop, square-rooted twice at the end rather
  than once per sample.

Measured with the new tests/profile_shader.sh, above softpipe's own ~657ms
fullscreen floor: 9482ms -> 405ms of shader work, a 23x reduction.

Then the larger change: the field is a fixed function of world position, so
every frame recomputed an identical answer. It is now made periodic (the
cell index is wrapped before hashing, which makes a Worley field tile
exactly rather than merely blend), baked once at load into one tile, and
scrolled with repeat wrapping by lunar_tiled.gdshader - one texture fetch
per pixel. Two honest costs: the field repeats every 7040 world units, and
a fixed-resolution tile softens when zoomed past 1:1. Lunar regolith is
forgiving of both, having no landmarks and being smooth gradients already.

The bake waits a frame and asks the material whether it actually samples a
field_texture, so a gas giant - which swaps in its own background shader
(#51) - does not pay for a 2048-square tile it will never read. Headless
runs skip it entirely, having nothing to rasterize.

tests/test_lunar_field.gd guards the reasoning, since a fragment shader
cannot be asserted on directly: the premises are still in the source, the
worst-case ring-2 geometry stays clear of every threshold independent of
the hash, a 3x3 and a 5x5 scan render identically over 4000 sampled points,
the field repeats exactly in both axes, and the miss sentinel still cannot
overflow mediump into +Inf and then NaN.

test_surface_profiles.gd asserted "a rocky landing leaves the background
alone" by naming lunar_surface.gdshader; it now reads the shader from the
scene, which is the property it actually cares about.

PlanetSurface.tscn's edit also carries the planet-name relocation described
in the HUD commit - same scene file, so the two cannot be split.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 23:20:14 +02:00