BACK TO HOMEPAGE
Project Timeline

STARGAZER · DAY 7

StarGazer is a single-player 3D space combat game where you command massive capital warships through procedural roguelike runs — broadsides, voxel hull damage, deterministic simulation, built solo with AI assistants.

Started May 21, 2026 · last activity today

Latest StarGazer milestone screenshot
7
Days running
7
Active days
0
Commits
10
Posts
186
Decisions

Activity & Beats

Composite of commits, decisions, and posts per day. Click a bar to filter the timeline below.

Activity barBlog postMilestone
May 27DECISION

Tighten 10 skills with mandatory proof artifacts or ask-user fallbacks

Audit found weakness clustered in "look at it and confirm" steps that all relied on the LLM honestly reporting what it sees. The 2026-05-25 Schema Phase 1 silent rollback and the bright-pink default-run regression are load-bearing precedents, both shipped because no machine-readable gate caught the failure. User's directive: "AUTOMATE, AUTOMATE, AUTOMATE." When no automation exists for a step, ASK the user for the proof recipe and encode it, never assume.

May 27DECISION

Split `Game1.cs` (1689 LOC god-class) into `StarGazerGame` partial-class file map

`Game1.cs` at 1689 LOC was a god-class, `LoadContent` interleaved 4 jobs in 522 lines, `Update` interleaved 5 concerns in ~190 lines, MCP terminal block buried 160 LOC under `#if STARGAZER_MCP`. The AI-access angle was load-bearing: an agent fixing a beam tint could spend the whole file paging `Game1.cs` before realizing the answer lives in `Render/`. Names like `StarGazerGame.Commands.cs` and `StarGazerGame.Draw.cs` self-reject that mis-step on grep alone.

May 27POSTSHIPPED

Inside-Out Spaceships, Caught by a Side-by-Side Sweep

A rigorous parity sweep between the in-game render and the reference shipyard preview cracked open a see-through hull bug, and the real fix lived all the way back in the exporter.

May 26DECISION

Skills migrated from `.agents/skills/` to `.claude/skills/`; lint script enforces the path

Claude Code's skill loader auto-discovers from `.claude/skills/` (project) and `~/.claude/skills/` (user) only. Skills under `.agents/skills/` worked when an agent read AGENTS.md and followed links manually, but they did NOT appear in the auto-discovered tool list for fresh sessions, defeating the entire point of having them as Skills. Verified in this session by inspecting the system-prompt skill list and confirming zero StarGazer skills present. The `.agents/` naming was project-creation intuition that pre-dated awareness of Claude Code's discovery path. Once observed, the fix is mechanical (move) but the value is permanent (every future session auto-loads `stargazer-mcp-usage`, `stargazer-visual-eval`, the deploy skills, etc. as named invocable Skills).

May 26POSTCROSSED

An Operating Console for the Running Game: Six Weeks of MCP

StarGazer now hosts a Model Context Protocol server inside the game process. Twenty tools across read, mutate, capture, commit. Every screenshot in this post was captured by the MCP driving itself.

May 25DECISION

Diverged-main sync policy: rebase the trivial cases, surface the messy ones

The push-rejection path in commit-push was silent, no analysis, no proposed move, just "ask the user", putting triage cost on the user every time, even when the answer is obvious. At the same time, autonomously resolving a real conflict or rebasing across a merge commit is the exact failure mode that destroys work. Split: agent does the work where the answer is mechanical, user decides anything involving history-shape judgment. Force-push on `main` stays banned regardless, the release-branch exception in deploy skills does NOT extend here.

May 25DECISION

Add stargazer-verify-determinism skill + .gitattributes LF-pin for hash-bound fixtures + defer stargazer-schema-contract

Determinism is load-bearing per CHARTER §2.1, and [`game.yml:44-48`](../.github/workflows/game.yml) skips `--voxel-test-suite` on Windows ("headless path TBD"). Solo-dev on Windows + pre-push hook capped at 30s (CHARTER §5.2 forbids expanding) = zero local determinism gate with CI catching regressions N-commits-deep; the skill closes that gap, running ~3.5s locally. The fixture-hash crash on Windows traced to git autocrlf eating LF bytes the sidecar hash was computed against, hence (1). Schema-contract deferral waits for referents to exist; writing it now would calcify placeholder layouts that PLAN phases 4–5 will likely reshape.

May 25DECISION

Ship pipeline phases L→F→G: shipyard cooks voxels, game stops cooking at load

Two long-standing problems converged, voxel-cook drifted across OS (Mac `0x516953CC401E7EB7` vs Windows `2D1009DC12BA4008` from the same source made `.last-hash` only meaningful on one platform) AND every C# build paid ~250 ms `CookGlb` at startup + every R-key reset despite the GLB never changing. Both root-cause to the cook living inside the runtime where FP depends on JIT+libm+CPU; moving it into the digest-pinned container makes byte output a property of the container, not the host. Three-file packs load 250 ms faster AND eliminate cross-platform drift; the 10-check validator is defense-in-depth (structural 1-6 fail-fast, self_hash 7 catches in-transit corruption, cross-refs 8-9 catch torn writes, check 10 closes splice attacks), each with a distinct error message for log forensics.

May 25DECISION

Ship pipeline Phase I followup: multi-stage shipyard.Dockerfile + digest-pinned cook-base + cross-platform byte-determinism verified

The pin + multi-stage is the load-bearing piece of "Python becomes canonical", digest pin blocks Docker Hub retags swapping in new libc6, `apt-mark hold` blocks `apt-get upgrade` in stage 2 from swapping libc6 out from under stage 1, `--require-hashes` blocks transitive numpy/pygltflib upgrades silently changing cook output. Two-stage design means production cook and CI canonical cook literally share the same Python interpreter and libm, they cannot drift, because `shipyard-service` IS `cook-base` + Node + web frontend. The cross-platform verification step is the receipt the whole plan was after: pure-Python `float` is deterministic on paper, but the proof is Mac+Linux Docker producing identical `stable_hash` byte-for-byte. Subtle gotcha during verification: an earlier 83-voxel Mac-vs-Linux delta traced to CLI-vs-pipeline voxel_size truncation, not FP drift, once both runs got the full-precision input, both produced identical output.

May 25DECISION

Close out 3 deferred ship-pipeline items: schema.yml canonical-cook CI gate + oracle threshold calibration + `.last-hash` Mac bump

Without the CI gate, contributors can edit `voxel_cooker.py` or bump a transitive numpy in `requirements.lock` and silently change canonical cook output, the `HASH_BUMPS.md` entry is the grep-able artifact proving drift was intentional. The threshold lowering reflects that runtime LoadFromBin uses the Python-cooked core stored in the binary header, so the C# cooker's independent core-pick can't affect runtime; the strict gate failed on a no-production-consequence delta. The `.last-hash` bump closes the Phase L row deferred at original ship time ("Mac sees different hash than Windows"), plan §L.1 required it in the same commit as the schema lockdowns. Windows-side bump will be a same-commit baseline update by whoever next runs on Windows.

May 25DECISION

Skill overhaul: fix daily-blog, add data/asset/combat verification skills, add `--dump-fleet`/`--dump-pack`, extend determinism gate to physics

A full skill audit (4 parallel reviewer agents on the existing 9-skill set) found that 5 of 13 verification surfaces in the codebase were dark, the C# game emits rich data lines (PERF_TICK, FLEET_LOAD, VOXEL_LOAD_FALLBACK_COOK, BENCH_PHYSICS hashes, asset validator output) that no skill was consuming. The "screenshot proves the pixels but not the code path" past-sin (default `dotnet run` falling back to runtime cook, rendering bright pink while static screenshots looked fine) was specifically what the log-scan addition guards against; auto-firing data-eval on every voxel/sim/pack edit closes the loop. The four-persona daily-blog swarm was load-bearing design intent (4 independent voices stay distinct in a way 1 voice merging them cannot) but the implementation never worked, phantom subagent APIs. Physics determinism wasn't gated despite BEPU being half the sim, so a floating-point regression there could sail through CI. YAGNI reviewer pushed back hard on `verify-combat` (5 scenario sweeps per beam tweak is too expensive); user resolved by gating it behind explicit invocation, keeping the auto-trigger budget cheap. `--dump-fleet`/`--dump-pack` exist as headless read-only CLI flags because the alternative (parsing `.run/stargazer.log` after a full launch) costs ~10s per commit suggestion vs <1s for the dump.

May 25DECISION

Unified capital-ship reactor core (Singularity Forge)

The prior quantum_sphere focal was undersized (~0.6m on a 72m ship) and the four-style split meant no ship felt iconic, plus the connectivity-slide pass buried it inside the hull. Replacement size lands 1.05m on an 18m gunship and 4m on a 72m dreadnought; the spine yoke gives the shell guaranteed OBB-overlap with rib frames so connectivity is satisfied without sliding. The anchor leaves ~78% of the surface exposed above hull silhouette, supporting "the core is your exposed kill target, defend it." Validator passes 8/8 randomized ships at 82–100% exposure (was failing-by-burial on most).

May 25DECISION

Singularity Forge v2: midline-centred, 20% cap, hex+multi-band

v1 floated above the hull instead of sitting in the gap, had no diameter ceiling (8m orbs on 72m dreadnoughts), and read as a saturated white emissive ball with no surface structure. v2 inverts the contrast strategy, bright shell, dark cladding, so the hex pattern and bands read as silhouette structure at every zoom. Multi-agent parallel review across 30 renders (6 ship families × 5 angles) confirms v2-iter3 reads as "a contained hex-panelled reactor cell on the centreline" on every family.

May 25POSTSHIPPED

Singularity Forge: The Reactor Core Your Capital Ship Is Built Around

Every capital ship now carries a hex-paneled, multi-banded containment cell at its centreline, a deliberately exposed kill-target the rest of the hull defends.

May 24DECISION

Phase 5b kinematic chain: 3-phase render-side aim path

Each phase is its own commit with a clear regression surface. Phase 1 alone gives rest-pose-equivalent visuals, Phase 2 alone shows kinematic parts at rest, Phase 3 alone rotates turrets; bundling would make blame impossible if math or API shape proves wrong. Render-side aim placement is correct because aim VISUAL is a render concern while weapon FIRE today is sim-owned via `WeaponBeamActive`/`FindEnemyTargetForShip`; computing render-only angles from the same sim target preserves the determinism tenet (no writes back to sim). When weapon fire is rewired to consume aim angles, the state migrates sim-side and `KinematicAimSystem` moves to `Sim/`.

May 24DECISION

StarUI Zero-Allocation Responsive UI Framework

Zero-allocation avoids GC sweeps in critical update/draw frames, ensuring locked 60+ FPS on Steam Deck. Single-pass batch rendering coalesces draw sessions, minimizing CPU/GPU driver command overhead.

May 24DECISION

Wound clusterer: O(n²) all-pairs → O(n) cell-indexed BFS

Prior implementation rescanned all unvisited records on every BFS dequeue, for a beam producing ~125 destroyed voxels on one face axis, that's ~15.6k comparisons per face per chunk, summed over 6 faces × dirty chunks × ships dominating the per-frame damage path during sustained combat. New cost is O(n) for the index build + O(k · 9) per cluster of size k, no allocation per occupied cell (linked list lives in a single `int[]` keyed by record index). The 9-cell neighborhood preserves the original Chebyshev-radius-1 semantics (prior comment claimed "4-neighbor" but the `|du|≤1 ∧ |dv|≤1` test was always Chebyshev, corrected in comment too).

May 24DECISION

DamageVisualHistory skips clean chunks instead of full 21k-voxel walk

Prior cheap chunk-hash early-out skipped the whole pass when nothing changed, but as soon as ANY chunk version incremented (every frame in combat) the inner loop visited all `asset.VoxelCount` (~21k) voxels and did 6 neighbor-lookup + IsAlive + key-hash-Add per live voxel, ~750k face-checks/frame at 60Hz across 6 combatants, dominant render-tick cost. With per-chunk skipping, a single-beam-impact frame typically touches only the 1–2 chunks the beam hit (~5% of voxels), so the inner work drops ~20×. Sentinel `-1` initialization preserves the "first call always walks everything" contract from the prior `lastStateChunkVersionsHash = -1` start.

May 24DECISION

KinematicAimSystem.BuildDeltaLookup reuses caller-owned Dictionary

Old signature allocated a fresh `Dictionary<string, (Vector3, float)>` once per ship per draw frame (~120 dicts/sec at the default 2-ship scene, growing linearly with ship count), directly violating the [StarUI zero-alloc render-loop discipline](#2026-05-24--starui-zero-allocation-responsive-ui-framework) that explicitly cited per-frame Dictionary allocation as the bottleneck dropping framerate to ~23fps in the prior HUD alternative. Reusing one Dictionary across all ships and frames removes the alloc entirely; `.Clear()` keeps capacity, so steady-state cost is one rehash-free walk per call.

May 24DECISION

Gameplay loop phase 2: 9-slot hotbar UI + scenario keys move to F1/F2/F3

Phase 2 of the gameplay-loop goal (see [`DOC/GAMEPLAY-LOOP-DESIGN.md`](../DOC/GAMEPLAY-LOOP-DESIGN.md)), the hotbar is the player-facing surface for the ability system. Landing the UI + key reshuffle before the sim-side routing keeps each commit reviewable in isolation: phase 2 ships "visible, no behavior", phase 3 ships "behavior wired through the existing UI". Self-contained `HotbarPanel` (no StarUI child elements) instead of mirroring `HudButtonPanel`'s UIElement-per-button pattern because the 9 slots sit at fixed offsets, the layout engine would burn allocations recomputing them every frame for no win. Voxel test suite PASS (hash `0x2A7028BB38A011F0`).

May 24DECISION

Enforce voxel core placement zone and exposure constraints

Complies with core gameplay requirements that each hull contains a visible and easily targeted core. Ensured 100% validation pass rates on procedural designs.

May 24DECISION

StarUI Responsive HUD Button Panel and Command Routing

Decoupled, addressable UI trees make all UI functionality reachable visually AND programmatically. This meets the architectural tenet that external AI agents can query UI state via JSON dumps and execute path-addressable button triggers (e.g., `/hud/buttonpanel/reset-battle`), keeping test automation fast and direct.

May 24DECISION

Majestic Dawn Logo Color Swap

User requested a swap to gold background with navy lines and details for a high-contrast, vibrant appearance.

May 24DECISION

Update Native Game Icons with Refined Logo

User requested the refined Majestic Dawn logo in the game shell too, updating raw SVG and compiled binaries ensures the executable shows gold-background/navy-lines branding on Windows and macOS windows, dock, and file explorer.

May 24DECISION

Require core visibility and longitudinal zoning on ship hulls

Guarantees every ship has a core that is visible, accessible, and shootable, directly supporting space-combat tactical gameplay where targeting cores is a primary mechanic.

May 24DECISION

Monorepo: four-stack + standard library, one repo

Solo dev, polyrepo discipline costs without buying anything. Shared schema/ subdir + CI cross-validation prevents the silent contract drift that motivated the integration plan in the first place.

May 24POSTSHIPPED

Turrets in Motion: Kinematic Aiming and Zero-Allocation StarUI

Bringing capital ship turrets to life with render-side kinematics and introducing StarUI, a zero-allocation HUD.

May 23DECISION

Share interactive combat launch defaults

The normal playable launch drifted away from the laser-duel proof, and loiter steering moved the enemy out of arc before the player could reliably see engagement.

May 23DECISION

Default Program camera to Orbit

The app runner and plain `dotnet run` share `Program`; putting the default there keeps the interactive launch, screenshot proof, and future agent runs from drifting.

May 23DECISION

Default Program voxel view to mesh damage

Combat was applying voxel damage, but default rendering stayed on the static GLB path; once visible, the older larger impact cluster deleted too much hull too quickly.

May 23DECISION

Keep default render systems enabled

The default live run is the showcase surface, so compiled visual systems should not be silently left null unless they are broken or explicitly disabled.

May 23DECISION

Tune beam damage as direct hits plus splash

The mesh-damage renderer exposed that repeated 1000-damage clusters detached nearly the whole GLB from the core after a few hits, making ships disappear.

May 23DECISION

Run live apphost as one-shot LaunchAgent

`launchctl submit` keeps failed programs alive, while plain background/nohup launches can be reaped by the Codex shell; a non-keepalive LaunchAgent survives launch but does not respawn after close.

May 23DECISION

Keep broadside orbit steering in AiPilotSystem

Orbit combat is simulation behavior, not render/gameplay glue; keeping it in the AI system makes it findable and keeps coordinate math consuming ship-pose basis vectors.

May 23DECISION

Default voxel debug to translucent overlay

The default visual proof needs damageable GLB triangles, voxel debug surfaces, and beam width to agree without making voxel-only debug captures transparent.

May 23DECISION

Plan damage rendering as derived render meshes

The simulation must stay deterministic and voxel-authoritative while the renderer becomes visually rich; the reviewed implementation checklist is [`DOC/SHIP-DAMAGE-RENDERING-PLAN.md`](SHIP-DAMAGE-RENDERING-PLAN.md).

May 23DECISION

Add retained wound history to damage plan

Live voxel state alone cannot reconstruct dramatic wounds after destroyed voxels detach or disappear; render history preserves visuals without becoming gameplay authority.

May 23DECISION

Build first retained damage renderer slice

The first damage-render slice needs to look like layered ship wreckage while keeping voxel state as gameplay truth and leaving triangle deletion as `debug-cull`.

May 23DECISION

Aggregate damage wound across chunks, cull interior-poking GLB

The first slice had per-voxel hot-edge dashes that read as a Minecraft grid, and the ship's own GLB tail-fin triangles (owner voxel alive but vertices in destroyed space) painted a pink "lightning bolt" through the wound regardless of how dark the cap was. Aggregating per-axis gives one authored-looking breach; vertex-level culling stops the GLB bleed-through; the cap stays per-voxel/dirty-region so simple beam hits still rebuild cheaply.

May 23DECISION

Remove energy shields from the universe

A shield bubble in front of the hull hides exactly the damage geometry the `Render/Damage/*` slice exists to show; the combat fiction is grounded in physical damage, not energy fields.

May 23DECISION

Gate full structural wound on cluster size; tiny hits scorch only

A 1-voxel beam hit generates a wound record on all 5–6 live face-neighbors of the destroyed voxel, so the unconstrained generator was painting 5–6 voxel-sized dark cavity backdrops around every tiny hit, they read as floating black "planes" instead of damage. Front-breach demo still looks the same because big wounds always have many records per cluster.

May 23DECISION

Default launch is an AI-vs-AI RTS battle with damage on the selected ship

User wants the default run to be the showcase. AI vs AI from above so battles unfold and damage is visible. Per-ship damage rendering for every combatant scaled badly on FNA/Metal (chunk rebuilds + buffer uploads × ships); selected-ship-only matches the original Render/Damage LOD plan and keeps the frame rate playable.

May 23DECISION

In-game HUD buttons for reset + runtime scenario switching

User wants to try different scenarios and visual states without restarting with new CLI flags every time. The architectural tenet keeps keyboard shortcuts as the primary entry (so headless scenarios and screenshot automation still work); buttons are a mouse-friendly mirror.

May 23DECISION

Voxel state is the truth for visual feedback, not the physics OBB

The user noticed beam glow floating multiple voxels past the actual hull, root cause was the visual endpoint reading the OBB sweep while the damage path correctly used the voxel raycast. Lining both up on voxel hit eliminates the gap and codifies the rule for every future "thing that visually touches a ship", missile splatters, decal positions, particle spawn origins.

May 23DECISION

Ship contact normal + point routed through voxel raycast

Audit of the physics layer found `PhysicsWorld.TryGetPairContactData` was setting contact normal to `(positionB - positionA).Normalize()` and contact position to the body midpoint, both broadphase placeholders that pretended every collision was sphere-to-sphere. Physics broadphase cannot know about voxel state; the simulation layer is the right place to refine. Same pattern as the beam-endpoint fix: physics for broadphase only, voxels for "what actually touched."

May 23DECISION

Coalesce shell triangle draws into one per-ship buffer

With the half-voxel-size default (~0.067u edge, 120k voxels/ship), the prior per-chunk `DynamicVertexBuffer` layout issued ~1,900 `SetVertexBuffer + DrawPrimitives` calls per ship per frame. FNA per-call CPU overhead summed to ~30ms/frame at 7 ships, dropping live frame rate from 60 to ~23 fps. Coalescing keeps the dirty-region rebuild benefit (only the affected slice re-uploads on damage) but stops paying for ~13,300 redundant draw calls every frame. Confirmed restore: live capture at tick 60 with 7 ships shows 60.9 fps, matching the pre-fine-voxel baseline.

May 23DECISION

Defer voxel topology BFS to end-of-batch

A beam at `BeamImpactRadius=2` queues ~125 destroys per hit. Under per-voxel BFS those flowed into `ShipVoxelStateStore.Apply` as a single ship-run, then ran ~125 full-ship BFS over the ~30k-voxel hull = ~3.75M neighbor visits per beam = ~107ms UPDATE_TICK spike per beam hit. FNA fixed-timestep catchup swallowed every Update, starving Draw to 1–2 fps during combat. Batch-end BFS cuts the per-beam cost to one ~30ms pass; live measurement holds 60 fps with 24% of a ship destroyed across 6 combatants.

May 23DECISION

Honor glTF PBR materials on ship draw

External authors (ninja-blender) need per-part color, metallic distribution, and emissive engine glows to survive the round-trip into the in-game render. Prior `GlbLoader` discarded materials entirely ("first mesh, first primitive", `Vector2.Zero` UV, no material read), and `RenderPipeline` overrode `DiffuseColor` with a hardcoded faction tint (cool steel for player, warm copper for AI). Per-primitive `GlbMaterialParams` + a per-part bind on BasicEffect is the minimum work delivering "the same ship reads as the same ship" without a custom shader pipeline. Approach picked after surfacing the toolchain tradeoff with the user as a v1/v2 split in DESIGN-LOG.md.

May 23DECISION

Read KHR_materials_emissive_strength from raw GLB JSON chunk

The user's original brief identified the strength multiplier as `material.extras["strength"]`, but the correction is that it's the standard glTF 2.0 extension `KHR_materials_emissive_strength`. Reading from the documented standard location is the right contract: any PBR-aware authoring tool (Blender, Three.js, Unity, Unreal) writes strength there. The earlier extras path would have silently returned 1.0 for every ninja-blender ship, ignoring authored emissive intensity.

May 23DECISION

Revert emissive strength to `material.extras["strength"]`

The standards-correct lookup is `extensions.KHR_materials_emissive_strength.emissiveStrength`, but ninja-blender dual-writes the same value into BOTH the standard extension AND `material.extras["strength"]`, exactly so projects on libraries that haven't exposed the extension publicly can read it without re-exporting. SharpGLTF 1.0.6 marks `MaterialEmissiveStrength` as `internal`; the prior raw-JSON workaround was correct but unnecessary given the dual-write contract. Reading extras directly is fewer lines and switches to the standard accessor with a one-line change when SharpGLTF exposes the extension.

May 23DECISION

DamageableGlbMeshAsset multi-primitive, retains node identity

Prior `DamageableGlbMeshAsset.Load` was locked to `LogicalMeshes[0].Primitives[0]`. Every incoming ninja-blender ship is multi-node + multi-primitive (one node per `module.*` part, one primitive per material), so under the old loader a new ship would render as a single sliver, whichever primitive happened to land first. The cold-launch showcase has to keep working when the new ship packs replace `Content/models/spaceship.glb`, and this is the load-bearing path for that.

May 23DECISION

GlbVoxelTriangle carries NodeName

Per-voxel component-kind assignment is currently a geometry heuristic; the sidecar promotes it to authored intent. The mapping table needs string-prefix matching on the originating node name for each triangle that voted for a voxel cell, so the data must be on the triangle when it reaches the builder. Adding the field now is one structural change; adding it later requires rewriting every triangle-construction site after the sidecar consumer is in flight.

May 23DECISION

Automated frame sequence capture and GIF compiler pipeline

Enables the AI agent swarm to automatically record and compile short, high-fidelity gameplay clips (voxel detaches, combat maneuvers, explosion patterns) for dev diaries without manual screen-recording.

May 23DECISION

Default launch is single-ship Dreadnought-style + hull binds GLB material

Three corrections layered into one commit: (1) StarGazer is Dreadnought-style single-player-ship combat, not RTS, the prior 6-AI overhead default mis-framed the game and pushed the player ship off-frame; (2) hardcoded faction tint destroyed the PBR work by overriding `DiffuseColor` to a saturated cyan/copper that the warm key light multiplied into a bright pink hull, masking every authored material; (3) PBR binding was wired only into the static draw path, not the damage-cook path the default scenario actually uses, so even after removing faction tint, GLB color wouldn't have read. Fixing all three together is what makes "the new stuff is visible when you `run`" actually true.

May 23DECISION

ShipPackSidecarReader implements the ninja-blender ship-pack v1.0 contract

Pre-positions the consumer side for multi-node ship packs the ninja-blender exporter now produces. The schema doc is the single source of truth across both repos, implementing it line-for-line (including the placeholder-substitution hash trick) keeps both sides in lockstep without re-deriving anything. Hash-chain hard-fails are the contract that catches partial writes, hand-edits, and pack files from different SceneIR generations before they manifest as voxel-mapping or material-rendering bugs further down the pipeline.

May 23DECISION

Migrate default ship path to Content/ships/spaceship/

Last step of the cross-repo migration the user laid out in the ninja-blender pipeline-update brief. The legacy `Content/models/spaceship.glb` was a single-material collapse of the 897-object SceneIR, keeping it as default would mean the PBR + multi-primitive + sidecar pipeline ships in the codebase but never actually fires in the default scene (the gate-features-behind-flags anti-pattern from `feedback_default_run_is_the_showcase`). Falling back to the legacy path means dev checkouts that haven't synced the new asset still launch cleanly.

May 23DECISION

Start the default run in combat

The current playable slice is proving voxel/GLB combat, so the first window should show enemy engagement without requiring hidden scenario flags or perfect timing.

May 23DECISION

Make run launch orbit combat

The user "run" loop should land directly in the playable test surface, not require remembering hidden flags or pressing Tab before camera input works.

May 23DECISION

Show voxel damage through the GLB by default

Voxels are intentionally invisible gameplay truth, but the normal playtest view must show their effects or combat looks like lasers hitting an indestructible model.

May 23DECISION

Treat default run as the showcase

The user run loop is the product review surface; hiding effects behind flags makes features look broken or absent.

May 23DECISION

Make lasers scar before they sever

Capital ship damage should read as progressive hull punishment; disappearing after a few hits feels like a render bug even when voxel topology is technically obeying core ownership.

May 23DECISION

Make enemy broadsides orbit the player

Moving the firing origin spreads laser hits across the voxel/GLB damage surface and makes the encounter feel like capital ships maneuvering around each other.

May 23DECISION

Show voxel truth over the GLB in default combat

Playtesting the voxel damage cornerstone needs the authored ship and the underlying damage grid visible together; oversized lasers made hits look wider than the damage they applied.

May 23DECISION

Replace triangle deletion with authored wound rendering

Capital-ship damage needs to expose structure and battle scars, not look like missing GLB triangles; full reviewed plan is [`DOC/SHIP-DAMAGE-RENDERING-PLAN.md`](SHIP-DAMAGE-RENDERING-PLAN.md).

May 23DECISION

Treat breaches as capital-ship interiors

A capital ship breach should read as a huge layered vessel being torn open, not a GLB shell with missing faces or flat caps.

May 23DECISION

Use layered wreckage as the damage bar

The damage cornerstone needs to feel like a huge ship being gutted, not a debug mesh disappearing; working art bible is [`DOC/DAMAGE-VFX-ART-BIBLE.md`](DAMAGE-VFX-ART-BIBLE.md).

May 23DECISION

Honor glTF PBR materials by mapping onto BasicEffect (v1, no custom shader)

External authors (ninja-blender) need to author per-part colors, metallic distribution, roughness, and emissive engine glows and have the in-game ship match the source preview color identity. The renderer was throwing every material away and re-tinting per faction (cool steel player / warm copper AI), so two visually-distinct authored ships read the same in-game. The "color identity matches" + "engines glow" goals only require per-part color, per-part specular character, and per-part emissive, none of which need a custom shader.

May 23DECISION

Tonemap target: ACES filmic at exposure 1.0

Ninja-blender web preview uses `THREE.ACESFilmicToneMapping` with exposure 1.0; matching tonemap end-to-end means per-part baseColor and emissive values authored in Blender will read the **same shade** in StarGazer as in the source preview. Without it, a red-orange engine glow can read pink-white because the shoulder curve differs. Tonemap choice has more visible impact on "does this ship look like its preview" than BRDF choice does, picking the matching one closes the largest source-of-truth gap.

May 23DECISION

Tonemap target: AGX (supersedes ACES choice)

AGX preserves bright chromatic edges around stars and thrusters better than ACES, important for a space-combat game where every visible HDR source (engines, beams, suns) sits over a dark background and the tonemap shoulder decides whether they "bloom and bleed" or "clip to a flat hot ring." This was the original analysis in the render architecture entry; the brief deviation toward ACES was driven by ninja-blender preview using ACES, but matching the planned architecture beats matching one external preview.

May 23DECISION

v1 IBL stand-in: asymptotic raise-the-blacks emissive lift

Bare PBR-via-BasicEffect renders the literal product of `baseColor * directLighting`, and for a baseColor authored expecting environment-map bounce (every modern ship tool, including ninja-blender's Three.js studio HDRI), the missing bounce leaves dark hulls reading pure black. The first `dotnet run` reaction was "why is the ship so dark?", the math confirmed it is authored that dark on purpose, expecting IBL to lift it. The `(1 - baseColor) * k` curve is the "raise the blacks" component of any HDR/tonemap workflow, max lift to surfaces with minimum direct-light response, zero lift to already-bright ones. This matches the qualitative behavior of an IBL irradiance map without requiring the cubemap or shader pipeline.

May 23DECISION

Trust contrast: tiny IBL floor + brighter key lights (supersedes the k=0.25 lift)

The k=0.25 lift was the wrong knob, for ninja-blender baseColor (linear ~0.014), a 0.25 emissive add is a ~17× boost that paints near-grey over near-black and erases the "dark hull" identity. The right principle per user feedback is "accept dark materials render dark in shadow, raise key-light intensity, trust contrast." With the bright key, lit faces read ~30-35% sRGB (dark but structured), shadow faces ~5% (truly dark, matches SceneIR), and metallic spec hot spots punch ~60% because they reflect the bright key/fill not the dark base.

May 23DECISION

Configure interactive combat launch defaults and broadside AI behavior

Grouping combat parameters under a central defaults registry guarantees consistent, predictable scenario initialization; the broadside-hold pilot keeps weapon alignment for high-fidelity combat feedback.

May 23POSTFIRST

Guts and Glow: Voxel Damage & Broadside AI

We watch exposed decks, ribs, and dark interior structural wounds take shape, tune laser carving, and face orbiting broadside enemies.

May 23POSTSHIPPED

Retained Damage Rendering & Zero-Alloc Billboards

Under the Hood: Technical deconstruction of procedural wound mesh generation and optimization of FNA billboard structures.

May 22DECISION

Canonicalize agent skills and split solo-main from worktree-branch commits

Codex agents need one visible skill home, and parallel work needs branch isolation without weakening the small-commit solo workflow.

May 22DECISION

Add world grid and scripted thrust for movement captures

The camera follows the ship, so motion needs a fixed reference frame and a non-manual way to drive movement during `--screenshot` runs.

May 22DECISION

Camera is lightweight render-side state with CLI modes

Agents need a fast, scriptable way to frame screenshots without polluting deterministic simulation or adopting a heavy camera/scene library.

May 22DECISION

Orbit camera remains render-side and scriptable

Orbiting is a viewing tool, not simulation state; keeping it render-side preserves determinism while letting agents and humans inspect ships from exact angles.

May 22DECISION

Camera default far clip keeps 10x space headroom

Space scenes need more draw-distance headroom than the first ship-framing camera; making the default deeper avoids clipping future world/grid/ship content during orbit inspection.

May 22DECISION

Engine systems use findable ownership boundaries

AI agents need obvious search targets and small ownership boundaries to extend camera, input, rendering, and sim systems without scattering behavior through the game loop.

May 22DECISION

Ship visual forward is world +Z

The model nose faces `+Z` and its visual right side faces `-X`, while the first mappings used XNA-style `-Z` and then world `+X`, making controls feel backward.

May 22DECISION

Ship motion starts as assisted Newtonian velocity

This gives space-like momentum without jumping straight to orbital mechanics or full rigid-body simulation.

May 22DECISION

Make thrust follow ship yaw

The early physics prototype needs momentum to respect where the bow points without jumping to a full six-degree rigid body simulation.

May 22DECISION

Keep vertical thrust on Shift

`Space` reads as a deliberate stop/brake command in this prototype, and `Shift` is easier to hold while combining vertical thrust with WASD.

May 22DECISION

Add target dummy instrumentation before weapons

Target/range/bearing feedback lets movement feel and approach geometry be tested before committing to weapon rules.

May 22DECISION

Kill stale game instances before launching

Hung FNA/dotnet windows accumulate invisibly and make testing confusing; one fresh process keeps verification and human playtests honest.

May 22DECISION

Roll joins ship-local orientation

Roll needs to affect thrust/debug vectors and broadside posture, not just visually tilt the model.

May 22DECISION

Ship physics has a named sim system

Physics will get tuned often, so agents need one obvious ownership file instead of editing the general sim loop.

May 22DECISION

Roll basis matches rendered ship roll

A render/sim sign mismatch made broadside arcs roll opposite the model, which breaks visual trust while testing weapons.

May 22DECISION

Coordinate conventions get a project skill

Recent roll and firing-arc fixes showed that ad hoc inverse/sign patches drift fast unless future agents identify coordinate spaces and ownership first.

May 22DECISION

Keep AGENTS as the instruction source

Multiple agents will touch StarGazer, and mirrored instruction files would drift as skills and workflow rules change.

May 22DECISION

Plan BEPU-backed body physics

Large numbers of colliding space objects need real broadphase/collision while gameplay systems still need clear ownership, coordinate boundaries, and agent-friendly extension points.

May 22DECISION

Extend physics plan for modules and constraints

Capital ships need child hit zones, detachable pieces, and possible articulated/docking/tether behavior without leaking BEPU handles or weakening PC/Mac/Linux portability.

May 22DECISION

Headless sim scenarios start physics migration

The BEPU/ECS migration needs a fast, windowless baseline for thrust, yaw, roll, brake, and fire behavior so architecture work does not silently change ship feel.

May 22DECISION

Benchmark body iteration before BEPU

The physics migration needs a cheap baseline for body iteration, event buffers, churn, and snapshot publication before final ECS handles or BEPU integration lock in structure.

May 22DECISION

Ship pose snapshots become the read model

Render, debug, and scenario code need to migrate to body snapshots incrementally so BEPU/ECS work can land without rewriting every consumer at once.

May 22DECISION

Validate physics assets before runtime bodies

Collider, mass, and response rules need a diffable contract before gameplay systems depend on them.

May 22DECISION

Keep BEPU behind StarGazer physics handles

The first runtime spike must prove body creation and missile sweep hits without letting backend types leak into the rest of the game.

May 22DECISION

Start collision slice headless

Ordered body events and debug records need to be stable before rendering them, or visual overlay work will paper over event ordering bugs.

May 22DECISION

Render physics debug from collision slice data

Visual physics debugging should consume the tested snapshot/event path, not a second renderer-only interpretation of physics state.

May 22DECISION

Prove compound detach before articulated joints

Detach and hit-zone semantics need stable headless outputs before BEPU constraints make failures harder to inspect.

May 22DECISION

Set first physics performance gates

The physics migration needs measurable platform gates before more BEPU, compound, and debug-overlay complexity lands.

May 22DECISION

Harden physics runtime API

Future agents need reusable physics primitives and lifecycle behavior before adding many object types, not spike-only helpers capped at 64 bodies.

May 22DECISION

Add a BEPU runtime benchmark gate

Scaling decisions need runtime collision-world numbers before adding pooling, multithreaded stepping, or more body systems.

May 22DECISION

Support static physics bodies behind BodyRef

Non-ship physics objects include immobile obstacles, stations, beacons, and environmental colliders; exposing BEPU static handles would split the agent-facing API.

May 22DECISION

Name physics runtime velocity units

Seconds-based BEPU integration and tick-based ship tuning coexist during migration, so the unit boundary must be visible in API names.

May 22DECISION

Keep BEPU shape ownership inside PhysicsWorld

Spawn/despawn churn should release backend shape bookkeeping while preserving the StarGazer `BodyRef` API boundary.

May 22DECISION

Serialize dotnet commands per worktree

MSBuild apphost and intermediate DLL writes race inside one `obj/` tree, producing false failures that waste agent time.

May 22DECISION

Route runtime queries through collision channels

Runtime collision behavior needs to use the same channel contract as authored physics assets, otherwise agents would reimplement filters per feature.

May 22DECISION

Make runtime query hits self-describing

Gameplay needs enough metadata to resolve hits without doing backend lookups or guessing which response path produced the hit.

May 22DECISION

Make playable physics consume PhysicsWorld

The BEPU migration needed to stop living only in CLI proofs so gameplay, debug overlays, weapons, and benchmarks exercise the same runtime body/event/query path agents will extend.

May 22DECISION

Wake controlled physics bodies on motion

BEPU can put idle bodies to sleep; without an explicit wake, ship rotation stayed responsive while translation appeared dead after waiting at launch.

May 22DECISION

Separate chase and orbit camera ownership

Tying the view target to a rotated model bounds center made real translation look like rotation-only movement, and shared arrow keys made camera/ship ownership ambiguous.

May 22DECISION

Harden live physics review gaps

The first gameplay migration exposed agent-review gaps that would scale poorly: next-tick query stamps, O(n^2) contact diffs, invalid values poisoning BEPU, and debug overlays that could silently truncate or allocate every frame.

May 22DECISION

Use raw runtime icon assets

FNA/SDL discovers the window icon by title-named PNG beside the executable, while raw icon derivatives keep the asset pipeline simple and cross-platform.

May 22DECISION

Deterministic AI pilot emits movement intent only

The first AI ship needs to exercise the same scalable sim path as the player while keeping deterministic replay and no-combat separation clean.

May 22DECISION

Promote ships into a dense control store

Scaling from one player body to many ships needs one agent-findable path that preserves BEPU collision response and avoids singleton fields or per-feature control code.

May 22DECISION

Add real-ship scale and determinism gates

Fleet gameplay needs evidence from the actual ship/control/AI/physics path before scaling visual debug or combat behavior. Multi-instance threading is the first safe threading gate because same-world parallel mutation belongs behind a deliberate `PhysicsWorld` worker-dispatch design.

May 22DECISION

Measure live frame metrics in the render HUD

Fixed sim ticks prove deterministic step cadence, but live playtesting needs a quick render-loop signal while AI/physics scale changes land.

May 22DECISION

Draw selected ship vectors from sim snapshots

Debug vectors must use the same sim-owned basis conventions as gameplay, not render-side matrix reconstruction. Keeping selection in the game layer avoids adding UI-only state to deterministic simulation.

May 22DECISION

Split ship scale profiles and benchmark phases

Old benchmark spawn layout mixed normal fleet scale with worst-case contact load, making it unclear whether cost came from AI/control, BEPU stepping, or contact publication. Sparse and dense profiles let agents compare normal space spread against deliberate contact stress.

May 22DECISION

Plan voxel ships as deterministic state over coarse physics bodies

One-body-per-voxel would not scale to hundreds/thousands of ships and would leak backend physics details into damage rules. Dense deterministic arrays give stable IDs, replayable damage ordering, cheap core-connectivity checks, and a clean path to future system tags without rebuilding colliders in the first slice.

May 22DECISION

Harden voxel plan around chunked and pooled performance paths

Earlier plan was directionally right but still allowed expensive fallback implementations: full-ship remeshes, full BFS for leaf damage, binary-search/hash lookup during DDA, per-ship heap islands, runtime GLB voxelization. Those would pass first screenshot while failing the "hundreds or thousands of ships" goal.

May 22DECISION

Define voxel construction, destruction, debris, and performance test gates

Voxel system can fail subtly in ways screenshots will not catch: floating source pieces, diagonal-only contact, impossible bounds, sliver-triangle cook explosions, bridge cuts leaving ownership bugs, split debris still responding to player/AI systems. Capturing these as CLI scenarios + benchmarks makes the cornerstone damage system testable before it gets too large.

May 22DECISION

Add synthetic voxel validation, split scenarios, and benchmark gates

Before GLB voxel cooking lands, topology and ownership rules need exact synthetic proofs. These tests catch disconnected construction, diagonal-only contact, core errors, leaf-vs-bridge topology behavior, and detached pieces accidentally remaining live/player/AI-owned.

May 22DECISION

Add CPU GLB voxel cook slice

Synthetic topology tests prove rules exactly, but the cornerstone feature also needs an early real-model proof that GLB traversal, coordinate conversion, density selection, validation, and cache boundaries work without touching render GPU buffers.

May 22DECISION

Add aggregate voxel test suite gate

Individual voxel probes are useful during development, but agents need one obvious command that catches construction, split/debris, DDA, performance smoke, and real-model cook regressions before committing later voxel work.

May 22DECISION

Record core-death hull as voxel debris

A killed ship must stop exposing live controllable/targetable voxels even when the final hit destroys only the core cell.

May 22DECISION

Hash voxel suite results by stable evidence

Performance output should still show elapsed/p95/p99 metrics, but those values cannot participate in repeatable scenario hashes.

May 22DECISION

Harden voxel headless gates before runtime integration

Runtime voxel wiring needs trustworthy automated gates; false determinism claims, zero-default cook options, or unbounded pre-validation allocations would hide the next real scaling bugs.

May 22DECISION

Attach voxel state to runtime ships

Voxel damage has to exercise the same sim-owned ship lifecycle as movement, AI, weapons, and hashes before render overlays or combat hit mapping can be trusted.

May 22DECISION

Add selected-ship voxel debug overlay

Voxel topology and core death need visible proof in the running game before combat hit mapping and chunked mesh rendering build on top.

May 22DECISION

Plan FNA-native HDR forward rendering pipeline

FNA Effect API caps at SM3 (no compute/geometry/tessellation), so the renderer must be built from pixel/vertex passes only; targeting Steam Deck (1.6 TFLOPS RDNA2, 1280×800) forces tight per-pass budgets and rules out TAA, FSR 2/3, and Forward+. Cheapest path to a modern look that survives all four supported backends (D3D11 + SDL_GPU → D3D12/Vulkan/Metal).

May 22DECISION

Fold Git LFS upload into the committed pre-push hook

`core.hooksPath=scripts/hooks` replaces Git LFS default `.git/hooks/pre-push` hook. That let Git commits containing screenshot pointer files reach GitHub while the binary LFS objects stayed only in the local checkout, causing fresh clones to fail during smudge with 404 missing-object errors.

May 22DECISION

Render voxel debug cells at cooked GLB density

Previous runtime overlay used the synthetic 27-cell cube and a radius-derived display scale, making the voxel system look much coarser than the GLB cook actually supports. Explicit debug cooking keeps normal startup free of GLB voxelization while giving honest density proof.

May 22DECISION

Route beam hits through ship-local voxel DDA

Voxel damage has to come from actual weapon rays, not hand-entered voxel ids. Keeping BEPU at the coarse-body layer preserves the one-body-per-ship scaling rule while DDA gives deterministic, allocation-free per-voxel targeting.

May 22DECISION

Prove sparse voxel DDA crosses empty cells without allocation

Capital ships will be sparse and beams can traverse long empty spans before hitting damage cells. The hot path must stay bounded and allocation-free before weapon volume increases.

May 22DECISION

Add aggregate voxel state slots with promotion on demand

The game needs hundreds/thousands of physics objects, but only selected/combat-near ships should carry detailed voxel state. Gives sim an explicit ownership boundary for fleet LOD without changing default gameplay, which still uses detailed state unless a cap/scenario opts in.

May 22DECISION

Add detailed voxel state scale ladder

Fleet LOD proves far ships can stay aggregate, but combat tuning also needs a clean headless ladder for fully detailed ships before broadside/explosion command volumes rise.

May 22DECISION

Report detailed voxel caps in sim scenarios

Agents need a direct headless signal that fleet LOD caps are active in the real simulation, not only in synthetic state runners and benchmark counters.

May 22DECISION

Apply voxel damage by sorted command runs

Broadside and explosion damage should scale with command runs, not `shipCount * commandCount`. The sorted command buffer already provides target grouping; the store should preserve that shape.

May 22DECISION

Resolve same-tick voxel detaches inside command runs

The damage transaction path must match gameplay ownership rules: once an island detaches from the core, later commands in that ordered run cannot keep damaging it as live ship hull.

May 22DECISION

Report real voxel topology and LOD benchmark metrics

Voxel splitting performance needs real per-tick topology and LOD signals before the pending-topology queue exists, or scale regressions can hide behind aggregate totals and synthetic counters.

May 22DECISION

Guard aggregate voxel ships against invalid promotion

Fleet-scale LOD only works if debug, benchmark, and combat misses do not accidentally promote far ships into detailed HP/alive/BFS arrays.

May 22DECISION

Read aggregate voxel core ids without promotion

Debug and scripted damage need asset metadata, but metadata reads should not defeat aggregate fleet LOD before the damage transaction actually lands.

May 22DECISION

Test killed voxel ships through runtime control systems

Debris/kill ownership rules need coverage through runtime input and AI systems, not only metadata on synthetic debris records.

May 22DECISION

Extract render pipeline scaffold from Game1.Draw

Pre-Phase-0 Draw method had become a 162-line dump mixing scene, HUD, and physics-debug rendering; subsequent visual phases (HDR fp16 RT, AGX tonemap, bloom, lens flare, FSR1, PBR, CSM, WBOIT) need a single seam to layer onto without re-disturbing `Game1`.

May 22DECISION

Mark detached voxel debris without drawing dead hull

Debug visuals must reflect live ownership: removed islands are no longer part of the ship, but agents still need a visible marker proving where the split happened.

May 22DECISION

Use apphost launcher for live game runs

Backgrounded `dotnet run` leaves a parent/child process pair and plain `nohup` launches can die when the agent shell session ends; launchd owns the app after command exit, but must be pointed back at the repo root so relative dev paths like `obj/voxel-cache` do not resolve under `/`.

May 22DECISION

Add live voxel damage demo

Voxel correctness screenshots were useful, but the working loop needs an obvious live demo for watching damage progress over time.

May 22DECISION

Render voxel debug cells as filled blocks

Wire cubes proved topology but were unreadable as gameplay visuals; exposed faces remove the fake gaps and make voxel damage and splits legible in the live demo.

May 22DECISION

Cache voxel debug rendering by dirty chunks

Full per-draw voxel surface rebuilds were fine for proof but wrong for the cornerstone damage system; chunk versions give render a cheap dirty boundary while keeping coordinate conversion owned by `ModelToShipVoxelSpace`.

May 22DECISION

Drive GLB mesh damage from voxel state

Voxels should remain the gameplay truth, but the visible ship must preserve the authored GLB silhouette and lose mesh regions instead of becoming visible blocks.

May 22DECISION

Add programmatic GLB breach demo

Agents need a one-flag live/screenshot state for GLB mesh damage instead of brittle hand-written lists of voxel damage commands.

May 22DECISION

Share GLB breach damage patterns with tests

Programmatic visual states need headless guards so agents can tune camera/rendering without silently drifting the underlying damage scenario.

May 22DECISION

Route ship lasers through voxel damage

Beam rendering and voxel hit mapping already existed, but gameplay needed one owner that could connect ship targeting, damage transactions, GLB mesh removal, and HUD life values without render-side state.

May 22DECISION

First weapon is a broadside beam gate

Capital ships should reward bearing and positioning before twitch aim; the beam gives immediate feedback while weapon art/damage rules are still fluid.

May 22DECISION

Make prototype thrust visibly readable

The physics-backed ship was technically moving but too subtly against the follow camera and large grid for live playtesting to trust the controls.

May 22DECISION

Default flight view is ship-local chase

Translational motion needs to stay readable during live playtesting, a visual-center follow camera can hide movement on a capital ship because rotation changes the camera target.

May 22DECISION

First AI gameplay slice is no-combat flight

This proves ownership, control, collision, camera framing, and debug surfaces before layering fighting, targeting, damage, or fleet behaviors on top.

May 22DECISION

Compact runtime HUD around performance first

The previous debug readout exposed useful values but behaved like an unstructured dump, performance needs to be visible while playtesting scale, and the rest should be scannable without covering the playfield.

May 22DECISION

Ship selection is keyboard and CLI addressable

Fleet-scale debugging needs a stable way to inspect one ship at a time without mouse-only picking; keeping chase player-owned preserves the flying camera while orbit becomes the inspection camera.

May 22DECISION

Capital ships become core-rooted damageable voxel structures

Damageable capital ships are a cornerstone fantasy, positioning and weapon impacts should matter spatially, not just subtract from a single health bar. A core-rooted graph gives readable rules, supports future "real ship systems," and keeps disconnected wreckage behavior deterministic for the first slice.

May 22DECISION

Detached voxel islands become debris, not ship

Capital-ship damage needs to feel physical without letting severed pieces keep acting like the player ship, and this gives the sim a crisp ownership boundary for future salvage, collision debris, visual wreckage, and system detachment.

May 22DECISION

Core death sheds remaining hull ownership

Core death is the same ownership boundary as bridge severing, once the root is gone, no surviving hull piece should keep behaving like the ship.

May 22DECISION

Adopt cinematic space-opera aesthetic

The "modern beautiful space game" target needs a single aesthetic compass so per-effect technique choices stop being relitigated. Cinematic space-opera fits capital-ship combat's slow tactical pacing, gives every system biome a strong identity, and survives the FNA SM3 rendering ceiling without compute-required modern looks.

May 22DECISION

Tune flight around capital-ship commitment

Capital ships should feel massive because repositioning is a decision with momentum, not because top speed is tiny.

May 22DECISION

Put primary turning on A/D

For capital-ship handling, left/right on the home cluster should command heading commitment first, strafe is secondary maneuvering thrust.

May 22DECISION

Use voxels as invisible damage truth

Capital ships need authored silhouettes and readable art direction while voxels give spatial damage and scaling, showing raw blocks as the main ship undercuts the fantasy.

May 22DECISION

Make lasers the first live voxel combat proof

This proves the cornerstone loop in play, weapons, ship ownership, voxel damage, life, debris, and GLB mesh removal, without waiting for turrets, reactors, armor types, or full combat AI.

May 22POSTSHIPPED

Newtonian Flight and Weapon Gating

We integrate assisted Newtonian momentum, implement roll controls, and implement broadside weapon firing arcs.

May 22POSTSHIPPED

Newtonian Basis Vectors & BEPUphysics Integration

Under the Hood: Deconstruction of ship-local thrust transformations and details of BEPUphysics v2 integration with dense scaling benchmarks.

May 21DECISION

Vendor FNA instead of MonoGame NuGet

User wants full ownership of the runtime, returning to indie roots from XNA days, ability to patch FNA source directly without coordinating upstream. The MIT / MS-PL licenses are preserved under `FNA/licenses/` and `FNA/lib/*/LICENSE` for Steam release.

May 21DECISION

Target .NET 8

Current LTS, fully cross-platform, native ARM64 on Apple Silicon, no Mono dependency. FNA `FNA.Core.csproj` already targets net8.0 so we inherit cleanly.

May 21DECISION

SDL3, not SDL2, as the FNA platform layer

This is what current `fnalibs-dailies` ships (no SDL2 binary in 2026 builds). FNA auto-detects the available platform at runtime and prefers SDL3. SDL3 has better controller, HiDPI, and Wayland support, all relevant for SteamDeck and Mac.

May 21DECISION

Native libs laid out as runtimes/&lt;rid&gt;/native/

Idiomatic .NET layout. Honors `dotnet publish -r <rid>` for cross-platform publishing while falling back to the host RID for `dotnet run`. The D3D12 Agility SDK folder structure is preserved on Windows because FNA3D refuses to load it otherwise.

May 21DECISION

Use SharpGLTF.Core via NuGet for GLB loading

Canonical .NET glTF library, actively maintained, pure managed code (no native deps). Small utility scope. NuGet is appropriate here. The vendor-source rule is strongest for framework-level deps like FNA, not for utilities.

May 21DECISION

Main-only git workflow with auto commit + push

Solo developer, no team to coordinate. Multi-machine work (Windows + Mac travel) needs frequent pushes to avoid stranded uncommitted state. Always-green main + small commits gives natural rollback points.

May 21DECISION

Adopt DOC/ decision-log discipline

Vibe-coded projects lose decision reasoning between sessions because the user did not physically type the code. The log is the durable memory the future-us reads first when reopening the repo.

May 21DECISION

Defer text rendering library choice; "Hello World" via window title only

`FontStashSharp.XNA` 1.5.5 only targets .NET Framework 4.8 and dragged in original Microsoft XNA reference assemblies, producing CS0433 type-conflict errors against vendored FNA. `FNA.NET.FontStashSharp` would conflict in reverse (pulls competing FNA NuGet). The clean path exists (`PlatformAgnostic` + custom adapter) but is a focused workstream, not scaffold-verification work. Choosing a font library before knowing kerning/bitmap/rich-text/localization needs would lock in the wrong tool.

May 21DECISION

Adopt FontStashSharp.PlatformAgnostic + custom FNA renderer for text

PlatformAgnostic has no XNA/MonoGame transitive dep, so it composes cleanly with vendored FNA. The ~70-line custom renderer is the price of vendored FNA, small, owned, no drift. FontStashSharp does runtime TTF rasterization so we swap typefaces without a content build step.

May 21DECISION

In-process screenshot via RenderTarget2D + `--screenshot` CLI flag

Implements the screenshot half of the [programmatic-control tenet](DESIGN-LOG.md), game is its own screenshot source, no external tools, no OS permission gates, identical Win/Mac/Linux behavior. Render-to-target is also the foundation for post-processing (bloom, grading, tonemap, UI compositing); paying that cost now is cheaper than retrofitting later.

May 21DECISION

First ship renders: GLB loader extended for normals + BasicEffect render path

Loader existed but nothing rendered, lighting needed normals, which forced upgrading vertex format from `VertexPositionColor` to `VertexPositionNormalTexture`. `BasicEffect` is FNA's built-in fixed-function effect: zero HLSL, zero asset pipeline, "looks 3D" free. Bounding-sphere framing makes `--model <path>` auto-frame any GLB without per-model camera tuning. The `--model` flag applies the programmatic-control tenet.

May 21DECISION

Cull clockwise, not counter-clockwise, for glTF + FNA right-handed projection

glTF winds front-facing triangles counter-clockwise from outside. FNA's `CreateLookAt` + `CreatePerspectiveFieldOfView` are right-handed and preserve CCW into NDC. `CullCounterClockwise` therefore culls the *front* faces, `CullClockwise` is the correct setting.

May 21DECISION

Roguelike structure (procedural runs, not scripted campaign)

Solo indie scope: replay value scales with procedural systems while content authoring stays manageable, and the roguelike loop fits "one more run" sessions which is how the user actually plays games while traveling.

May 21DECISION

3D space combat centered on capital ships

Stated game concept from project inception; capital ships afford strategic-feel combat (positioning, broadside arcs, subsystem targeting) over twitch reflexes, aligning with the roguelike "tactical decision" loop more than a flight-sim arcade loop.

May 21DECISION

GLB as the ship model format

Modern open format, widely supported in DCC tools (Blender, etc.); avoids the dead XNA-era `.fbx` → `.xnb` Content Pipeline; runtime loading enables data-driven ship variants without rebuild.

May 21DECISION

Target PC, Mac, Linux (incl. SteamDeck)

SteamDeck is a strategic platform for indie space games, controller-friendly, growing audience, lower competition; Mac coverage enables continued work on a Mac during travel; Linux is essentially free with the FNA+SDL3 stack.

May 21DECISION

Programmatic control of every game state is a core tenet

Four compounding reasons: AI-driven verification needs programmatic state access for automated screenshots and scenario runs; dev velocity collapses if reproducing a bug takes 90 seconds of menu-clicking instead of a `--scenario` flag; the same affordances let players write mods/replay/speedrun tools later; and forcing programmatic reach makes the sim explicitly expose its state transitions (easier to test, more predictable). Retrofitting onto a finished UI is a multi-week refactor, menu state machines, animation skippers, save/load headless paths all have to be threaded through every screen, but designing it in from feature one is essentially free.

May 21DECISION

Determinism in the simulation layer is a core tenet

Replay unlocks bug-repro-by-file (user sends a 200-byte replay, you reproduce locally), automated scenario regression (replay → diff every Nth frame), AI-agent verification loops, and modding/speedrun infrastructure, and these compound forever atop a deterministic sim. Retrofit cost is brutal (every system touching randomness/time/IO has to be audited and rewritten), so adopting the discipline now while sim code is empty is essentially free. The cost gap between "do this from day 1" and "add this in year 2" is multiple orders of magnitude.

May 21DECISION

Parallelizable by design, multi-agent, multi-instance from day 1

The user wants multiple Claude Code agents running in parallel (e.g., one in worktree A, another in worktree B, screenshot diff comparing). That only works if the codebase has no hidden contention surface, no shared file someone forgot about, no global cache that gets corrupted, no log file two agents fight over. Building this in from day 1 costs nothing; bolting it on after the first global-state bug is brutal, you find them one race at a time over months.

May 21DECISION

Asset pipeline: raw files at runtime, no packing until shipped game needs it

Projected asset footprint is modest, small fleet of ship GLBs, modest textures, conventional SFX/music, low hundreds of MB at ship. In that size class, the iteration-speed benefits of raw files (no build latency, trivial future hot-reload, every agent can write any asset in any worktree without tooling, dev environment matches ship) outweigh distribution-size and cold-start benefits of packing. Stating "no packing" as principle now prevents drift into "just add a small manifest" creep that compounds into a pipeline rewrite a year in.

May 21DECISION

Determinism tenet refinements after expert review

Determinism specialist review (2026-05-21) named these as the 30% the original 7 rules missed, the parts that actually break in shipped lockstep games. Cheap to add to the tenet text now while sim code is still empty.

May 21DECISION

Run shape: short FTL-style runs, hard perma-death, procedural fleets + sectors, no meta-progression

Short runs + hard perma-death gives the "one more run" loop that fits travel-on-Mac sessions, matches solo-dev scope (no meta-DB), and lets each run carry real stakes. Procedural-fleet-on-fixed-roster keeps content authoring tractable.

May 21POSTFIRST

First Transmissions: Vendored FNA Scaffolding

We vendor FNA, construct native runtime paths, and set up our development environment to render our first capital ship model.

May 21POSTSHIPPED

FNA Native Scaffolding & Dynamic Font Rendering

Under the Hood: Details of the RID-aware native deployment build pipelines and implementing runtime TTF vector font generation via FontStashSharp.