Skip to content

test(coverage): wave 7 — async handlers + final push (80.8% branch)#136

Merged
Prekzursil merged 1 commit intomainfrom
test/coverage-wave7
Apr 4, 2026
Merged

test(coverage): wave 7 — async handlers + final push (80.8% branch)#136
Prekzursil merged 1 commit intomainfrom
test/coverage-wave7

Conversation

@Prekzursil
Copy link
Copy Markdown
Owner

@Prekzursil Prekzursil commented Apr 4, 2026

Wave 7: 200+ tests. Branch 80.8%, Line 81.8%.


Summary by cubic

Adds the final Wave 7 test suite (200+ tests) targeting async handlers and edge cases across Runtime, Saves, Meg, DataIndex, Flow, Profiles, Core, and App. Raises coverage to 80.8% branch and 81.8% line.

  • Coverage Focus
    • Runtime: async routes in RuntimeAdapter (ExecuteByRouteAsync, backend handlers), attach/detach lifecycle, calibration scan, and exception branches.
    • Saves: BinarySaveCodec write guards and float endianness; SavePatchApplyService apply/restore/rollback IO and permissions paths.
    • Meg/DataIndex/Flow: MegArchiveReader format fallbacks and range/parse errors; loose entry/path resolution guards; flow exporter switch arms and harness defaults.
    • Profiles/Core/App: GitHub profile extraction validation and rollback; record constructors and null guards across models and view models.

Written for commit 3ccc21b. Summary will update on new commits.

Summary by CodeRabbit

  • Tests
    • Added comprehensive test coverage for Wave 7 final validation across App, Core, DataIndex, Flow, Meg, Profiles, Runtime, and Saves subsystems, including edge case handling, error scenarios, persistence operations, and service interface compliance to improve overall system reliability.

…ranch)

Wave 7A: RuntimeAdapter async execution paths (116 test cases)
  - ExecuteByRouteAsync all backend switch arms
  - ExecuteExtenderBackendActionAsync success/null guard
  - ExecuteLegacyBackendActionAsync all ExecutionKind arms
  - ProbeCapabilitiesAsync inference paths
  - TryCreateMechanicBlockedResultAsync exception catches
  - AttachAsync/DetachAsync lifecycle

Wave 7B: Package gap-filling (88 tests)
  - Flow 94.8->97.9%, Meg 87.1->88.7%, Saves 92.8->93.6%
  - Core, Profiles, DataIndex, App record constructors + edge cases

Overall: Line 81.8%, Branch 80.8%, 3,900+ tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@devloai
Copy link
Copy Markdown

devloai Bot commented Apr 4, 2026

Unable to trigger custom agent "Code Reviewer". You have run out of credits 😔
Please upgrade your plan or buy additional credits from the subscription page.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 4, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a2ee1e4-7866-49a9-a1f4-27967c306956

📥 Commits

Reviewing files that changed from the base of the PR and between 2d5f971 and 3ccc21b.

📒 Files selected for processing (8)
  • tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs
  • tests/SwfocTrainer.Tests/Core/CoreWave7FinalTests.cs
  • tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs
  • tests/SwfocTrainer.Tests/Flow/FlowWave7FinalTests.cs
  • tests/SwfocTrainer.Tests/Meg/MegWave7FinalTests.cs
  • tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs
  • tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs
  • tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs

📝 Walkthrough

Walkthrough

This PR adds comprehensive test coverage across 8 subsystem areas (App, Core, DataIndex, Flow, Meg, Profiles, Runtime, Saves) through new Wave 7 final test classes, targeting edge cases, failure paths, and coverage gaps in view models, services, and core components.

Changes

Cohort / File(s) Summary
App Layer Tests
tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs
Tests App ViewModel record construction, runtime mode override persistence, and effective runtime mode resolution logic.
Core Model Tests
tests/SwfocTrainer.Tests/Core/CoreWave7FinalTests.cs
Tests Core record constructors across action specs, capability reports, live-ops, workshop, and onboarding models; validates default interface method overloads for action reliability and runtime adapter services.
Data Index Tests
tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs
Tests EffectiveGameDataIndexService.Build with empty/whitespace mod paths, MEG file references in MegaFiles.xml, and file map entry source path storage.
Flow Component Tests
tests/SwfocTrainer.Tests/Flow/FlowWave7FinalTests.cs
Tests StoryFlowGraphEdge constructor, LuaHarnessRunner default harness path resolution, StoryFlowGraphExporter switch behavior with tactical mode hints, and StoryPlotFlowExtractor null root handling.
Meg Archive Tests
tests/SwfocTrainer.Tests/Meg/MegWave7FinalTests.cs
Tests MegArchiveReader.Open failure paths including locked files, corrupted payloads, format3 header fallback, truncated records, and entry flags; validates TryEnsureRange offset bounds checking.
Profile Service Tests
tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs
Tests GitHubProfileUpdateExtractionHelpers for empty entries, path traversal prevention, and directory handling; validates package extraction failure and directory preparation via reflection; tests ModOnboardingService error handling.
Runtime Adapter Tests
tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs
Extensive tests for RuntimeAdapter async execution routing, backend validation, legacy execution kind handling, SDK action behavior, capability probing, mechanic gating, expert-mutation overrides, exception handling, and internal helper methods via reflection.
Save Patch Tests
tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs
Tests SavePatchApplyServiceHelper methods (backup resolution, value normalization, temp file deletion) and SavePatchPackService.LoadPackAsync validation for invalid operation contracts and schema mismatches via reflection-based invocations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

area:runtime, area:app, area:profiles, area:ci, needs-reviewer, review-effort-3

Poem

🐰 Wave Seven's veil at last unfurled,
Eight test suites guard this grand world,
From flows to saves, from runtimes deep,
Edge cases caught, their vigil keep,
Coverage blooms in testing's light! ✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/coverage-wave7

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

test(coverage): wave 7 — async handlers + final push (80.8% branch coverage)

🧪 Tests

Grey Divider

Walkthroughs

Description
• Comprehensive test coverage wave 7 with 200+ new tests achieving 80.8% branch coverage and 81.8%
  line coverage
• **RuntimeAdapter async execution paths**: 1868 lines covering ExecuteByRouteAsync,
  ExecuteExtenderBackendActionAsync, ExecuteMemoryActionAsync, ExecuteSdkActionAsync, and
  ExecuteLegacyBackendActionAsync with lifecycle methods and exception handling
• **Core layer**: Record constructors, default interface method overloads for
  IActionReliabilityService, IRuntimeAdapter, ISavePatchPackService, and IModOnboardingService
• **Save operations**: Exception handling for SavePatchApplyService helpers, contract validation
  for SavePatchPackService, and edge cases with locked files and schema mismatches
• **Profile updates**: GitHubProfileUpdateService extraction paths, security validation for path
  traversal, and IO exception handling
• **MEG archive reader**: Edge case coverage for locked files, payload parsing exceptions, format
  fallback paths, and entry record validation
• **App layer**: View model record constructors and runtime mode override helpers with file
  persistence edge cases
• **DataIndex service**: Loose entries path handling, MEG file diagnostics propagation, and fallback
  behavior
• **Flow service**: Story flow graph models, exporter mode-specific feature ID generation, and null
  root handling
Diagram
flowchart LR
  A["Test Coverage Wave 7"] --> B["RuntimeAdapter Async Paths"]
  A --> C["Core Layer Models"]
  A --> D["Save Operations"]
  A --> E["Profile Updates"]
  A --> F["MEG Archive Reader"]
  A --> G["App Layer VMs"]
  A --> H["DataIndex Service"]
  A --> I["Flow Service"]
  B --> J["80.8% Branch Coverage"]
  C --> J
  D --> J
  E --> J
  F --> J
  G --> J
  H --> J
  I --> J
Loading

Grey Divider

File Changes

1. tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs 🧪 Tests +1868/-0

Wave 7 async execution paths and exception handling coverage

• Comprehensive async execution path coverage for RuntimeAdapter with 1868 lines of tests
• Tests for ExecuteByRouteAsync, ExecuteExtenderBackendActionAsync, ExecuteMemoryActionAsync,
 ExecuteSdkActionAsync, and ExecuteLegacyBackendActionAsync
• Coverage of lifecycle methods (AttachAsync, DetachAsync), capability probing, mechanic
 detection, and expert mutation overrides
• Exception handling tests for Win32Exception, IOException, and KeyNotFoundException

tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs


2. tests/SwfocTrainer.Tests/Core/CoreWave7FinalTests.cs 🧪 Tests +567/-0

Core record constructors and interface default method overloads

• Tests for record constructors across action models, backend routing models, and SDK capability
 models
• Coverage of default interface method overloads for IActionReliabilityService, IRuntimeAdapter,
 ISavePatchPackService, and IModOnboardingService
• Tests for ModOnboardingServiceExtensions null guards and parameter validation
• Validation of optional parameters in profile models like HelperHookSpec

tests/SwfocTrainer.Tests/Core/CoreWave7FinalTests.cs


3. tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs 🧪 Tests +451/-0

Save patch service exception handling and contract validation

• Tests for SavePatchApplyService.Helpers edge cases including null directory handling and
 exception paths
• Coverage of TryNormalizePatchValue with FormatException, TryDeleteTempOutput with locked
 files, and TryNormalizeBackupCandidatePath validation
• Tests for SavePatchPackService contract validation including negative offsets, missing fields,
 and schema version mismatches
• Exception handling for IOException, JsonException, and InvalidDataException in save
 operations

tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs


View more (5)
4. tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs 🧪 Tests +226/-0

Profile update service extraction and exception handling coverage

• Tests for GitHubProfileUpdateExtractionHelpers including path traversal security validation and
 directory entry handling
• Coverage of GitHubProfileUpdateService exception paths for IOException and
 InvalidDataException during extraction
• Tests for PrepareExtractDirectory cleanup of existing directories
• Validation of ModOnboardingService exception catch blocks for IO and operation errors

tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs


5. tests/SwfocTrainer.Tests/Meg/MegWave7FinalTests.cs 🧪 Tests +216/-0

MegArchiveReader edge case and error handling test coverage

• Adds 216 lines of comprehensive test coverage for MegArchiveReader edge cases
• Tests IOException handling when opening locked files (lines 30-32)
• Tests InvalidOperationException and FormatException catches during payload parsing (lines 55-63)
• Tests format3→format2 fallback path and name table trailing bytes diagnostics
• Tests TryParseEntryRecord with entry flags support and TryEnsureRange offset bounds validation
 via reflection

tests/SwfocTrainer.Tests/Meg/MegWave7FinalTests.cs


6. tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs 🧪 Tests +142/-0

App view model records and runtime mode override helpers coverage

• Adds 142 lines of test coverage for App layer view models and helpers
• Tests record constructors for RuntimeCalibrationCandidateViewItem,
 SelectedUnitTransactionViewItem, SavePatchOperationViewItem, and ActionReliabilityViewItem
• Tests MainViewModelRuntimeModeOverrideHelpers Load/Save edge cases including file not found,
 null values, and round-trip persistence
• Tests ResolveEffectiveRuntimeMode with known and unknown override values

tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs


7. tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs 🧪 Tests +140/-0

DataIndex service diagnostics and loose entries path handling

• Adds 140 lines of test coverage for EffectiveGameDataIndexService and related models
• Tests AddLooseEntries with empty and whitespace mod paths (lines 176-178)
• Tests MegaFiles.xml diagnostics propagation when referenced MEG files are missing
• Tests ResolveMegaPath fallback behavior when files are not found under game root
• Tests EffectiveFileMapEntry.SourcePath property coverage

tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs


8. tests/SwfocTrainer.Tests/Flow/FlowWave7FinalTests.cs 🧪 Tests +112/-0

Flow models and exporter mode-specific feature ID generation

• Adds 112 lines of test coverage for Flow service models and exporters
• Tests StoryFlowGraphEdge record constructor property storage
• Tests LuaHarnessRunner.ResolveDefaultHarnessScriptPath fallback default path via reflection
• Tests StoryFlowGraphExporter switch arms for TacticalLand, TacticalSpace, and unknown mode hints
• Tests StoryPlotFlowExtractor.ExtractEvents null root early return via reflection

tests/SwfocTrainer.Tests/Flow/FlowWave7FinalTests.cs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 4, 2026

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (2) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. CreateAttachedAdapter too many params 📘 Rule violation ⚙ Maintainability
Description
CreateAttachedAdapter has 8 parameters and spans well over 50 lines, exceeding the repo’s method
size/parameter limits. This increases maintenance burden and makes future changes harder to test and
review.
Code

tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[R33-109]

+    private static RuntimeAdapter CreateAttachedAdapter(
+        RuntimeMode mode = RuntimeMode.Galactic,
+        TrainerProfile? profile = null,
+        IBackendRouter? router = null,
+        IHelperBridgeBackend? helperBackend = null,
+        IModMechanicDetectionService? mechanicService = null,
+        ISdkOperationRouter? sdkRouter = null,
+        IExecutionBackend? executionBackend = null,
+        bool includeExecutionBackend = true)
+    {
+        profile ??= BuildProfile("set_credits");
+        var services = new Dictionary<Type, object>
+        {
+            [typeof(IBackendRouter)] = router ?? new StubBackendRouter(
+                new BackendRouteDecision(true, ExecutionBackendKind.Memory, RuntimeReasonCode.CAPABILITY_PROBE_PASS, "ok")),
+            [typeof(IHelperBridgeBackend)] = helperBackend ?? new StubHelperBridgeBackend(),
+            [typeof(IModDependencyValidator)] = new StubDependencyValidator(
+                new DependencyValidationResult(DependencyValidationStatus.Pass, "", new HashSet<string>(StringComparer.OrdinalIgnoreCase))),
+            [typeof(ITelemetryLogTailService)] = new StubTelemetryLogTailService()
+        };
+
+        if (includeExecutionBackend)
+        {
+            services[typeof(IExecutionBackend)] = executionBackend ?? new StubExecutionBackend();
+        }
+
+        if (mechanicService is not null)
+        {
+            services[typeof(IModMechanicDetectionService)] = mechanicService;
+        }
+
+        if (sdkRouter is not null)
+        {
+            services[typeof(ISdkOperationRouter)] = sdkRouter;
+        }
+
+        var adapter = new RuntimeAdapter(
+            new StubProcessLocator(),
+            new StubProfileRepository(profile),
+            new StubSignatureResolver(),
+            NullLogger<RuntimeAdapter>.Instance,
+            new MapServiceProvider(services));
+
+        var symbolMap = new SymbolMap(new Dictionary<string, SymbolInfo>(StringComparer.OrdinalIgnoreCase)
+        {
+            ["credits"] = new SymbolInfo("credits", (nint)0x1000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
+            ["unit_cap"] = new SymbolInfo("unit_cap", (nint)0x2000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
+            ["fog_reveal"] = new SymbolInfo("fog_reveal", (nint)0x3000, SymbolValueType.Byte, AddressSource.Signature, Confidence: 0.95),
+            ["test_float"] = new SymbolInfo("test_float", (nint)0x7000, SymbolValueType.Float, AddressSource.Signature, Confidence: 0.95),
+            ["test_bool"] = new SymbolInfo("test_bool", (nint)0x9000, SymbolValueType.Bool, AddressSource.Signature, Confidence: 0.95)
+        });
+
+        var session = new AttachSession(
+            "profile",
+            new ProcessMetadata(
+                ProcessId: Environment.ProcessId,
+                ProcessName: "swfoc",
+                ProcessPath: @"C:\Games\swfoc.exe",
+                CommandLine: null,
+                ExeTarget: ExeTarget.Swfoc,
+                Mode: mode,
+                Metadata: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)),
+            new ProfileBuild("profile", "build", @"C:\Games\swfoc.exe", ExeTarget.Swfoc),
+            symbolMap,
+            DateTimeOffset.UtcNow);
+
+        typeof(RuntimeAdapter)
+            .GetProperty("CurrentSession", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
+            .SetValue(adapter, session);
+        SetField(adapter, "_attachedProfile", profile);
+
+        var memType = typeof(RuntimeAdapter).Assembly.GetType("SwfocTrainer.Runtime.Interop.ProcessMemoryAccessor")!;
+        var accessor = RuntimeHelpers.GetUninitializedObject(memType);
+        SetField(adapter, "_memory", accessor);
+
+        return adapter;
+    }
Evidence
The checklist caps methods at 50 lines and 5 parameters. The newly added helper
CreateAttachedAdapter(...) exceeds both limits (8 parameters and ~77 lines).

CLAUDE.md
tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[33-109]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`CreateAttachedAdapter` exceeds the method-size and parameter-count limits (over 50 lines and more than 5 parameters).

## Issue Context
This helper is newly added in the PR and will be a central test utility; keeping it small/composable reduces future test brittleness.

## Fix Focus Areas
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[33-109]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Null-forgiving ! in runtime tests 📘 Rule violation ☼ Reliability
Description
The new tests introduce multiple uses of the null-forgiving operator (!), which violates the
explicit null-safety requirement. This can mask real nullability issues and bypass required guards.
Code

tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[R26-106]

+    private static void SetField(object target, string name, object? value)
+    {
+        var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
+        field.Should().NotBeNull($"field '{name}' should exist");
+        field!.SetValue(target, value);
+    }
+
+    private static RuntimeAdapter CreateAttachedAdapter(
+        RuntimeMode mode = RuntimeMode.Galactic,
+        TrainerProfile? profile = null,
+        IBackendRouter? router = null,
+        IHelperBridgeBackend? helperBackend = null,
+        IModMechanicDetectionService? mechanicService = null,
+        ISdkOperationRouter? sdkRouter = null,
+        IExecutionBackend? executionBackend = null,
+        bool includeExecutionBackend = true)
+    {
+        profile ??= BuildProfile("set_credits");
+        var services = new Dictionary<Type, object>
+        {
+            [typeof(IBackendRouter)] = router ?? new StubBackendRouter(
+                new BackendRouteDecision(true, ExecutionBackendKind.Memory, RuntimeReasonCode.CAPABILITY_PROBE_PASS, "ok")),
+            [typeof(IHelperBridgeBackend)] = helperBackend ?? new StubHelperBridgeBackend(),
+            [typeof(IModDependencyValidator)] = new StubDependencyValidator(
+                new DependencyValidationResult(DependencyValidationStatus.Pass, "", new HashSet<string>(StringComparer.OrdinalIgnoreCase))),
+            [typeof(ITelemetryLogTailService)] = new StubTelemetryLogTailService()
+        };
+
+        if (includeExecutionBackend)
+        {
+            services[typeof(IExecutionBackend)] = executionBackend ?? new StubExecutionBackend();
+        }
+
+        if (mechanicService is not null)
+        {
+            services[typeof(IModMechanicDetectionService)] = mechanicService;
+        }
+
+        if (sdkRouter is not null)
+        {
+            services[typeof(ISdkOperationRouter)] = sdkRouter;
+        }
+
+        var adapter = new RuntimeAdapter(
+            new StubProcessLocator(),
+            new StubProfileRepository(profile),
+            new StubSignatureResolver(),
+            NullLogger<RuntimeAdapter>.Instance,
+            new MapServiceProvider(services));
+
+        var symbolMap = new SymbolMap(new Dictionary<string, SymbolInfo>(StringComparer.OrdinalIgnoreCase)
+        {
+            ["credits"] = new SymbolInfo("credits", (nint)0x1000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
+            ["unit_cap"] = new SymbolInfo("unit_cap", (nint)0x2000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
+            ["fog_reveal"] = new SymbolInfo("fog_reveal", (nint)0x3000, SymbolValueType.Byte, AddressSource.Signature, Confidence: 0.95),
+            ["test_float"] = new SymbolInfo("test_float", (nint)0x7000, SymbolValueType.Float, AddressSource.Signature, Confidence: 0.95),
+            ["test_bool"] = new SymbolInfo("test_bool", (nint)0x9000, SymbolValueType.Bool, AddressSource.Signature, Confidence: 0.95)
+        });
+
+        var session = new AttachSession(
+            "profile",
+            new ProcessMetadata(
+                ProcessId: Environment.ProcessId,
+                ProcessName: "swfoc",
+                ProcessPath: @"C:\Games\swfoc.exe",
+                CommandLine: null,
+                ExeTarget: ExeTarget.Swfoc,
+                Mode: mode,
+                Metadata: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)),
+            new ProfileBuild("profile", "build", @"C:\Games\swfoc.exe", ExeTarget.Swfoc),
+            symbolMap,
+            DateTimeOffset.UtcNow);
+
+        typeof(RuntimeAdapter)
+            .GetProperty("CurrentSession", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
+            .SetValue(adapter, session);
+        SetField(adapter, "_attachedProfile", profile);
+
+        var memType = typeof(RuntimeAdapter).Assembly.GetType("SwfocTrainer.Runtime.Interop.ProcessMemoryAccessor")!;
+        var accessor = RuntimeHelpers.GetUninitializedObject(memType);
+        SetField(adapter, "_memory", accessor);
Evidence
The compliance rule forbids using the null-forgiving operator as a shortcut. The added code uses !
to suppress nullability at several points (e.g., field!, reflection GetProperty(...)!, and
GetType(...)!).

CLAUDE.md
tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[26-31]
tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[99-106]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The tests use the null-forgiving operator (`!`) in multiple places, which is disallowed by the null-safety compliance rule.

## Issue Context
Most of these are reflection lookups and nullable values where a direct `?? throw` or other explicit guard can preserve safety without suppressing nullability.

## Fix Focus Areas
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[26-31]
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[99-106]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. AppData settings test race 🐞 Bug ☼ Reliability
Description
AppWave7FinalTests reads/writes the real LocalAppData runtime-mode settings file, which can race
with other test classes that also call MainViewModelRuntimeModeOverrideHelpers.Save/Load and cause
flaky assertions and persistent user-machine side effects. This is especially risky when tests
execute concurrently.
Code

tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs[R74-116]

+    [Fact]
+    public void Load_FileNotFound_ShouldReturnAuto()
+    {
+        // Temporarily override the settings path to a nonexistent location
+        // Since GetSettingsPath is private, we test via the public Load method
+        // which will use the actual app data path
+        var result = MainViewModelRuntimeModeOverrideHelpers.Load();
+        // Should return a valid string (either "auto" or whatever is currently saved)
+        result.Should().NotBeNullOrWhiteSpace();
+    }
+
+    [Fact]
+    public void Save_ThenLoad_ShouldRoundTrip()
+    {
+        // Save a known value then load it back
+        var originalValue = MainViewModelRuntimeModeOverrideHelpers.Load();
+        try
+        {
+            MainViewModelRuntimeModeOverrideHelpers.Save("Galactic");
+            var loaded = MainViewModelRuntimeModeOverrideHelpers.Load();
+            loaded.Should().Be("Galactic");
+        }
+        finally
+        {
+            // Restore original
+            MainViewModelRuntimeModeOverrideHelpers.Save(originalValue);
+        }
+    }
+
+    [Fact]
+    public void Save_NullValue_ShouldNormalizeToAuto()
+    {
+        var originalValue = MainViewModelRuntimeModeOverrideHelpers.Load();
+        try
+        {
+            MainViewModelRuntimeModeOverrideHelpers.Save(null);
+            var loaded = MainViewModelRuntimeModeOverrideHelpers.Load();
+            loaded.Should().Be("Auto");
+        }
+        finally
+        {
+            MainViewModelRuntimeModeOverrideHelpers.Save(originalValue);
+        }
Evidence
The new tests call Save/Load which write to a shared file under
TrustedPathPolicy.GetOrCreateAppDataRoot(). Other existing test classes also call the same Save/Load
methods, so concurrent execution can observe transient values or overwrite each other’s expected
state.

tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs[74-116]
src/SwfocTrainer.App/ViewModels/MainViewModelRuntimeModeOverrideHelpers.cs[65-109]
tests/SwfocTrainer.Tests/App/MainViewModelRuntimeModeOverrideBranchTests.cs[86-119]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`MainViewModelRuntimeModeOverrideHelpers.Save/Load` persists to a real per-user LocalAppData file (`runtime-mode-settings.json`). Multiple test classes call these methods, so tests can interfere with each other (and with a developer’s real settings) when the runner executes classes concurrently.

### Issue Context
- The helper writes to `Path.Join(TrustedPathPolicy.GetOrCreateAppDataRoot(), "runtime-mode-settings.json")`.
- `AppWave7FinalTests` adds more Save/Load tests, increasing the collision window.

### Fix Focus Areas
- tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs[74-116]
- tests/SwfocTrainer.Tests/App/MainViewModelRuntimeModeOverrideBranchTests.cs[86-119]
- src/SwfocTrainer.App/ViewModels/MainViewModelRuntimeModeOverrideHelpers.cs[65-109]

### Suggested fix
1. Put *all* tests that touch `MainViewModelRuntimeModeOverrideHelpers.Save/Load` into a shared xUnit collection with parallelization disabled (e.g., `[Collection("RuntimeModeOverrideSettings")]` + `[CollectionDefinition(..., DisableParallelization=true)]`).
2. In each test, snapshot the settings file contents (or whether it exists) at start and restore it in `finally` (restore the file contents, not just the mode string), so developer machines/CI agents are not left mutated.
3. (Optional but best) Refactor `MainViewModelRuntimeModeOverrideHelpers` to allow overriding the settings path for tests (e.g., internal setter / internal `GetSettingsPath` delegate) so tests can use a temp directory instead of LocalAppData.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Env var tests interfere 🐞 Bug ☼ Reliability
Description
RuntimeAdapterAsyncWave7Tests mutates process-global environment variables
SWFOC_EXPERT_MUTATION_OVERRIDES and SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC, which are also
mutated/read by other RuntimeAdapter test classes. Concurrent execution can observe transient values
and fail nondeterministically.
Code

tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[R729-757]

+    [Fact]
+    public async Task ExecuteAsync_ExpertOverride_PanicDisableActive_NoOverride()
+    {
+        var previousOverride = Environment.GetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES");
+        var previousPanic = Environment.GetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC");
+        try
+        {
+            Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES", "1");
+            Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC", "1");
+            var profile = BuildProfile("set_unit_cap");
+            var adapter = CreateAttachedAdapter(
+                profile: profile,
+                router: new StubBackendRouter(
+                    new BackendRouteDecision(false, ExecutionBackendKind.Extender, RuntimeReasonCode.CAPABILITY_REQUIRED_MISSING, "blocked")));
+
+            var result = await adapter.ExecuteAsync(
+                BuildRequest("set_unit_cap", RuntimeMode.Galactic),
+                CancellationToken.None);
+
+            // Panic disable overrides the expert override — should be blocked
+            result.Succeeded.Should().BeFalse();
+            result.Diagnostics.Should().ContainKey("panicDisableState");
+            result.Diagnostics!["panicDisableState"]!.ToString().Should().Be("active");
+        }
+        finally
+        {
+            Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES", previousOverride);
+            Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC", previousPanic);
+        }
Evidence
Production code reads these env vars to decide expert override behavior. Multiple test classes
set/restore the same env vars, so concurrent tests can affect each other during the window between
Set and finally-restore.

tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[705-860]
tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterWave6Tests.cs[1294-1357]
src/SwfocTrainer.Runtime/Services/RuntimeAdapter.Constants.cs[24-28]
src/SwfocTrainer.Runtime/Services/RuntimeAdapter.cs[2055-2086]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Multiple test classes set `SWFOC_EXPERT_MUTATION_OVERRIDES*` environment variables. Because environment variables are process-global, concurrent tests can read the wrong value mid-test, causing flakes.

### Issue Context
`RuntimeAdapter.ResolveExpertMutationOverrideState()` reads the env vars to control expert override behavior. Both `RuntimeAdapterAsyncWave7Tests` and existing `RuntimeAdapterWave6Tests` set these env vars.

### Fix Focus Areas
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[705-860]
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterWave6Tests.cs[1294-1357]
- src/SwfocTrainer.Runtime/Services/RuntimeAdapter.cs[2055-2086]

### Suggested fix
1. Put all tests that set/read these env vars into the same xUnit collection with parallelization disabled.
2. Optionally add a shared static lock helper (e.g., `EnvironmentVariableLock`) and wrap every env-var mutation/read-sensitive test section in that lock to enforce mutual exclusion.
3. Keep the existing try/finally restoration, but ensure it’s used consistently across all tests that touch these env vars.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Temp schema dirs leaked 🐞 Bug ☼ Reliability
Description
SavesWave7FinalTests.CreatePackService creates a unique temp schema directory and never deletes it,
leaking files/directories across test runs. This can accumulate in %TEMP% and degrade CI/dev
environments over time.
Code

tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs[R429-434]

+    private static SavePatchPackService CreatePackService()
+    {
+        var tempDir = Path.Combine(Path.GetTempPath(), "saves-w7-schemas-" + Guid.NewGuid().ToString("N"));
+        Directory.CreateDirectory(tempDir);
+        return new SavePatchPackService(new SaveOptions { SchemaRootPath = tempDir });
+    }
Evidence
The helper unconditionally creates a new temp directory for SchemaRootPath and returns a service;
there is no cleanup path for that directory in the helper or calling tests.

tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs[429-434]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`CreatePackService()` creates a unique directory under `Path.GetTempPath()` and never removes it, causing deterministic temp-directory leaks.

### Issue Context
The helper is invoked by multiple tests; each invocation leaks a directory even on success.

### Fix Focus Areas
- tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs[429-434]

### Suggested fix
- Change `CreatePackService()` to return both the `SavePatchPackService` and the created `tempDir` (or wrap them in a small disposable/fixture type), and ensure the caller deletes `tempDir` in a `finally`/`Dispose`.
- Alternatively, avoid creating a new temp dir per call by using a per-test temp dir created in the test body and cleaned up in that test’s `finally` block.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Apr 4, 2026

DeepSource Code Review

We reviewed changes in 2d5f971...3ccc21b on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Terraform Apr 4, 2026 3:01p.m. Review ↗
SQL Apr 4, 2026 3:01p.m. Review ↗
Rust Apr 4, 2026 3:01p.m. Review ↗
Shell Apr 4, 2026 3:01p.m. Review ↗
Ruby Apr 4, 2026 3:01p.m. Review ↗
PHP Apr 4, 2026 3:01p.m. Review ↗
Kotlin Apr 4, 2026 3:01p.m. Review ↗
Swift Apr 4, 2026 3:01p.m. Review ↗
Scala Apr 4, 2026 3:01p.m. Review ↗
Python Apr 4, 2026 3:01p.m. Review ↗
JavaScript Apr 4, 2026 3:01p.m. Review ↗
Java Apr 4, 2026 3:01p.m. Review ↗
Go Apr 4, 2026 3:01p.m. Review ↗
Docker Apr 4, 2026 3:01p.m. Review ↗
C & C++ Apr 4, 2026 3:01p.m. Review ↗
Ansible Apr 4, 2026 3:01p.m. Review ↗

@Prekzursil Prekzursil merged commit 3871476 into main Apr 4, 2026
41 of 52 checks passed
@qltysh
Copy link
Copy Markdown

qltysh Bot commented Apr 4, 2026

❌ 12 blocking issues (12 total)

Tool Category Rule Count
qlty Duplication Found 19 lines of similar code in 4 locations (mass = 75) 11
qlty Structure Function with many parameters (count = 8): CreateAttachedAdapter 1

IHelperBridgeBackend? helperBackend = null,
IModMechanicDetectionService? mechanicService = null,
ISdkOperationRouter? sdkRouter = null,
IExecutionBackend? executionBackend = null,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function with many parameters (count = 8): CreateAttachedAdapter [qlty:function-parameters]

CooldownMs: 0),
Payload: payload,
ProfileId: "profile",
RuntimeMode: runtimeMode);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 19 lines of similar code in 4 locations (mass = 75) [qlty:similar-code]

finally
{
Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES", previous);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 25 lines of similar code in 2 locations (mass = 133) [qlty:similar-code]

finally
{
Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES", previous);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 25 lines of similar code in 2 locations (mass = 133) [qlty:similar-code]

CancellationToken.None);

result.Succeeded.Should().BeTrue();
result.Diagnostics.Should().ContainKey("helperEntryPoint");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 21 lines of similar code in 2 locations (mass = 148) [qlty:similar-code]

CancellationToken.None);

result.Diagnostics.Should().ContainKey("contextRoutedAction");
result.Diagnostics!["contextRoutedAction"]!.ToString().Should().Be("spawn_galactic_entity");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 17 lines of similar code in 3 locations (mass = 129) [qlty:similar-code]

CancellationToken.None);

result.Diagnostics.Should().ContainKey("contextRoutedAction");
result.Diagnostics!["contextRoutedAction"]!.ToString().Should().Be("set_selected_owner_faction");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 17 lines of similar code in 3 locations (mass = 129) [qlty:similar-code]

finally
{
Directory.Delete(tempDir, true);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 41 lines of similar code in 3 locations (mass = 127) [qlty:similar-code]

finally
{
Directory.Delete(tempDir, true);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 29 lines of similar code in 3 locations (mass = 127) [qlty:similar-code]

finally
{
Directory.Delete(tempDir, true);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found 32 lines of similar code in 3 locations (mass = 127) [qlty:similar-code]

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 4, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
1 New issue

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@codacy-production
Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 3 high · 47 medium · 2 minor

Alerts:
⚠ 52 issues (≤ 0 issues of at least minor severity)
⚠ 3 issues (≤ 0 issues of at least minor severity)

Results:
52 new issues

Category Results
Compatibility 8 medium
UnusedCode 2 medium
BestPractice 35 medium
Security 3 high
CodeStyle 2 minor
Complexity 2 medium

View in Codacy

🔴 Metrics 264 complexity · 95 duplication

Metric Results
Complexity ⚠️ 264 (≤ 10 complexity)
Duplication 95 (≤ 0 duplication)

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

Comment on lines +33 to +109
private static RuntimeAdapter CreateAttachedAdapter(
RuntimeMode mode = RuntimeMode.Galactic,
TrainerProfile? profile = null,
IBackendRouter? router = null,
IHelperBridgeBackend? helperBackend = null,
IModMechanicDetectionService? mechanicService = null,
ISdkOperationRouter? sdkRouter = null,
IExecutionBackend? executionBackend = null,
bool includeExecutionBackend = true)
{
profile ??= BuildProfile("set_credits");
var services = new Dictionary<Type, object>
{
[typeof(IBackendRouter)] = router ?? new StubBackendRouter(
new BackendRouteDecision(true, ExecutionBackendKind.Memory, RuntimeReasonCode.CAPABILITY_PROBE_PASS, "ok")),
[typeof(IHelperBridgeBackend)] = helperBackend ?? new StubHelperBridgeBackend(),
[typeof(IModDependencyValidator)] = new StubDependencyValidator(
new DependencyValidationResult(DependencyValidationStatus.Pass, "", new HashSet<string>(StringComparer.OrdinalIgnoreCase))),
[typeof(ITelemetryLogTailService)] = new StubTelemetryLogTailService()
};

if (includeExecutionBackend)
{
services[typeof(IExecutionBackend)] = executionBackend ?? new StubExecutionBackend();
}

if (mechanicService is not null)
{
services[typeof(IModMechanicDetectionService)] = mechanicService;
}

if (sdkRouter is not null)
{
services[typeof(ISdkOperationRouter)] = sdkRouter;
}

var adapter = new RuntimeAdapter(
new StubProcessLocator(),
new StubProfileRepository(profile),
new StubSignatureResolver(),
NullLogger<RuntimeAdapter>.Instance,
new MapServiceProvider(services));

var symbolMap = new SymbolMap(new Dictionary<string, SymbolInfo>(StringComparer.OrdinalIgnoreCase)
{
["credits"] = new SymbolInfo("credits", (nint)0x1000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
["unit_cap"] = new SymbolInfo("unit_cap", (nint)0x2000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
["fog_reveal"] = new SymbolInfo("fog_reveal", (nint)0x3000, SymbolValueType.Byte, AddressSource.Signature, Confidence: 0.95),
["test_float"] = new SymbolInfo("test_float", (nint)0x7000, SymbolValueType.Float, AddressSource.Signature, Confidence: 0.95),
["test_bool"] = new SymbolInfo("test_bool", (nint)0x9000, SymbolValueType.Bool, AddressSource.Signature, Confidence: 0.95)
});

var session = new AttachSession(
"profile",
new ProcessMetadata(
ProcessId: Environment.ProcessId,
ProcessName: "swfoc",
ProcessPath: @"C:\Games\swfoc.exe",
CommandLine: null,
ExeTarget: ExeTarget.Swfoc,
Mode: mode,
Metadata: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)),
new ProfileBuild("profile", "build", @"C:\Games\swfoc.exe", ExeTarget.Swfoc),
symbolMap,
DateTimeOffset.UtcNow);

typeof(RuntimeAdapter)
.GetProperty("CurrentSession", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
.SetValue(adapter, session);
SetField(adapter, "_attachedProfile", profile);

var memType = typeof(RuntimeAdapter).Assembly.GetType("SwfocTrainer.Runtime.Interop.ProcessMemoryAccessor")!;
var accessor = RuntimeHelpers.GetUninitializedObject(memType);
SetField(adapter, "_memory", accessor);

return adapter;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. createattachedadapter too many params 📘 Rule violation ⚙ Maintainability

CreateAttachedAdapter has 8 parameters and spans well over 50 lines, exceeding the repo’s method
size/parameter limits. This increases maintenance burden and makes future changes harder to test and
review.
Agent Prompt
## Issue description
`CreateAttachedAdapter` exceeds the method-size and parameter-count limits (over 50 lines and more than 5 parameters).

## Issue Context
This helper is newly added in the PR and will be a central test utility; keeping it small/composable reduces future test brittleness.

## Fix Focus Areas
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[33-109]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +26 to +106
private static void SetField(object target, string name, object? value)
{
var field = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
field.Should().NotBeNull($"field '{name}' should exist");
field!.SetValue(target, value);
}

private static RuntimeAdapter CreateAttachedAdapter(
RuntimeMode mode = RuntimeMode.Galactic,
TrainerProfile? profile = null,
IBackendRouter? router = null,
IHelperBridgeBackend? helperBackend = null,
IModMechanicDetectionService? mechanicService = null,
ISdkOperationRouter? sdkRouter = null,
IExecutionBackend? executionBackend = null,
bool includeExecutionBackend = true)
{
profile ??= BuildProfile("set_credits");
var services = new Dictionary<Type, object>
{
[typeof(IBackendRouter)] = router ?? new StubBackendRouter(
new BackendRouteDecision(true, ExecutionBackendKind.Memory, RuntimeReasonCode.CAPABILITY_PROBE_PASS, "ok")),
[typeof(IHelperBridgeBackend)] = helperBackend ?? new StubHelperBridgeBackend(),
[typeof(IModDependencyValidator)] = new StubDependencyValidator(
new DependencyValidationResult(DependencyValidationStatus.Pass, "", new HashSet<string>(StringComparer.OrdinalIgnoreCase))),
[typeof(ITelemetryLogTailService)] = new StubTelemetryLogTailService()
};

if (includeExecutionBackend)
{
services[typeof(IExecutionBackend)] = executionBackend ?? new StubExecutionBackend();
}

if (mechanicService is not null)
{
services[typeof(IModMechanicDetectionService)] = mechanicService;
}

if (sdkRouter is not null)
{
services[typeof(ISdkOperationRouter)] = sdkRouter;
}

var adapter = new RuntimeAdapter(
new StubProcessLocator(),
new StubProfileRepository(profile),
new StubSignatureResolver(),
NullLogger<RuntimeAdapter>.Instance,
new MapServiceProvider(services));

var symbolMap = new SymbolMap(new Dictionary<string, SymbolInfo>(StringComparer.OrdinalIgnoreCase)
{
["credits"] = new SymbolInfo("credits", (nint)0x1000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
["unit_cap"] = new SymbolInfo("unit_cap", (nint)0x2000, SymbolValueType.Int32, AddressSource.Signature, Confidence: 0.95),
["fog_reveal"] = new SymbolInfo("fog_reveal", (nint)0x3000, SymbolValueType.Byte, AddressSource.Signature, Confidence: 0.95),
["test_float"] = new SymbolInfo("test_float", (nint)0x7000, SymbolValueType.Float, AddressSource.Signature, Confidence: 0.95),
["test_bool"] = new SymbolInfo("test_bool", (nint)0x9000, SymbolValueType.Bool, AddressSource.Signature, Confidence: 0.95)
});

var session = new AttachSession(
"profile",
new ProcessMetadata(
ProcessId: Environment.ProcessId,
ProcessName: "swfoc",
ProcessPath: @"C:\Games\swfoc.exe",
CommandLine: null,
ExeTarget: ExeTarget.Swfoc,
Mode: mode,
Metadata: new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)),
new ProfileBuild("profile", "build", @"C:\Games\swfoc.exe", ExeTarget.Swfoc),
symbolMap,
DateTimeOffset.UtcNow);

typeof(RuntimeAdapter)
.GetProperty("CurrentSession", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)!
.SetValue(adapter, session);
SetField(adapter, "_attachedProfile", profile);

var memType = typeof(RuntimeAdapter).Assembly.GetType("SwfocTrainer.Runtime.Interop.ProcessMemoryAccessor")!;
var accessor = RuntimeHelpers.GetUninitializedObject(memType);
SetField(adapter, "_memory", accessor);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Null-forgiving ! in runtime tests 📘 Rule violation ☼ Reliability

The new tests introduce multiple uses of the null-forgiving operator (!), which violates the
explicit null-safety requirement. This can mask real nullability issues and bypass required guards.
Agent Prompt
## Issue description
The tests use the null-forgiving operator (`!`) in multiple places, which is disallowed by the null-safety compliance rule.

## Issue Context
Most of these are reflection lookups and nullable values where a direct `?? throw` or other explicit guard can preserve safety without suppressing nullability.

## Fix Focus Areas
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[26-31]
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[99-106]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +74 to +116
[Fact]
public void Load_FileNotFound_ShouldReturnAuto()
{
// Temporarily override the settings path to a nonexistent location
// Since GetSettingsPath is private, we test via the public Load method
// which will use the actual app data path
var result = MainViewModelRuntimeModeOverrideHelpers.Load();
// Should return a valid string (either "auto" or whatever is currently saved)
result.Should().NotBeNullOrWhiteSpace();
}

[Fact]
public void Save_ThenLoad_ShouldRoundTrip()
{
// Save a known value then load it back
var originalValue = MainViewModelRuntimeModeOverrideHelpers.Load();
try
{
MainViewModelRuntimeModeOverrideHelpers.Save("Galactic");
var loaded = MainViewModelRuntimeModeOverrideHelpers.Load();
loaded.Should().Be("Galactic");
}
finally
{
// Restore original
MainViewModelRuntimeModeOverrideHelpers.Save(originalValue);
}
}

[Fact]
public void Save_NullValue_ShouldNormalizeToAuto()
{
var originalValue = MainViewModelRuntimeModeOverrideHelpers.Load();
try
{
MainViewModelRuntimeModeOverrideHelpers.Save(null);
var loaded = MainViewModelRuntimeModeOverrideHelpers.Load();
loaded.Should().Be("Auto");
}
finally
{
MainViewModelRuntimeModeOverrideHelpers.Save(originalValue);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Appdata settings test race 🐞 Bug ☼ Reliability

AppWave7FinalTests reads/writes the real LocalAppData runtime-mode settings file, which can race
with other test classes that also call MainViewModelRuntimeModeOverrideHelpers.Save/Load and cause
flaky assertions and persistent user-machine side effects. This is especially risky when tests
execute concurrently.
Agent Prompt
### Issue description
`MainViewModelRuntimeModeOverrideHelpers.Save/Load` persists to a real per-user LocalAppData file (`runtime-mode-settings.json`). Multiple test classes call these methods, so tests can interfere with each other (and with a developer’s real settings) when the runner executes classes concurrently.

### Issue Context
- The helper writes to `Path.Join(TrustedPathPolicy.GetOrCreateAppDataRoot(), "runtime-mode-settings.json")`.
- `AppWave7FinalTests` adds more Save/Load tests, increasing the collision window.

### Fix Focus Areas
- tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs[74-116]
- tests/SwfocTrainer.Tests/App/MainViewModelRuntimeModeOverrideBranchTests.cs[86-119]
- src/SwfocTrainer.App/ViewModels/MainViewModelRuntimeModeOverrideHelpers.cs[65-109]

### Suggested fix
1. Put *all* tests that touch `MainViewModelRuntimeModeOverrideHelpers.Save/Load` into a shared xUnit collection with parallelization disabled (e.g., `[Collection("RuntimeModeOverrideSettings")]` + `[CollectionDefinition(..., DisableParallelization=true)]`).
2. In each test, snapshot the settings file contents (or whether it exists) at start and restore it in `finally` (restore the file contents, not just the mode string), so developer machines/CI agents are not left mutated.
3. (Optional but best) Refactor `MainViewModelRuntimeModeOverrideHelpers` to allow overriding the settings path for tests (e.g., internal setter / internal `GetSettingsPath` delegate) so tests can use a temp directory instead of LocalAppData.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +729 to +757
[Fact]
public async Task ExecuteAsync_ExpertOverride_PanicDisableActive_NoOverride()
{
var previousOverride = Environment.GetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES");
var previousPanic = Environment.GetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC");
try
{
Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES", "1");
Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC", "1");
var profile = BuildProfile("set_unit_cap");
var adapter = CreateAttachedAdapter(
profile: profile,
router: new StubBackendRouter(
new BackendRouteDecision(false, ExecutionBackendKind.Extender, RuntimeReasonCode.CAPABILITY_REQUIRED_MISSING, "blocked")));

var result = await adapter.ExecuteAsync(
BuildRequest("set_unit_cap", RuntimeMode.Galactic),
CancellationToken.None);

// Panic disable overrides the expert override — should be blocked
result.Succeeded.Should().BeFalse();
result.Diagnostics.Should().ContainKey("panicDisableState");
result.Diagnostics!["panicDisableState"]!.ToString().Should().Be("active");
}
finally
{
Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES", previousOverride);
Environment.SetEnvironmentVariable("SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC", previousPanic);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Env var tests interfere 🐞 Bug ☼ Reliability

RuntimeAdapterAsyncWave7Tests mutates process-global environment variables
SWFOC_EXPERT_MUTATION_OVERRIDES and SWFOC_EXPERT_MUTATION_OVERRIDES_PANIC, which are also
mutated/read by other RuntimeAdapter test classes. Concurrent execution can observe transient values
and fail nondeterministically.
Agent Prompt
### Issue description
Multiple test classes set `SWFOC_EXPERT_MUTATION_OVERRIDES*` environment variables. Because environment variables are process-global, concurrent tests can read the wrong value mid-test, causing flakes.

### Issue Context
`RuntimeAdapter.ResolveExpertMutationOverrideState()` reads the env vars to control expert override behavior. Both `RuntimeAdapterAsyncWave7Tests` and existing `RuntimeAdapterWave6Tests` set these env vars.

### Fix Focus Areas
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs[705-860]
- tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterWave6Tests.cs[1294-1357]
- src/SwfocTrainer.Runtime/Services/RuntimeAdapter.cs[2055-2086]

### Suggested fix
1. Put all tests that set/read these env vars into the same xUnit collection with parallelization disabled.
2. Optionally add a shared static lock helper (e.g., `EnvironmentVariableLock`) and wrap every env-var mutation/read-sensitive test section in that lock to enforce mutual exclusion.
3. Keep the existing try/finally restoration, but ensure it’s used consistently across all tests that touch these env vars.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs">

<violation number="1" location="tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs:978">
P2: These exception tests don't await `ThrowAsync`, so the assertions can be skipped and produce false-positive passing tests.</violation>
</file>

<file name="tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs">

<violation number="1" location="tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs:42">
P2: This test name claims mod loose entries are skipped, but it only asserts `report` is non-null, so regressions in whitespace MODPATH handling would still pass unnoticed.</violation>

<violation number="2" location="tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs:64">
P2: This test does not verify `MegaFiles.xml` diagnostics propagation: the XML input is valid, and the assertion matches MEG file-not-found diagnostics instead of checking propagated `MegaFiles.xml:` diagnostics.</violation>
</file>

<file name="tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs">

<violation number="1" location="tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs:29">
P2: This test claims to verify empty-entry handling but only exercises a normal `valid.txt` extraction path, so it does not validate the intended branch.</violation>

<violation number="2" location="tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs:173">
P2: This test is labeled as integration coverage for exception catch paths, but it never invokes `ModOnboardingService`, so the targeted catch blocks are not tested.</violation>
</file>

<file name="tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs">

<violation number="1" location="tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs:431">
P3: Avoid creating a new unmanaged schema temp directory per call; this helper currently leaks temp folders across test runs.</violation>
</file>

<file name="tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs">

<violation number="1" location="tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs:80">
P2: These tests hit the real app-data settings file without any skip/guard. Per tests/AGENTS.md, live tests must skip with an explicit reason when prerequisites (writable settings path) are absent. Add an explicit skip/guard or isolate the settings path so this doesn’t fail or mutate developer settings on CI/local runs.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.

}

[Fact]
public void ExecuteAsync_NullRequest_Throws()
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: These exception tests don't await ThrowAsync, so the assertions can be skipped and produce false-positive passing tests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/SwfocTrainer.Tests/Runtime/RuntimeAdapterAsyncWave7Tests.cs, line 978:

<comment>These exception tests don't await `ThrowAsync`, so the assertions can be skipped and produce false-positive passing tests.</comment>

<file context>
@@ -0,0 +1,1868 @@
+    }
+
+    [Fact]
+    public void ExecuteAsync_NullRequest_Throws()
+    {
+        var adapter = CreateAttachedAdapter();
</file context>
Fix with Cubic

}

[Fact]
public void Build_WithWhitespaceModPath_ShouldSkipModLooseEntries()
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test name claims mod loose entries are skipped, but it only asserts report is non-null, so regressions in whitespace MODPATH handling would still pass unnoticed.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs, line 42:

<comment>This test name claims mod loose entries are skipped, but it only asserts `report` is non-null, so regressions in whitespace MODPATH handling would still pass unnoticed.</comment>

<file context>
@@ -0,0 +1,140 @@
+    }
+
+    [Fact]
+    public void Build_WithWhitespaceModPath_ShouldSkipModLooseEntries()
+    {
+        var tempDir = Path.Combine(Path.GetTempPath(), "di-w7-ws-" + Guid.NewGuid().ToString("N"));
</file context>
Fix with Cubic

#region MegaFiles.xml diagnostics propagation (lines 99-102)

[Fact]
public void Build_WhenMegaFilesXmlHasDiagnostics_ShouldPropagate()
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test does not verify MegaFiles.xml diagnostics propagation: the XML input is valid, and the assertion matches MEG file-not-found diagnostics instead of checking propagated MegaFiles.xml: diagnostics.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/SwfocTrainer.Tests/DataIndex/DataIndexWave7FinalTests.cs, line 64:

<comment>This test does not verify `MegaFiles.xml` diagnostics propagation: the XML input is valid, and the assertion matches MEG file-not-found diagnostics instead of checking propagated `MegaFiles.xml:` diagnostics.</comment>

<file context>
@@ -0,0 +1,140 @@
+    #region MegaFiles.xml diagnostics propagation (lines 99-102)
+
+    [Fact]
+    public void Build_WhenMegaFilesXmlHasDiagnostics_ShouldPropagate()
+    {
+        var tempDir = Path.Combine(Path.GetTempPath(), "di-w7-diag-" + Guid.NewGuid().ToString("N"));
</file context>
Fix with Cubic

#region GitHubProfileUpdateExtractionHelpers (lines 24-25, 70-71)

[Fact]
public void ExtractToDirectorySafely_EntryWithEmptyName_ShouldSkipEntry()
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test claims to verify empty-entry handling but only exercises a normal valid.txt extraction path, so it does not validate the intended branch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs, line 29:

<comment>This test claims to verify empty-entry handling but only exercises a normal `valid.txt` extraction path, so it does not validate the intended branch.</comment>

<file context>
@@ -0,0 +1,226 @@
+    #region GitHubProfileUpdateExtractionHelpers (lines 24-25, 70-71)
+
+    [Fact]
+    public void ExtractToDirectorySafely_EntryWithEmptyName_ShouldSkipEntry()
+    {
+        var tempDir = Path.Combine(Path.GetTempPath(), "prof-w7-" + Guid.NewGuid().ToString("N"));
</file context>
Fix with Cubic

#region ModOnboardingService — exception catch blocks (lines 212-227)

[Fact]
public void ModOnboardingService_ExceptionCatchPaths_AreTestedViaIntegration()
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test is labeled as integration coverage for exception catch paths, but it never invokes ModOnboardingService, so the targeted catch blocks are not tested.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/SwfocTrainer.Tests/Profiles/ProfilesWave7FinalTests.cs, line 173:

<comment>This test is labeled as integration coverage for exception catch paths, but it never invokes `ModOnboardingService`, so the targeted catch blocks are not tested.</comment>

<file context>
@@ -0,0 +1,226 @@
+    #region ModOnboardingService — exception catch blocks (lines 212-227)
+
+    [Fact]
+    public void ModOnboardingService_ExceptionCatchPaths_AreTestedViaIntegration()
+    {
+        // The catch blocks at lines 212-227 handle IOException, InvalidOperationException,
</file context>
Fix with Cubic

// Temporarily override the settings path to a nonexistent location
// Since GetSettingsPath is private, we test via the public Load method
// which will use the actual app data path
var result = MainViewModelRuntimeModeOverrideHelpers.Load();
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: These tests hit the real app-data settings file without any skip/guard. Per tests/AGENTS.md, live tests must skip with an explicit reason when prerequisites (writable settings path) are absent. Add an explicit skip/guard or isolate the settings path so this doesn’t fail or mutate developer settings on CI/local runs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/SwfocTrainer.Tests/App/AppWave7FinalTests.cs, line 80:

<comment>These tests hit the real app-data settings file without any skip/guard. Per tests/AGENTS.md, live tests must skip with an explicit reason when prerequisites (writable settings path) are absent. Add an explicit skip/guard or isolate the settings path so this doesn’t fail or mutate developer settings on CI/local runs.</comment>

<file context>
@@ -0,0 +1,142 @@
+        // Temporarily override the settings path to a nonexistent location
+        // Since GetSettingsPath is private, we test via the public Load method
+        // which will use the actual app data path
+        var result = MainViewModelRuntimeModeOverrideHelpers.Load();
+        // Should return a valid string (either "auto" or whatever is currently saved)
+        result.Should().NotBeNullOrWhiteSpace();
</file context>
Fix with Cubic


private static SavePatchPackService CreatePackService()
{
var tempDir = Path.Combine(Path.GetTempPath(), "saves-w7-schemas-" + Guid.NewGuid().ToString("N"));
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Avoid creating a new unmanaged schema temp directory per call; this helper currently leaks temp folders across test runs.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/SwfocTrainer.Tests/Saves/SavesWave7FinalTests.cs, line 431:

<comment>Avoid creating a new unmanaged schema temp directory per call; this helper currently leaks temp folders across test runs.</comment>

<file context>
@@ -0,0 +1,451 @@
+
+    private static SavePatchPackService CreatePackService()
+    {
+        var tempDir = Path.Combine(Path.GetTempPath(), "saves-w7-schemas-" + Guid.NewGuid().ToString("N"));
+        Directory.CreateDirectory(tempDir);
+        return new SavePatchPackService(new SaveOptions { SchemaRootPath = tempDir });
</file context>
Fix with Cubic

Copy link
Copy Markdown

@github-advanced-security github-advanced-security AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3ccc21be58

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

method.Should().NotBeNull();

// Pass a nonexistent zip path to trigger IOException from ZipFile.OpenRead
var result = method!.Invoke(null, new object[] { "testProfile", "/nonexistent/file.zip", "/tmp/extract" });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep extraction path under test temp directory

This test hardcodes the extract destination as "/tmp/extract", but TryExtractPackage creates the extraction directory before opening the zip. That means the test writes outside its own temp sandbox and can fail with UnauthorizedAccessException on environments that cannot write to a root-level path (especially Windows drive roots), producing a failure unrelated to the branch being tested. Using a path under tempDir would keep the test deterministic and self-cleaning.

Useful? React with 👍 / 👎.

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.

2 participants