Skip to content

Consolidate per-platform label files and add fuzzy romName matching - #5

Merged
tmstedman merged 3 commits into
masterfrom
label-db-improvements
Jul 6, 2026
Merged

Consolidate per-platform label files and add fuzzy romName matching#5
tmstedman merged 3 commits into
masterfrom
label-db-improvements

Conversation

@tmstedman

@tmstedman tmstedman commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • New label file format: replaces the old per-game subdirectory structure (Labels/{platform}/{rom}.xml) with a single consolidated Labels/{platform}.xml per platform. Each file has a <Defaults> block and <Game launchBoxId="..." romName="..."> entries. The two-tier Defaults//User/ merge now happens at the entry level — User entries win over Defaults entries by launchBoxId, then by romName.
  • launchBoxId lookup: InputLabelsLoader indexes entries by launchBoxId (now typed as int? throughout, matching IGame.LaunchBoxDbId) and tries it before romName. Stable across ROM renames and No-Intro set updates.
  • Fuzzy romName matching: when neither launchBoxId nor exact romName match, the loader falls back to a normalised form (balanced bracket groups stripped, lowercased). NormalizeRomName is extracted to RomNameUtils with its own unit tests. Asset and fixture romName values are stripped of region tags to reflect realistic contributor data; the existing E2E tests now exercise the fuzzy path automatically.
  • Caching and timing: each platform file is parsed at most once per session. Elapsed parse time is logged on first load.

Test plan

  • All existing tests pass — no behavioural regressions
  • RomNameUtilsTests — 13 normalisation cases + idempotency
  • InputLabelsLoaderTests — unit coverage: name/id/fuzzy lookup, user/defaults merge, caching, error paths
  • InputLabelsSubsystemTests — 4 fuzzy-match combination tests + launchBoxId subsystem test
  • SegaGenesisEndToEndTests.SegaGenesis_OutRun_MatchedByLaunchBoxId — E2E coverage of the launchBoxId path

🤖 Generated with Claude Code

T Stedman and others added 3 commits July 5, 2026 22:28
…latform}.xml

Labels are now stored in a single Labels/{platform}.xml per platform combining a
<Defaults> block and <Game launchBoxId="..." romName="..."> entries. The two-tier
Defaults/User filesystem now merges at the entry level rather than doing wholesale
file shadowing: User game entries win over Defaults entries matched first by
launchBoxId then by romName; User <Defaults> buttons override Defaults buttons per-key.

GameInfo gains LaunchBoxId (populated from IGame.LaunchBoxDbId in the LaunchBox layer)
as the primary lookup key, with RomName as the fallback. InputLabelsLoader is rewritten
to load and merge both tiers, replacing the old per-game + _DefaultLabels.xml approach.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Label lookup now falls back to fuzzy matching when neither launchBoxId nor exact
romName match: NormalizeRomName strips balanced parenthesis and bracket groups then
lowercases, so "OutRun (USA, Europe)" on disk finds a bare "OutRun" entry and vice
versa. Extracted into RomNameUtils.NormalizeRomName (Infrastructure) with unit tests.

InputLabelsLoader caches parsed platform data so each Labels/{platform}.xml is read
and merged at most once per session, consistent with TemplateService. Logs elapsed
parse time on first load.

romName attributes in fixture and asset label files stripped of region tags to
reflect realistic contributor data — the E2E tests now exercise the fuzzy path.
Four subsystem tests cover all matching combinations: bare/bracketed entry vs
bare/bracketed ROM, and a non-match guard confirming bracket-stripping does not
imply substring matching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The loader reads node.Attributes["launchBoxId"], so an `id` attribute
would be silently ignored.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Unit test coverage (Core.Tests)

Summary - Unit tests (Core.Tests)

Summary
Generated on: 07/05/2026 - 23:57:08
Coverage date: 07/05/2026 - 23:56:57
Parser: Cobertura
Assemblies: 1
Classes: 74
Files: 59
Line coverage: 99.1% (2320 of 2339)
Covered lines: 2320
Uncovered lines: 19
Coverable lines: 2339
Total lines: 4964
Branch coverage: 99.8% (1215 of 1217)
Covered branches: 1215
Total branches: 1217
Method coverage: Feature is only available for sponsors

Coverage

DynamicControls.Core - 99.1%
Name Line Branch
DynamicControls.Core 99.1% 99.8%
DynamicControls.Composition.ConfigLoader 100% 100%
DynamicControls.Config.GlobalConfig 100%
DynamicControls.ControllerOverlayModel 83.3%
DynamicControls.ControllerOverlayService 100% 100%
DynamicControls.Infrastructure.FileUtils 100% 100%
DynamicControls.Infrastructure.ImageHeader 100% 100%
DynamicControls.Infrastructure.LayeredFileSystem 100% 100%
DynamicControls.Infrastructure.Logger 100% 100%
DynamicControls.Infrastructure.RomNameUtils 100%
DynamicControls.InputMapping.AnalogToDigitalMirror 100% 100%
DynamicControls.InputMapping.ControllerConfig 80%
DynamicControls.InputMapping.InputMappingConfig 80%
DynamicControls.InputMapping.InputMappingLoader 100% 100%
DynamicControls.InputMapping.InputMappingPlugins 100% 100%
DynamicControls.InputMapping.InputMappingResolver 100% 100%
DynamicControls.InputMapping.InputMappingService 100% 100%
DynamicControls.InputMapping.MappingEntry 66.6%
DynamicControls.InputMapping.PerGameXmlMappingSource 100% 100%
DynamicControls.InputMapping.PlatformControllersConfig 87.5% 100%
DynamicControls.InputMapping.PlatformDefaultMappingSource 100% 100%
DynamicControls.Labels.InputLabelsConfig 50%
DynamicControls.Labels.InputLabelsLoader 100% 100%
DynamicControls.Labels.InputLabelsPlugins 100%
DynamicControls.Labels.InputLabelsService 100% 100%
DynamicControls.Labels.LabelEntry 66.6%
DynamicControls.Plugins.ControlsXml.ControlsXmlLoader 100% 100%
DynamicControls.Plugins.ControlsXml.MameControlsXmlSource 100% 100%
DynamicControls.Plugins.Mame.JoycodeMapping 100% 100%
DynamicControls.Plugins.Mame.JoycodeMappingLoader 100% 100%
DynamicControls.Plugins.Mame.MameCfgLoader 100% 100%
DynamicControls.Plugins.Mame.MameEmulator 100%
DynamicControls.Plugins.Mame.MameInputMappingSource 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchCfgLoader 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchCfgSwapResolver 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchConfigFileReader 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchControllerConfig 66.6%
DynamicControls.Plugins.RetroArch.RetroArchCoreConfig 66.6%
DynamicControls.Plugins.RetroArch.RetroArchCoreInfo 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchCoreLoader 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchEmulator 100%
DynamicControls.Plugins.RetroArch.RetroArchGameData 100%
DynamicControls.Plugins.RetroArch.RetroArchMappings 100%
DynamicControls.Plugins.RetroArch.RetroArchMappingSource 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchOverridesResolver 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchRemapLoader 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchRemapSwapResolver 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchSwapApplier 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchVariantResolver 100% 100%
DynamicControls.Rendering.InputImageRenderer 100% 100%
DynamicControls.Rendering.InputImageResolver 100% 100%
DynamicControls.Rendering.InputLabelRenderer 100% 100%
DynamicControls.Rendering.InputRenderingService 100% 100%
DynamicControls.Rendering.LayoutFilter 100% 96.6%
DynamicControls.Rendering.VisibilityEvaluator 100% 100%
DynamicControls.Rendering.VisibilityFlags 100% 100%
DynamicControls.Static.StaticImageResolver 100% 100%
DynamicControls.Templates.CollapseGroupBuilder 100% 100%
DynamicControls.Templates.Coordinate 100% 100%
DynamicControls.Templates.GroupNode 66.6%
DynamicControls.Templates.HeadConfig 66.6%
DynamicControls.Templates.InputDescendantsBuilder 100% 100%
DynamicControls.Templates.InputNode 92.3%
DynamicControls.Templates.LabelNode 80%
DynamicControls.Templates.LayoutConfig 66.6%
DynamicControls.Templates.LayoutResolver 100% 100%
DynamicControls.Templates.OneOfNode 50%
DynamicControls.Templates.OverlayNode 88.8%
DynamicControls.Templates.RenderNode 88.8%
DynamicControls.Templates.StackNode 85.7%
DynamicControls.Templates.StyleConfig 80%
DynamicControls.Templates.TemplateImageResolver 100% 100%
DynamicControls.Templates.TemplateImageSource 100%
DynamicControls.Templates.TemplateLoader 100% 100%
DynamicControls.Templates.TemplateService 100% 100%

Integration test coverage (Core.IntegrationTests)

Summary - Integration tests (Core.IntegrationTests)

Summary
Generated on: 07/05/2026 - 23:57:09
Coverage date: 07/05/2026 - 23:57:05
Parser: Cobertura
Assemblies: 1
Classes: 74
Files: 59
Line coverage: 90.8% (2124 of 2339)
Covered lines: 2124
Uncovered lines: 215
Coverable lines: 2339
Total lines: 4964
Branch coverage: 82.3% (1002 of 1217)
Covered branches: 1002
Total branches: 1217
Method coverage: Feature is only available for sponsors

Coverage

DynamicControls.Core - 90.8%
Name Line Branch
DynamicControls.Core 90.8% 82.3%
DynamicControls.Composition.ConfigLoader 93.9% 66.6%
DynamicControls.Config.GlobalConfig 100%
DynamicControls.ControllerOverlayModel 83.3%
DynamicControls.ControllerOverlayService 72.3% 50%
DynamicControls.Infrastructure.FileUtils 100% 50%
DynamicControls.Infrastructure.ImageHeader 95.6% 66.6%
DynamicControls.Infrastructure.LayeredFileSystem 100% 100%
DynamicControls.Infrastructure.Logger 95.6% 100%
DynamicControls.Infrastructure.RomNameUtils 100%
DynamicControls.InputMapping.AnalogToDigitalMirror 96.9% 87.5%
DynamicControls.InputMapping.ControllerConfig 80%
DynamicControls.InputMapping.InputMappingConfig 80%
DynamicControls.InputMapping.InputMappingLoader 90.6% 86.7%
DynamicControls.InputMapping.InputMappingPlugins 100% 100%
DynamicControls.InputMapping.InputMappingResolver 100% 100%
DynamicControls.InputMapping.InputMappingService 100% 75%
DynamicControls.InputMapping.MappingEntry 66.6%
DynamicControls.InputMapping.PerGameXmlMappingSource 100% 83.3%
DynamicControls.InputMapping.PlatformControllersConfig 87.5% 66.6%
DynamicControls.InputMapping.PlatformDefaultMappingSource 100% 100%
DynamicControls.Labels.InputLabelsConfig 50%
DynamicControls.Labels.InputLabelsLoader 96.7% 91.6%
DynamicControls.Labels.InputLabelsPlugins 100%
DynamicControls.Labels.InputLabelsService 100% 100%
DynamicControls.Labels.LabelEntry 66.6%
DynamicControls.Plugins.ControlsXml.ControlsXmlLoader 95.7% 84.7%
DynamicControls.Plugins.ControlsXml.MameControlsXmlSource 87.5% 100%
DynamicControls.Plugins.Mame.JoycodeMapping 100% 50%
DynamicControls.Plugins.Mame.JoycodeMappingLoader 81.2% 68.7%
DynamicControls.Plugins.Mame.MameCfgLoader 90.1% 84.2%
DynamicControls.Plugins.Mame.MameEmulator 100%
DynamicControls.Plugins.Mame.MameInputMappingSource 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchCfgLoader 100% 88.8%
DynamicControls.Plugins.RetroArch.RetroArchCfgSwapResolver 88.6% 82.7%
DynamicControls.Plugins.RetroArch.RetroArchConfigFileReader 83.3% 100%
DynamicControls.Plugins.RetroArch.RetroArchControllerConfig 66.6%
DynamicControls.Plugins.RetroArch.RetroArchCoreConfig 66.6%
DynamicControls.Plugins.RetroArch.RetroArchCoreInfo 84.2% 50%
DynamicControls.Plugins.RetroArch.RetroArchCoreLoader 67.2% 64.2%
DynamicControls.Plugins.RetroArch.RetroArchEmulator 100%
DynamicControls.Plugins.RetroArch.RetroArchGameData 50%
DynamicControls.Plugins.RetroArch.RetroArchMappings 100%
DynamicControls.Plugins.RetroArch.RetroArchMappingSource 94% 91.1%
DynamicControls.Plugins.RetroArch.RetroArchOverridesResolver 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchRemapLoader 100% 85%
DynamicControls.Plugins.RetroArch.RetroArchRemapSwapResolver 75.8% 65%
DynamicControls.Plugins.RetroArch.RetroArchSwapApplier 100% 100%
DynamicControls.Plugins.RetroArch.RetroArchVariantResolver 73.9% 75%
DynamicControls.Rendering.InputImageRenderer 100% 100%
DynamicControls.Rendering.InputImageResolver 96.2% 82.1%
DynamicControls.Rendering.InputLabelRenderer 100% 100%
DynamicControls.Rendering.InputRenderingService 100% 81.2%
DynamicControls.Rendering.LayoutFilter 77.1% 60%
DynamicControls.Rendering.VisibilityEvaluator 78.1% 60%
DynamicControls.Rendering.VisibilityFlags 90.9% 90.9%
DynamicControls.Static.StaticImageResolver 94.1% 50%
DynamicControls.Templates.CollapseGroupBuilder 79.5% 75%
DynamicControls.Templates.Coordinate 100% 100%
DynamicControls.Templates.GroupNode 66.6%
DynamicControls.Templates.HeadConfig 66.6%
DynamicControls.Templates.InputDescendantsBuilder 90.5% 89.2%
DynamicControls.Templates.InputNode 92.3%
DynamicControls.Templates.LabelNode 80%
DynamicControls.Templates.LayoutConfig 66.6%
DynamicControls.Templates.LayoutResolver 94% 86.3%
DynamicControls.Templates.OneOfNode 50%
DynamicControls.Templates.OverlayNode 88.8%
DynamicControls.Templates.RenderNode 88.8%
DynamicControls.Templates.StackNode 85.7%
DynamicControls.Templates.StyleConfig 80%
DynamicControls.Templates.TemplateImageResolver 94.1% 86.3%
DynamicControls.Templates.TemplateImageSource 100%
DynamicControls.Templates.TemplateLoader 87.6% 79.6%
DynamicControls.Templates.TemplateService 100% 100%

@tmstedman
tmstedman merged commit d104eea into master Jul 6, 2026
2 checks passed
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