# normalizer-followups — Evaluation **Verdict: PASS** **Generator commit:** eeee3c2 **Evaluator date:** 2026-04-07 ## Verdict table | # | Criterion | Evidence | Result | |---|-----------|----------|--------| | 1 | `dotnet build recordingtest.sln` — 0 warn / 0 err | Build succeeded, 0 Warning(s), 0 Error(s) | pass | | 2 | `dotnet test` total 77 pass | 16+17+16+5+5+6+6+6 = 77 passed, 0 failed | pass | | A1 | `Profile.FloatDecimals` int? with YAML alias `float_decimals` | `Profile.cs:14-15` `[YamlMember(Alias="float_decimals")] public int? FloatDecimals` | pass | | A2 | `RoundFloatsInNode` accepts decimals parameter | `Rules.cs:102` `RoundFloatsInNode(JsonNode?, int decimals)` + default overload using `DefaultFloatDecimals` | pass | | A3 | `DefaultFloatDecimals = 6` | `Rules.cs:97` `public const int DefaultFloatDecimals = 6` | pass | | A4 | Profile decimals flows via `Normalizer` | `Normalizer.cs:95` `profile.FloatDecimals ?? Rules.DefaultFloatDecimals` | pass | | A5 | Omitted `float_decimals` defaults to 6 | Test `Profile_OmittedFloatDecimals_DefaultsTo6` asserts `profile.FloatDecimals == null` and output rounds to 3.141593 | pass | | A6 | Configurable decimals actually applied | Test `RoundFloats_ProfileWithDecimals3_RoundsTo3` writes temp profile, expects 3.142 | pass | | B1 | `ParseJsonPathLite` exists, rejects `*` and `[...]` | `Rules.cs:200-222` throws on wildcards/indexers, requires leading `$.` | pass | | B2 | `MaskVolatileSettings(node, paths)` walks with path stack | `Rules.cs:227-289` pre-parses allowlist, maintains `stack` list, exact-chain compare in `PathMatches()` | pass | | B3 | `DefaultVolatileSettingPaths` has 16 entries | `Rules.cs:176-194` — counted 16 paths | pass | | B4 | `default.yaml` migrated to list form | `profiles/default.yaml:10-26` — YAML sequence of 16 `$.` strings; `float_decimals: 6` present | pass | | B5 | Regression trap: `SameNameInUnrelatedSubtree_NotMasked` | `RuleTests.cs:174-183` — input `{"GridSnap":true,"Foo":{"GridSnap":false}}` with `["$.GridSnap"]`; asserts root masked and `n["Foo"]["GridSnap"].GetValue() == false`. Pre-fix name-based fallback would have masked both, causing `GetValue()` to throw InvalidOperationException on `` string → test is load-bearing | pass | | B6 | Nested path mask works | `MaskVolatileSettings_NestedPath_MasksCorrectly` — `$.GridColor.R` masks only R, leaves G | pass | | B7 | Root mask works | `MaskVolatileSettings_RootField_Masks` | pass | | B8 | No leftover `VolatileSettingFieldNames` fallback | Grep in `src/` — no matches anywhere | pass | | 9 | CoverageTests still green | Normalizer.Tests dll 16 passed (includes coverage tests) | pass | ## Notes - Regression trap load-bearing: confirmed. The old `VolatileSettingFieldNames.Contains(kv.Key)` approach would mask both `GridSnap` occurrences → nested `.GetValue()` on a `""` JsonValue would throw. The test would fail loudly. - Test count for Normalizer.Tests went from 10 → 16 as claimed (6 new tests present and accounted for). - Default-on behavior preserved: `default.yaml` both specifies `float_decimals: 6` explicitly AND the omitted-profile test proves the `?? DefaultFloatDecimals` fallback path. - Count of `DefaultVolatileSettingPaths`: 16 entries confirmed (CanOverrideWireColorWithFace, IsSidePanelVisible, OverrideFaceColor, Solar_IsLocalTime, VisibleGrid, GridSnap, MidpointOsnap, GridSpacing, GridColor.{ALPHA,BLUE,GREEN,RED}, MajorGridColor.{ALPHA,BLUE,GREEN,RED}). ## Partial / gaps None. Both follow-ups are complete with no residual fallback code.