Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Text.RegularExpressions;
|
2026-09-30 03:06:43 +02:00
|
|
|
using GameCult.Caching;
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
using GameCult.Caching.MessagePack;
|
|
|
|
|
using MessagePack;
|
|
|
|
|
using Xunit;
|
|
|
|
|
|
2026-09-30 03:06:43 +02:00
|
|
|
// tools/blender/aetheria_ships/ship_cc.py edits a ship file's HullData and ShipAuthoring records by MessagePack slot
|
|
|
|
|
// number and by hardpoint member position. These tests bind that file to the C# types that own the numbers: any renumbered, renamed or
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
// reordered member, enum value, or nested array shape fails here instead of corrupting a ship the next time Blender saves.
|
|
|
|
|
public sealed class ShipSchemaPinTests
|
|
|
|
|
{
|
|
|
|
|
private static readonly string Source = File.ReadAllText(Locate("tools", "blender", "aetheria_ships", "ship_cc.py"));
|
|
|
|
|
|
|
|
|
|
[Theory]
|
2026-10-03 17:07:10 +02:00
|
|
|
[InlineData("MODEL_ASSET_SLOT", typeof(ShipAuthoring), nameof(ShipAuthoring.ModelAsset))]
|
|
|
|
|
[InlineData("ANCHORS_SLOT", typeof(ShipAuthoring), nameof(ShipAuthoring.Anchors))]
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
[InlineData("SCHEMATIC_LINES_SLOT", typeof(ShipAuthoring), nameof(ShipAuthoring.SchematicLines))]
|
|
|
|
|
[InlineData("HULL_SHAPE_SLOT", typeof(HullData), nameof(HullData.Shape))]
|
|
|
|
|
[InlineData("HULL_HARDPOINTS_SLOT", typeof(HullData), nameof(HullData.Hardpoints))]
|
|
|
|
|
public void PythonSlotConstantsAreTheKeysOfTheMembersTheyNameInTheirComments(string constant, Type owner, string member)
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(KeyOf(owner, member), Integer(constant));
|
|
|
|
|
Assert.Matches(new Regex($@"^{constant} = \d+ # \w+\.{member}\r?$", RegexOptions.Multiline), Source);
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-30 03:06:43 +02:00
|
|
|
[Fact]
|
|
|
|
|
public void PythonSchemaNamesAreTheDocumentsTheyNameAndTheRetiredSlotIsNotReused()
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(typeof(ShipAuthoring).GetCustomAttribute<CultDocumentAttribute>().SchemaName, Text("SCHEMA"));
|
|
|
|
|
Assert.Equal(typeof(HullData).GetCustomAttribute<CultDocumentAttribute>().SchemaName, Text("HULL_SCHEMA"));
|
Ship S1 fixes: refuse the retired embedded hull, one body per hull, closed validator gaps
Load reads the raw ship authoring payload and refuses a non-nil retired key 1 (the
pre-S1 embedded hull) naming the old shape, so a stale hull is no longer silently
ignored; ship_cc.py refuses the same file on read and therefore on every save.
A hull naming both a Unity prefab and a visual is refused by one predicate
(ShipAuthoringStore.RequireOneBody), called by Validate and by Compose over every hull
in the derived catalog, shipped ones included; a whitespace-only Prefab is unset. This
replaces the mod-ship-only "cannot name a Unity prefab" check.
The validator also refuses non-finite or negative hardpoint Armor and FiringArc, an
undefined HardpointType or ItemRotation, and an anchor Role outside the eight the
schema documents.
Load wraps MessagePack decode failures with the file path. Python replace_layout
refuses non-boolean layout and hardpoint-footprint cells.
Tests: exact Visual key comparison, HullData.Visual at key 32, a save refreshing
stored_at, plus one case per new refusal. HullData.Visual is now [Inspectable] like
Prefab (Unity editor only; not compiled here).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:18:20 +02:00
|
|
|
Assert.Equal(1, Integer("RETIRED_HULL_SLOT"));
|
2026-09-30 03:06:43 +02:00
|
|
|
// Key 1 held the embedded hull until S1. A later member must not take it, or an old file would decode as it.
|
|
|
|
|
Assert.DoesNotContain(typeof(ShipAuthoring).GetMembers(BindingFlags.Public | BindingFlags.Instance),
|
|
|
|
|
member => member.GetCustomAttribute<KeyAttribute>()?.IntKey == 1);
|
|
|
|
|
}
|
|
|
|
|
|
Ship S1 fixes: refuse the retired embedded hull, one body per hull, closed validator gaps
Load reads the raw ship authoring payload and refuses a non-nil retired key 1 (the
pre-S1 embedded hull) naming the old shape, so a stale hull is no longer silently
ignored; ship_cc.py refuses the same file on read and therefore on every save.
A hull naming both a Unity prefab and a visual is refused by one predicate
(ShipAuthoringStore.RequireOneBody), called by Validate and by Compose over every hull
in the derived catalog, shipped ones included; a whitespace-only Prefab is unset. This
replaces the mod-ship-only "cannot name a Unity prefab" check.
The validator also refuses non-finite or negative hardpoint Armor and FiringArc, an
undefined HardpointType or ItemRotation, and an anchor Role outside the eight the
schema documents.
Load wraps MessagePack decode failures with the file path. Python replace_layout
refuses non-boolean layout and hardpoint-footprint cells.
Tests: exact Visual key comparison, HullData.Visual at key 32, a save refreshing
stored_at, plus one case per new refusal. HullData.Visual is now [Inspectable] like
Prefab (Unity editor only; not compiled here).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 06:18:20 +02:00
|
|
|
// HullData.Visual is the one binding between a hull and its visual; its slot is the wire contract.
|
|
|
|
|
[Fact]
|
|
|
|
|
public void HullVisualIsKey32()
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(32, KeyOf(typeof(HullData), nameof(HullData.Visual)));
|
|
|
|
|
var taken = typeof(HullData).GetMembers(BindingFlags.Public | BindingFlags.Instance)
|
|
|
|
|
.Select(member => member.GetCustomAttribute<KeyAttribute>()?.IntKey).Where(key => key != null).ToArray();
|
|
|
|
|
Assert.Equal(taken.Length, taken.Distinct().Count());
|
|
|
|
|
}
|
|
|
|
|
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
[Fact]
|
|
|
|
|
public void PythonHardpointMembersAreHardpointDataMembersAtTheirOwnPositions()
|
|
|
|
|
{
|
|
|
|
|
var members = Strings("HARDPOINT_MEMBERS");
|
|
|
|
|
Assert.Equal(7, members.Length);
|
|
|
|
|
for (var slot = 0; slot < members.Length; slot++)
|
|
|
|
|
Assert.Equal(slot, KeyOf(typeof(HardpointData), members[slot]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
2026-10-03 17:07:10 +02:00
|
|
|
public void PythonAnchorMembersAreShipAnchorMembersAtTheirPositions()
|
|
|
|
|
{
|
|
|
|
|
var members = Strings("ANCHOR_MEMBERS");
|
|
|
|
|
Assert.Equal(5, members.Length);
|
|
|
|
|
for (var slot = 0; slot < members.Length; slot++)
|
|
|
|
|
Assert.Equal(slot, KeyOf(typeof(ShipAnchor), members[slot]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
public void PythonEnumNamesAreTheEnumsInValueOrder()
|
|
|
|
|
{
|
|
|
|
|
Assert.Equal(EnumNames<HardpointType>(), Strings("HARDPOINT_TYPE_NAMES"));
|
|
|
|
|
Assert.Equal(EnumNames<ItemRotation>(), Strings("ROTATION_NAMES"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[Fact]
|
|
|
|
|
public void SerializedShipHasTheNestedArrayShapesPythonIndexes()
|
|
|
|
|
{
|
2026-09-30 03:06:43 +02:00
|
|
|
var (hull, visual) = ShipAuthoringTests.Fixture();
|
|
|
|
|
var options = CultDocumentMessagePackSerialization.OptionsFor(typeof(ShipAuthoring).Assembly);
|
|
|
|
|
object[] Body(object document) => Assert.IsType<object[]>(MessagePackSerializer.Deserialize<object>(
|
|
|
|
|
MessagePackSerializer.Serialize(document.GetType(), document, options), MessagePackSerializerOptions.Standard));
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
|
2026-09-30 03:06:43 +02:00
|
|
|
var body = Body(visual);
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
Assert.Equal("mod.skiff", body[0]);
|
2026-09-30 03:06:43 +02:00
|
|
|
var lines = Assert.IsType<object[]>(body[Integer("SCHEMATIC_LINES_SLOT")]);
|
|
|
|
|
Assert.Equal("Hull", Assert.IsType<object[]>(Assert.Single(lines))[0]);
|
|
|
|
|
|
|
|
|
|
var hullBody = Body(hull);
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
// A schematic is [[width, height, cells]] with cells flattened x-major (index x * height + y).
|
2026-09-30 03:06:43 +02:00
|
|
|
var hullShape = Assert.IsType<object[]>(Assert.IsType<object[]>(hullBody[Integer("HULL_SHAPE_SLOT")])[0]);
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
Assert.Equal(new object[] { 2, 2 }, hullShape.Take(2).Select(value => (object)Convert.ToInt32(value)));
|
|
|
|
|
Assert.Equal(new[] { true, false, true, false }, Assert.IsType<object[]>(hullShape[2]).Cast<bool>());
|
|
|
|
|
|
2026-09-30 03:06:43 +02:00
|
|
|
var hardpoints = Assert.IsType<object[]>(hullBody[Integer("HULL_HARDPOINTS_SLOT")]);
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
var row = Assert.IsType<object[]>(Assert.Single(hardpoints));
|
|
|
|
|
var members = Strings("HARDPOINT_MEMBERS");
|
|
|
|
|
Assert.True(row.Length >= members.Length);
|
|
|
|
|
Assert.Equal("thruster.port", row[Array.IndexOf(members, "Transform")]);
|
|
|
|
|
var footprint = Assert.IsType<object[]>(Assert.IsType<object[]>(row[Array.IndexOf(members, "Shape")])[0]);
|
|
|
|
|
Assert.Equal(3, footprint.Length);
|
|
|
|
|
Assert.Equal(new[] { true }, Assert.IsType<object[]>(footprint[2]).Cast<bool>());
|
|
|
|
|
Assert.Equal(new[] { 1, 0 }, Assert.IsType<object[]>(row[Array.IndexOf(members, "Position")]).Select(value => Convert.ToInt32(value)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int KeyOf(Type owner, string member)
|
|
|
|
|
{
|
|
|
|
|
var found = owner.GetMember(member, BindingFlags.Public | BindingFlags.Instance).Single();
|
|
|
|
|
return Assert.IsType<int>(found.GetCustomAttribute<KeyAttribute>().IntKey);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static int Integer(string constant) =>
|
|
|
|
|
int.Parse(Regex.Match(Source, $@"^{constant} = (\d+)", RegexOptions.Multiline).Groups[1].Value);
|
|
|
|
|
|
2026-09-30 03:06:43 +02:00
|
|
|
private static string Text(string constant)
|
|
|
|
|
{
|
|
|
|
|
var match = Regex.Match(Source, $@"^{constant} = ""([^""]*)""", RegexOptions.Multiline);
|
|
|
|
|
Assert.True(match.Success, $"{constant} is not a string in ship_cc.py");
|
|
|
|
|
return match.Groups[1].Value;
|
|
|
|
|
}
|
|
|
|
|
|
Give the Blender side tests and pin its hand-copied schema to C# (S-Soul F3)
ship_cc.py edited a ShipAuthoring record by bare slot numbers and hardpoint
positions (hp[3], hp[7:], raw[0..6]), and __init__.py copied the
HardpointType and ItemRotation enums by position. Nothing tested either
file and nothing tied the numbers to the C# [Key] attributes that own them.
- ship_cc.py names its schema facts once: the four record slots (each
commented with the C# member it mirrors), HARDPOINT_MEMBERS (HardpointData
member names in slot order), and the two enum name tuples. Hardpoint rows
are built and read through encode_hardpoint/decode_hardpoint; replace_layout
and the Blender operators no longer index a row by literal. __init__.py
builds its enum items from the tuples. The rotation label
"Counterclockwise" becomes "CounterClockwise", matching the C# member.
- tools/blender/tests/test_ship_cc.py (30 unittest cases, bpy-free, needs
CultLib's cultcache-py and msgpack): read, replace_lines, read_layout,
replace_layout (untouched slots and later-schema hardpoint slots preserved
by mount id, schema catalog preserved, every refusal leaves the file
byte-identical), the row codec, and capture_grease_pencil against stand-in
objects.
- ShipSchemaPinTests (C#, 7 cases) reads ship_cc.py and fails when a slot
constant, a HARDPOINT_MEMBERS name or position, or an enum name/value
disagrees with the C# types, and when a serialized ship no longer has the
nested array shapes the Python indexes (schematic [[w, h, cells]], cells
x-major, hardpoint transform and footprint positions).
Reading the slots from the file's schema catalog instead was rejected: the
catalog lists only document members, and Hull, Shape and HardpointData are
nested types it does not describe.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 23:55:46 +02:00
|
|
|
private static string[] Strings(string constant)
|
|
|
|
|
{
|
|
|
|
|
var block = Regex.Match(Source, $@"^{constant} = \((.*?)\)\r?$", RegexOptions.Multiline | RegexOptions.Singleline);
|
|
|
|
|
Assert.True(block.Success, $"{constant} is not a tuple in ship_cc.py");
|
|
|
|
|
return Regex.Matches(block.Groups[1].Value, "\"([^\"]*)\"").Select(match => match.Groups[1].Value).ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string[] EnumNames<T>() where T : struct, Enum
|
|
|
|
|
{
|
|
|
|
|
var values = Enum.GetValues<T>().OrderBy(value => Convert.ToInt32(value)).ToArray();
|
|
|
|
|
Assert.Equal(Enumerable.Range(0, values.Length), values.Select(value => Convert.ToInt32(value)));
|
|
|
|
|
return values.Select(value => value.ToString()).ToArray();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string Locate(params string[] relative)
|
|
|
|
|
{
|
|
|
|
|
for (var directory = new DirectoryInfo(AppContext.BaseDirectory); directory != null; directory = directory.Parent)
|
|
|
|
|
{
|
|
|
|
|
var candidate = Path.Combine(new[] { directory.FullName }.Concat(relative).ToArray());
|
|
|
|
|
if (File.Exists(candidate)) return candidate;
|
|
|
|
|
}
|
|
|
|
|
throw new FileNotFoundException("Repository file not found above the test binary: " + Path.Combine(relative));
|
|
|
|
|
}
|
|
|
|
|
}
|