Skip to content

feat: YAML reader for BattleScribe-schema data + file-based spec adapter setup - #309

Closed
amis92 wants to merge 10 commits into
feature/roster-enginefrom
feat/yaml-reader
Closed

amis92 wants to merge 10 commits into
feature/roster-enginefrom
feat/yaml-reader

Conversation

@amis92

@amis92 amis92 commented Jul 13, 2026

Copy link
Copy Markdown
Member

What

Foundation work for the muster CI harness (golden-roster regression checks for data repos):

  • New WarHub.ArmouryModel.Source.Yaml: reads YAML-serialized BattleScribe data (the BSData/wh40k-11e format) into the node model via a YAML→XML structural transform feeding the existing serializer. Scalars flow as raw strings (representation model — no typed-deserialization coercion; pinned by test), strict mode throws on unknown collection keys, $text → element text, text-element keys (comment/description/customNotes/readme) emit child elements. Loads all 47 real wh40k-11e files (integration suite, gated by WH40K11E_DATA env var, skips cleanly when unset).
  • SetupFromFiles on SpecRosterEngineAdapter (XML + YAML by extension), enabling dataSource-backed conformance specs and muster fixtures; parse failures name the offending file.
  • 4 additive enum members found in real 11e data: ModifierKind.Replace/Ceil/Floor, ConditionGroupKind.Count (with XmlEnum attrs → full serializer round-trip). ⚠️ These are parse/serialize-only: no conformance specs exercise their evaluation semantics yet — follow-up needed before engine behavior on them is trusted.
  • Release-build hygiene: fixed 16 pre-existing CA1822 (+ cascade CA1822/CA1859/CA1305 in StateMapper) that broke -c Release builds (CI builds Debug only, so these were invisible). Public API signatures unchanged (suppressed on public members, static-ified privates only).
  • Spec submodule bumped to feat(testkit): HarnessError crash marker + YAML dataSource enumeration battlescribe-spec#307 (HarnessError + YAML dataSource enumeration).

Known follow-ups (not in this PR)

  • associations/alias YAML keys load but round-trip lossily (no model support; documented).
  • Roster-level cost aggregation drops cost types only reachable via entryLinks — real defect found by muster's pilot fixtures; filed separately with code-level analysis.

Verification

Source.Yaml.Tests 54/54 (with data) / green-with-skips (without); RosterEngine.Spec.Tests 31/31; ConformanceTests 351/351 (baseline preserved); Release builds of all touched projects clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KcYK8hmTmXi8eLWpg9LGKJ

amis92 and others added 9 commits July 13, 2026 02:48
Adds WarHub.ArmouryModel.Source.Yaml with YamlBattleScribeReader.ReadSourceNode
and YamlToXmlConverter, converting a YAML representation model into the XML
shape expected by the existing BattleScribe deserializer. Covered by a single
TDD test parsing a minimal gameSystem YAML document into a GamesystemNode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…eader

Address code review findings on the YAML-to-XML converter: unguarded YAML
node casts now throw YamlBattleScribeFormatException naming the offending
key instead of raw InvalidCastException; invalid XML attribute/element
names (spaces, colons like xml:lang) now throw the same instead of raw
XmlException; scalar keys that are XML child text elements per the XSD
(comment, description, customNotes, readme) are now emitted as child
elements instead of attributes, fixing silent data loss on deserialize;
removed a fabricated "associations" collection mapping with no XSD/model
backing; dropped the unused FluentAssertions test dependency; sharpened
the raw-string round-trip test with an unquoted ambiguous scalar; and
noted that multi-document YAML streams are intentionally unsupported.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…, strict mode)

Add two new test cases to YamlBattleScribeReaderTests:
- ReadSourceNode_preserves_modifiers_costs_constraints_and_text: validates
  that a real-world catalogue fragment with modifiers, conditionGroups,
  costs, constraints, profiles with $text characteristics, and comment
  are all correctly parsed and preserved.
- ReadSourceNode_throws_on_unknown_collection_key: verifies that the
  reader strictly validates collection keys and throws
  YamlBattleScribeFormatException for unknown collections.

Both tests pass; all 6 tests in the suite pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcYK8hmTmXi8eLWpg9LGKJ
- Add StringComparison.Ordinal to Assert.Contains calls at lines 169 and 189
- Add missing assertions for condition group Type, constraint properties (Field, Value, Id), and entry Comment

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KcYK8hmTmXi8eLWpg9LGKJ
Add Wh40kDataIntegrationTests, a theory over every *.yaml file in
WH40K11E_DATA (wh40k-11e on branch json-to-yaml), plus a sanity check that
the loaded root has a non-empty Name and at least one populated child
collection (catches loads that "succeed" but are structurally hollow due
to a scalar key that silently missed the attribute/TextElementNames
paths).

Extend YamlToXmlConverter to handle two real-data collection keys absent
from Catalogue.xsd: `associations` -> `association` (a documented
NewRecruit extension, object-shaped items, added to ItemElementNames) and
`alias` (a scalar-list Rule extension with no XSD precedent, handled via
a new RepeatedTextElementNames mechanism since its items aren't mappings).

Also extend ModifierKind (replace, ceil, floor) and ConditionGroupKind
(count) - real-data enum literals missing from wham's hand-written enums
and from the bundled (stale) Catalogue.xsd. Purely additive, no
exhaustive switches over these enums exist within this build's dependency
graph.

All 47 wh40k-11e files load successfully; largest file (Imperium - Space
Marines.yaml, 4.4 MB) loads in ~372ms.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds SetupFromFiles to SpecRosterEngineAdapter so specs/fixtures can drive the
wham roster engine from real .cat/.gst (XML) or .yaml data files instead of
inline protocol data, using YamlBattleScribeReader for YAML and
DeserializeSourceNodeAuto for XML. Mirrors Setup's post-compilation wiring
(_catalogCompilation, _coreEngine, _state) exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gine

TreatWarningsAsErrors only applies in Release, and CI never builds Release,
so 16 pre-existing CA1822 ("mark as static") errors in WhamRosterEngine.cs
went unnoticed until the muster CLI's Release publish hit them. Public
engine API members (used by SpecRosterEngineAdapter/EditorServices) are
suppressed narrowly via [SuppressMessage] (existing repo convention, see
Binder.cs) to preserve their instance-based signatures; the one private
helper that didn't touch instance state is marked static.

Fixing WhamRosterEngine.cs exposed 4 further Release-only analyzer errors
in RosterEngine.Spec's internal StateMapper (previously masked because the
project it depends on failed to build first): two more CA1822 statics, one
CA1859 parameter-type tightening, and one CA1305 explicit IFormatProvider.

Mechanical modifier/suppression change only; no public API signatures
changed; behavior unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…taSource)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…upFromFiles parse errors

Every_wh40k11e_file_loads now uses [Theory(SkipTestWithoutData = true)]
so a clean CI run without WH40K11E_DATA skips the integration cases
instead of hard-failing on empty MemberData.

SpecRosterEngineAdapter.SetupFromFiles wraps per-file parsing so any
parse failure (YAML or XML) is rethrown as an InvalidOperationException
naming the offending file, with the original exception preserved as
InnerException. Adds a regression test asserting the second (malformed)
file's name appears in the thrown message.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Picks up the New Recruit console host + public Docker image publishing
work (feat/muster-support), needed by Muster's engine adapter chain.
@amis92

amis92 commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

Landed as #325, rebased onto the roster-engine stack (#315–#322) rather than onto feature/roster-engine, which is now closed.

Two changes during the rebase:

  • The four enum additions were dropped as redundant. ModifierKind.Replace/Ceil/Floor and ConditionGroupKind.Count all reached main independently via feat: model NewRecruit schema additions over BattleScribe v2.03 #308, which modelled the NewRecruit additions in bulk. I verified they are a strict subset before dropping — main carries 22 ModifierKind members and 18 ConditionGroupKind members, a superset of everything this branch added, so nothing is lost.

    Your doc comments for Prepend and Replace were worth keeping, though — feat: model NewRecruit schema additions over BattleScribe v2.03 #308 covered the whole block with a single // comment. They are restored in docs: document string ModifierKinds and cover them with tests #326.

  • ab77d59 ("resolve pre-existing CA1822 Release-build errors") was dropped automatically — git reported patch contents already upstream. I had independently hit the same 16 errors while splitting the stack and reached the same conclusion you did (public engine API stays instance-based; suppress per-member rather than make it static), so feat: add ISymbol-based roster engine core #321 already carries a byte-identical fix. Your diagnosis in that commit message was also what led me to check Release across every layer.

Everything else — the YAML reader, SetupFromFiles, and the submodule bump — came across unchanged and is on main.

@amis92 amis92 closed this Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant