10 Commits

Author SHA1 Message Date
Josh Creek e0a1cbaf6d fix(presentation): simplify camera and engine effects 2026-08-08 08:44:26 +01:00
Josh Creek 8fa40769a8 feat(rendering): add cinematic post processing 2026-08-07 22:54:01 +01:00
Josh Creek 8d4078989c feat(arena): add grounded lighting and asteroid dressing 2026-08-07 22:26:02 +01:00
Josh Creek 18fdb0f232 feat(arena): resize for 5v5, cut a real goal-wall hole, and bake collision geometry
Retraining from scratch removes the constraint that blocked these: the
existing checkpoints in Game/bots/promoted/ no longer need byte-identical
geometry.

- Fix the goal aperture constants (were 1.85/1.65, now match
  objects/goal.tscn's actual sensor exactly at 1.75/1.5) so a ball crossing
  the visible edge can't fail to score.
- Cut a real navigable hole in each end wall's collision
  (_build_end_wall_colliders), replacing the previous solid box — the ball
  now genuinely enters the net instead of triggering the sensor a hair
  before hitting a solid wall. Correct for both FLOOR and ELEVATED goal
  modes via _goal_surround_bounds. A separate, slightly wider
  GOAL_VISUAL_APERTURE_* pair keeps the collision hole exact while still
  giving goal.gd's bezel/rim frame clearance to be seen against the hull cut.
- Resize the play volume 1.5x (INNER_HALF_X/Z/HEIGHT 12/18/12 -> 18/27/18) to
  comfortably fit a 5v5 roster: updates every hand-authored literal in
  arena_boundary.tscn/arena_base.tscn/the elevated arena variants that
  doesn't derive from those constants, adds 5 spawn markers per team, and
  rescales ship_observations.gd's normalization scales and arena_02's
  cosmetic decoration/nebula-dust shader uniforms to match.
- Bake the ~170 runtime-generated CollisionShape3D nodes into the scene via
  a new bake_colliders()/tools/bake_arena_boundary.gd instead of rebuilding
  them on every load — a real load-time cost repeated in every parallel
  headless training env. Colliders must be direct children of the
  StaticBody3D to register at all, so bake_colliders() parents them onto
  self and tags them with a group for idempotent re-baking, rather than
  grouping them under an intermediate container node. _ready() self-heals:
  it skips regenerating only when an existing bake's goal_mode metadata
  matches the current one, so the FLOOR-mode bake in the shared scene is
  never silently reused by an ELEVATED arena variant.
2026-08-05 09:17:13 +01:00
Josh Creek fa9590d9c3 refactor(team-colors): collapse disagreeing team palettes into one source of truth
ship.gd, HUDController.gd, and goal.gd each declared their own TEAM_COLORS,
arena_boundary.gd/arena_deck.gdshader had a third pair, and HUD.tscn baked
in a fourth (hardcoded "BLUE"/"ORANGE" labels) — nose, goal rim, end zone,
and scoreboard all rendered different blues. New scripts/team_colors.gd
(class_name TeamColors) is now the single source every one of those reads
from, and team identity moves to purple/green.
2026-08-04 19:18:45 +01:00
Josh Creek a836a96d13 feat(weather): keep nebula dust clear of the play volume
The dust emitter's box fully encloses the arena, so motes drifted through the
pitch and hazed the surfaces the player is trying to read.

Fade each mote by the Chebyshev distance of its world-space origin outside
the play volume, taken from MODEL_MATRIX's translation column so it is
unaffected by the billboard rewrite of VERTEX. Motes inside the box are fully
hidden and fade in over arena_clear_distance beyond it, so the effect reads as
weather drifting around the station rather than through the match.

Extents default to ArenaBoundary's play volume and are exposed as uniforms.

Also adds the missing .uid sidecar for the shader.
2026-08-04 13:36:43 +01:00
Josh Creek 66b6215fdd feat(goal): recess goals into the hull behind a netted pocket
The goal was a single flat translucent slab. Because the chase camera sits
behind the goal line at kickoff it rendered as a large blue rectangle across
the lower screen, and the hull beside the mouth was translucent field panel,
so the pocket behind it showed straight through the wall and the net appeared
a second time alongside the frame.

The boundary now carries an opaque bulkhead around each mouth, which occludes
properly. On top of that the goal becomes a pocket sunk into the wall: a dark
machined bezel lining the opening, a thin team-coloured emissive rim flush
with the wall face, and netting from a single box viewed inside-out, giving a
five-sided pocket instead of a flat panel across the back.

The net is cut with discard rather than alpha blending so it still writes
depth and sorts against the frame and the containment field like solid
geometry; a blended net would join the transparent queue and sort per object
against the boundary shell, which is the artefact this whole pass removed.

goal.tscn drops from 18 node/sub-resource blocks to 3 — it is now just the
Area3D, its CollisionShape3D and the script. All geometry is built in code
from two loops, and the mouth is measured off the collision shape rather than
restated, so the frame cannot drift from the volume that actually scores.

The sensor, its collision shape and the goal_scored signal are unchanged, and
visuals are skipped under --headless.
2026-08-04 13:36:07 +01:00
Josh Creek 02df09e40d feat(arena): draw the play volume as one non-overlapping surface shell
Every visible surface of the enclosure shared one alpha-blended material and
was drawn as several overlapping layers: floor, ceiling and four walls as
solid BoxMeshes, plus corner curves and fillets generated on top of the box
faces they eased into. Alpha-blended surfaces don't write depth and sort per
object, so the perimeter composited that tint twice and the corners three
times, giving hard-edged trapezoidal patches that re-sorted as the camera
moved. The floor box also overhung the walls by 1 m and showed through them.

Replace all of it with a single generated mesh covering the inner surface
exactly once, every piece cut to meet its neighbours edge-on and only
inward-facing triangles emitted. It carries two surfaces: an opaque hull
(deck, base fillets, goal bulkheads) and the translucent containment field
(walls, corners, ceiling fillets, ceiling).

The field shader is additive and unshaded rather than alpha-blended: additive
cannot double-darken and composites order-independently, so overlap is
structurally invisible, and being unshaded it no longer picks up per-arena
light and GI gradients across a 28x40 m panel. Fresnel replaces the old
StandardMaterial3D rim, which was a lit effect and the wrong tool. The deck
shader draws plating and field markings procedurally from the boundary's own
constants, so markings cannot drift from the collision geometry.

Per-face MeshInstance3D visibility toggling is gone; the field shader fades
facets the camera has crossed outside of per-pixel from one uniform, which is
what allows a single merged mesh.

Also adds ELEVATED goal mode and the ceiling fillets, and skips the mesh build
entirely under --headless, where training spawns instances that never render.

Collision is untouched: 166 collider shapes in FLOOR mode and 158 in ELEVATED,
verified byte-identical to before, so trained policies in Game/bots/ are
unaffected.
2026-08-04 13:35:55 +01:00
Josh Creek 6f5ce488a9 feat: lit particle shader for nebula dust weather effect
Replace NebulaDust's flat unshaded glow material with a custom
ShaderMaterial: a fake per-pixel puff normal on the billboarded quad
feeds a light() override so motes catch the directional light and a
nebula-core color bias, alpha gets a depth-texture soft-particle fade
so motes no longer hard-clip through boundary/decoration geometry,
and a per-particle hash adds subtle sparkle.
2026-08-04 07:42:34 +01:00
Josh Creek 171cd4a840 feat: add Nebula and Asteroid Field arenas
Introduce arena_02 (Nebula) and arena_03 (Asteroid Field) alongside
arena_01, listed in a new ArenaRegistry (scripts/arena_registry.gd) as
the single source of truth for available arenas. Free Play lets the
player pick an arena from the main menu; Match/Spectate each pick one
at random per session; Training keeps its own fixed arena_01.

Nebula gets an RL-style visual treatment: a near-invisible glass
boundary material shared by all arenas, a baked equirect nebula sky
texture, a drifting-dust particle system, and a Blender-modeled
station/debris/planet decoration set. GameMode gains a
_get_arena_scene_path() hook so modes can instantiate their arena in
code instead of hardcoding it in the scene.
2026-08-03 19:11:13 +01:00