Aetheria/tests/Aetheria.Shared.Tests/ShipFixture.cs
Meta Vens 1702e0549f Ship S1: one hull record, and a hull names its visual
A mod ship file now holds a HullData at mod-hull:<id> and the ShipAuthoring
visual package at mod-ship:<id>. HullData.Visual (key 32) is the typed ref
between them; ShipAuthoring.Hull (key 1) is retired.

- One joint validator over (hull, visual) is the only semantic check; Load
  owns the file structure (one hull, one visual, deterministic keys).
- Compose copies the mod store's records verbatim, so the derived catalog
  holds one hull per mod ship.
- ship_cc.py edits the hull record's slots directly; the body[HULL_SLOT]
  indirection is gone.
- The package fixture is generated from ShipAuthoringTests.Fixture() (glTF
  scene included) and exported when AETHERIA_SHIP_FIXTURE_DIR is set.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 03:06:43 +02:00

54 lines
2.5 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Text;
// Writes the mod package the tests, the Python round-trip check and the Unity smoke (ShipModPreview.Smoke) share. It is
// generated from ShipAuthoringTests.Fixture() so no opaque bytes are committed.
internal static class ShipFixture
{
public static readonly string[] Nodes = { "map", "collider", "shield", "tractor", "thruster-port" };
// The GLB carries a glTF scene naming every node: glTFast instantiates a scene, and a scene-less file has none.
public static string GlbJson(string[] nodes) =>
@"{""asset"":{""version"":""2.0""},""scene"":0,""scenes"":[{""nodes"":[" + string.Join(",", nodes.Select((_, index) => index)) + @"]}],""nodes"":[" +
string.Join(",", nodes.Select(node => $@"{{""name"":""{node}"",""extras"":{{""aetheria.id"":""{node}""}}}}")) + "]}";
public static byte[] Glb(string json)
{
var body = Encoding.UTF8.GetBytes(json);
var padded = new byte[(body.Length + 3) / 4 * 4];
Array.Fill(padded, (byte)' ');
body.CopyTo(padded, 0);
var bytes = new byte[20 + padded.Length + 8];
BitConverter.GetBytes(0x46546C67u).CopyTo(bytes, 0);
BitConverter.GetBytes(2u).CopyTo(bytes, 4);
BitConverter.GetBytes((uint)bytes.Length).CopyTo(bytes, 8);
BitConverter.GetBytes((uint)padded.Length).CopyTo(bytes, 12);
BitConverter.GetBytes(0x4E4F534Au).CopyTo(bytes, 16);
padded.CopyTo(bytes, 20);
BitConverter.GetBytes(0u).CopyTo(bytes, 20 + padded.Length);
BitConverter.GetBytes(0x004E4942u).CopyTo(bytes, 24 + padded.Length);
return bytes;
}
// <modsRoot>/<id>/ship.cc plus the GLB at the record's ModelAsset path.
public static string WritePackage(string modsRoot, string id, string hullName = "Skiff", string[] nodes = null,
string modelAsset = "skiff.glb", Action<ShipParts> tweak = null)
{
var ship = ShipAuthoringTests.Fixture();
tweak?.Invoke(ship);
ship.WithId(id);
ship.Hull.Name = hullName;
ship.Visual.ModelAsset = modelAsset;
var directory = Path.Combine(modsRoot, id);
Directory.CreateDirectory(directory);
using (var cache = ShipAuthoringStore.Open(Path.Combine(directory, "ship.cc"), writable: true))
{
ShipAuthoringStore.Write(cache, ship.Hull, ship.Visual);
cache.FlushAsync().Wait();
}
File.WriteAllBytes(Path.Combine(directory, modelAsset), Glb(GlbJson(nodes ?? Nodes)));
return directory;
}
}