Skip to content

v3.8.1 to v3.8.2 Experiment Development Notes

uyujkk edited this page Sep 19, 2026 · 2 revisions

v3.8.1 to v3.8.2: Cross-Mod Internal-State Experiment Development Notes

This page is an engineering record derived from the source code, tests, and experiment notes in three separate research workspaces. It documents implemented prototypes, observed behavior, technical hypotheses based on those observations, rejected approaches, and clues worth preserving.

All features described here remained experimental. They do not mean that arbitrary third-party mod state restoration is supported by a released build, and they are not part of the current v3.9.0 feature set.

1. Goal and Scope

The original request was not merely to remember which mod folders were enabled. The manager was expected to remember clothing, color, component, toggle, variant, and other internal choices for each mod, then restore those choices when the user returned to that mod or applied a multi-mod profile.

Three kinds of state must be distinguished:

  1. Deployment state: whether a mod folder exists in the XXMI/EFMI Mods directory.
  2. On-disk state: values already written into a mod-local .ini or the loader-level d3dx_user.ini.
  3. Runtime state: values currently active in the game/3DMigoto process, which may not yet be written to disk and may not be reloaded automatically after an external file edit.

The central difficulty is that the manager can read and write files reliably, but it cannot assume that “the file now contains the desired value” means “the running game has adopted the desired value.”

2. Experiment Project Structure

All three workspaces were derived from the untouched v3.8.0 codebase. Existing repository, profile, deployment, updater, and WinUI behavior was retained while each experiment was added as an isolated module.

Experiment Version identity Core implementation UI integration Tests Research note
Whole-file snapshot/hot replacement 3.8.1-hot-injection-lab IntegratedModManager.Core/CompleteFileSnapshot.cs WinUI3/MainWindow.ModStatePresets.cs Tests/CompleteFileSnapshotTests.cs 跨Mod预设测试说明.md
Precise global persist state 3.8.1-persistent-state-lab IntegratedModManager.Core/ThreeDmigotoPersistentState.cs WinUI3/MainWindow.PersistentModState.cs Tests/ThreeDmigotoPersistentStateTests.cs docs/跨Mod内部状态实验说明.md
EFMI-native resident controller 3.8.2-efmi-resident-lab IntegratedModManager.Core/EfmiResidentMod.cs WinUI3/MainWindow.EfmiResident.cs Tests/EfmiResidentModTests.cs docs/EFMI原生常驻方案实验说明.md

Complex format handling lives in IntegratedModManager.Core; WinUI event handling, prompts, path selection, and configuration persistence live in WinUI3; pure algorithms have unit tests. This separation makes file logic testable without launching the game, but a passing unit test cannot prove F10 ordering or an in-game visual result.

3. Evolution at a Glance

Route Working hypothesis Implemented result Final assessment
v3.8.1 A: snapshot all of d3dx_user.ini Avoid understanding mod variables; restore the opaque file and ask F10 to reload it Backup, hashing, atomic replacement, and F10 simulation were implemented File restoration is reliable; runtime reload direction and global blast radius are not controlled
v3.8.1 B: capture per-mod global persist values Declarations in each mod can establish ownership of variables stored in the root config Precise capture, offline merge, a temporary post bridge, and a two-F10 flow were implemented Offline restoration is well bounded; hot restoration is still constrained by 3DMigoto save/reload ordering
v3.8.2: resident loading plus activation gates Keep namespaces registered and switch which mod is allowed to act instead of restoring values Compatibility analysis, conservative INI transformation, controller generation, and isolated output were implemented The architecture avoids root-config replacement, but arbitrary third-party INI cannot be transformed safely and generically; real performance/compatibility testing remains incomplete

4. v3.8.1 Route A: Whole-File Snapshot and Hot Replacement

4.1 Engineering Rationale

The first prototype prioritized format compatibility. Since different mods use different variable names and structures, the manager treated d3dx_user.ini as opaque bytes rather than interpreting it. This avoids damaging comments, ordering, line endings, or variable forms unknown to a simple parser.

The intended data flow was:

Change mod options in game
    ↓
XXMI/EFMI writes state to d3dx_user.ini
    ↓
The manager stores the complete byte sequence
    ↓
Applying a preset backs up the current file and atomically restores the snapshot
    ↓
F10 is sent in an attempt to reload the restored file

4.2 What Was Implemented

CompleteFileSnapshot operates only on complete byte arrays:

  • Snapshot metadata includes file length and SHA-256.
  • Both length and digest are checked before a restore.
  • Data is written to a temporary sibling file first, then committed with File.Replace; unsupported or failed replacement falls back to an overwrite move.
  • Temporary files are cleaned up in all cases.

The WinUI layer added:

  • Automatic d3dx_user.ini detection from the target Mods directory, its parent/grandparent, and the launcher directory/parent.
  • Snapshot storage under presets/mod-states in the application directory.
  • Timestamped current-file backups under backups/mod-states.
  • Metadata for repository ID, preset name, original config path, snapshot filename, SHA-256, size, first-level deployed mod folders, and UTC timestamp.
  • Detection of XXMI, EFMI, 3DMigoto, and 3DMLoader-related processes before applying.
  • Best-effort rollback to the pre-restore bytes if replacement fails.
  • A three-second delay followed by a Windows keyboard event for F10 when the foreground window is not the manager; otherwise the UI asks for a manual F10.
  • Snapshot filename validation that rejects nested paths and confirms the resolved path remains under the snapshot root.

4.3 Verified Parts

Unit tests verify that:

  • A snapshot is accepted when all bytes, length, and SHA-256 match.
  • A same-length mutation is rejected.
  • A wrong length is rejected.
  • Atomic replacement preserves every byte of the replacement content.

During the 2026-08-24 review, all 28 core tests and all 21 updater tests passed. The complete WinUI restore/build stage could not finish because the current environment could not access NuGet and therefore could not restore packages such as Microsoft.Data.Sqlite and Microsoft.WindowsAppSDK. This is an environment/dependency-restoration limitation in this review, not a newly observed failure in the snapshot algorithm tests.

4.4 Critical Clues and Why the Route Was Discontinued

Real-environment observation later showed that changing a global persist value in game causes the loader to rewrite d3dx_user.ini in real time. This proves a working runtime memory → disk file direction, but it does not prove a symmetric and reliable externally edited disk file → runtime memory direction.

That distinction created several fundamental problems:

  1. F10 may save before it reloads. If the current in-memory values are written first, they can overwrite the restored snapshot before the snapshot is read.
  2. The snapshot has a global blast radius. A preset intended for one mod also restores all other persistent mod values and unrelated settings stored in the same root file.
  3. The deployed-mod list is contextual metadata only. It does not isolate the variables owned by each mod.
  4. Sending F10 does not prove application. The manager can emit a key event, but cannot prove that the intended game window received it or that the loader completed its reload.
  5. The UI vocabulary was still evolving. The prototype contained both “hot injection” behavior and advice to restore with the game/XXMI closed, reflecting unresolved runtime semantics.

This route proved that complete-file preservation can be made robust. It did not prove that a running game will reliably adopt the restored file, so it was not promoted into a release.

5. v3.8.1 Route B: Per-Mod global persist Capture

5.1 Moving from File Replacement to Variable Ownership

The whole-file route failed because of granularity and reload ordering, not because copying bytes was difficult. The second v3.8.1 route therefore handles only variables that can be attributed to a specific mod: scan every .ini in an enabled mod, find global persist $variable declarations in [Constants], then read their current fully qualified values from [Constants] in the loader-level d3dx_user.ini.

The real test environment provided the following clues:

  • Launcher: G:\XXMI Launcher\Resources\Bin\XXMI Launcher.exe
  • Mods directory: G:\XXMI Launcher\EFMI\Mods
  • Persistent user state: G:\XXMI Launcher\EFMI\d3dx_user.ini
  • Game process: Endfield.exe
  • Reload binding: reload_config = no_modifiers VK_F10
  • One inspected outfit mod could be associated with roughly 14 persistent variables, including component toggles and numeric values resembling mx/my state.

5.2 Variable Identification and Ownership Algorithm

ThreeDmigotoPersistentState deliberately uses conservative rules:

  1. Read assignments only inside [Constants] in d3dx_user.ini.
  2. Accept only $name = numeric-value assignments.
  3. Permit signs, decimals, and scientific notation, but require the value to parse as a finite invariant-culture double; strings, vectors, commands, and non-finite values are rejected.
  4. Recursively scan every .ini in the mod and recognize only global persist $name declarations inside [Constants].
  5. Use an explicit namespace = ... declared before the first section when present. Otherwise derive the default namespace from Mods\<relative INI path>.
  6. Qualify a local $name as $\<namespace>\name; preserve an already fully qualified global name.
  7. Normalize names to lowercase and reject unsafe namespaces or names containing traversal, assignment, control, or other invalid syntax.
  8. Return the intersection between values declared by the mod and values currently present in the user config.

This eliminated the largest blast-radius problem of the whole-file approach: unrelated values no longer had to be restored.

5.3 Capturing Without F10

Capture does not send F10. Every 350 ms, the manager samples file length and last-write time. After two matching samples, it reads the text and verifies those attributes again. The default timeout is five seconds.

The reasoning follows directly from the observation that in-game changes already rewrite the file: the capture side only needs to wait for a stable write, and an extra F10 could itself alter the state being captured.

Captured state is stored by:

  • repository ID;
  • mod-relative source path;
  • variable name/value list;
  • update timestamp;
  • optional PersistentStates collections inside multi-mod configuration profiles.

Only a mod currently deployed in the target Mods directory can be captured or restored, reducing the chance of mapping a repository entry to the wrong runtime folder.

5.4 Offline Restore

Offline restore has the clearest technical boundary in this branch:

  1. Derive the game process from target = ... in the sibling d3dx.ini, while also checking Endfield.exe by default.
  2. Refuse offline restoration while the game process is running.
  3. Read a stable d3dx_user.ini.
  4. Update only target assignments in [Constants]; preserve other sections, values, and comments as far as possible.
  5. Update every duplicate occurrence of a target variable so a later stale assignment cannot win.
  6. Create [Constants] if missing and insert missing target variables in name order.
  7. Preserve CRLF/LF style and terminal-newline behavior.
  8. Create a millisecond-timestamped backup under IntegratedModManager Backups.
  9. Commit through a temporary file and overwrite move.
  10. Reparse for up to two seconds and compare expected values numerically.
  11. Start XXMI only after verification; never press F10 in this flow.

The prerequisite is that the game is fully closed. Otherwise the runtime component may write stale memory values over the external edit again.

5.5 Runtime Restore and the Two-F10 Bridge

Because direct root-config replacement may be overwritten at the start of F10 processing, the runtime prototype creates a temporary mod file:

ZZZ_IntegratedModManager_StateBridge.ini

It contains a generated marker and assignments such as:

[Constants]
post $\namespace\variable = 2

The intended sequence uses 3DMigoto config execution rather than only editing the root file:

  1. Write the temporary bridge.
  2. Ask the user to return to the game and manually press F10 once so the bridge is loaded and its post assignments enter runtime state.
  3. After the user confirms in the manager, delete the bridge.
  4. Ask for a second manual F10 so the temporary override disappears while the newly assigned persistent values are expected to remain saved.
  5. Poll d3dx_user.ini for up to six seconds and verify the target numeric values.

This version deliberately does not synthesize keyboard input. It refuses to overwrite a same-named file without the generated marker and cleans up only a marked generated bridge after success or failure.

5.6 Test Coverage

Core tests cover:

  • Parsing numeric assignments only from [Constants].
  • Explicit and path-derived default namespaces.
  • Capturing only the intersection of declarations and user-config values.
  • Building post bridges while rejecting command-like or unsafe values.
  • Rejecting malformed variable names and namespaces.
  • Updating only target variables while preserving other content.
  • Creating [Constants] when absent.
  • Updating all duplicate assignments so the final assignment cannot restore an old value.

In the 2026-08-24 review, all 37 core tests passed, with 97.7% line coverage and 93.4% branch coverage. The later DataStore restore stage stopped because NuGet was inaccessible in the current environment, so the current test-all.ps1 run cannot be recorded as a complete pass.

5.7 Why It Still Did Not Become a Released Solution

The route is more precise than a whole-file snapshot, but it still cannot provide universal, automatic, reliable runtime restoration:

  • It supports only parseable scalar numeric global persist values, not strings, vectors, resource state, commands, side effects, or memory-only variables.
  • A third-party mod may keep assigning the same value in [Present], a key section, or another command list; the bridge value can be overwritten in the same or a later frame.
  • F10 save/load ordering is loader runtime behavior and cannot be proven by file unit tests.
  • Two manual F10 presses and window switching are not seamless automatic restoration.
  • Fully qualified names may change when a mod folder or deployment layout changes.
  • Correct values in d3dx_user.ini prove the disk state only, not that clothing, materials, and rendered resources are already visually synchronized.

Offline restoration remains a potentially useful controlled tool. Runtime restoration did not meet the reliability bar for a released feature.

6. v3.8.2: EFMI-Native Resident Controller

6.1 Architectural Reframing

Instead of continuing to ask “how can old values be pushed back into a running mod?”, v3.8.2 asked:

If candidate mods remain in EFMI's loaded set, can their namespaces and global persist registrations remain alive while the manager controls only which mod may respond to keys, override resources, and draw?

This became the resident-loading plus activation-gate architecture:

Multiple managed mod copies remain in the Mods set
                ↓
Each standard runtime path receives an independent enable condition
                ↓
One controller persists $active_profile
                ↓
[Present] derives an enable flag for every mod from the selected profile
                ↓
Ctrl+Right / Ctrl+Left switches whole profiles in game without F10

The manager no longer needs to copy each mod's values into the root config. If the mod stays registered, XXMI/EFMI remains responsible for persisting the mod's own original variables.

6.2 Compatibility Analyzer

Before transformation, EfmiResidentMod.Analyze recursively scans active .ini files, ignores filenames beginning with DISABLED, and classifies a mod as:

  • Supported: matches the conservatively recognized EFMI Tools structure and can be generated automatically.
  • ReviewRequired: uses a nonstandard layout or contains a runtime path that cannot be proven gated. The current implementation also refuses automatic generation for this class.
  • Unsupported: has a blocking issue, such as shipping a global d3dx.ini inside the mod or containing no active INI.

Recognition checks include:

  • CommandList\EFMIv1\RegisterMod or CommandListRegisterMod registration paths;
  • a $mod_enabled declaration;
  • an $object_detected declaration;
  • counts of global persist declarations;
  • counts of [Key...] sections;
  • runtime effects such as handling, draw commands, run, this, shader resource bindings, and vertex/index buffer assignments inside TextureOverride/ShaderOverride sections;
  • whether each effect-bearing override is already under a recognized $mod_enabled or $object_detected gate.

An effect-bearing override without a standard gate produces UNGUARDED_OVERRIDE and requires review. The policy intentionally rejects unknown layouts instead of rewriting something it cannot establish as safe.

6.3 Managed INI Transformation

For a Supported mod, the transformer makes only two structured changes:

  1. Key gating

    • An existing condition = expression in [Key...] becomes (expression) && $\IntegratedModManager\ResidentController\enable_<modId>.
    • A key section without a condition receives the enable condition at the end of the section.
  2. Standard runtime-path gating

    • An if line using $mod_enabled or $object_detected is conjoined with the resident enable flag.
    • For example:
if $mod_enabled && DRAW_TYPE == 4

becomes:

if $\IntegratedModManager\ResidentController\enable_mod_a && ($mod_enabled && DRAW_TYPE == 4)

The source repository mod is never edited. A copy is made in an isolated output directory and only that copy is transformed. A stable mod ID combines an ASCII slug from the display name with the first four bytes of a SHA-256 over the normalized source path. If a non-ASCII name produces no slug, the prefix falls back to mod.

6.4 Controller Generation

The fixed controller filename is:

000_IntegratedModManager_ResidentController.ini

Its namespace is:

IntegratedModManager\ResidentController

The generated design contains:

  • global persist $active_profile = <first profile ID>, allowing EFMI itself to remember the last selected profile.
  • A non-persistent global $enable_<modId> for every controlled mod.
  • [KeyResidentProfile] with type = cycle and smart = true.
  • Ctrl+Right for next and Ctrl+Left for previous by default.
  • A [Present] block that derives each mod's enable value from $active_profile every frame.

The manager orders the current repository's configuration profiles by update time, numbers them 1, 2, 3, and so on, collects all mods referenced by any profile, generates stable IDs, then maps each profile's mod-relative paths to the set of enabled IDs.

6.5 Output and Data Protection

Generation deliberately remains isolated and experimental:

  • The user chooses a parent directory; output is written to an IntegratedModManager Resident Set child folder.
  • A non-empty existing output folder is never overwritten.
  • Every mod is reanalyzed immediately before generation, and only Supported is accepted.
  • PathSafety.ResolveInsideDirectory constrains every resolved path to the output root.
  • The controller is written first; every mod is then copied to Resident_<modId> and only the copied INIs are transformed.
  • Any failure deletes the newly created output tree to avoid leaving a partial deployment.
  • The prototype never automatically writes to G:\XXMI Launcher\EFMI\Mods, never modifies repository source files, and never reads or writes d3dx_user.ini.

6.6 Test Results

Tests cover:

  • Recognition of a standard EFMI Tools layout as Supported.
  • Classification of an unguarded override as ReviewRequired.
  • Rejection of a mod that ships d3dx.ini.
  • Ignoring DISABLED*.ini.
  • Adding activation gates to key sections with and without existing conditions.
  • Combining activation gates with $mod_enabled and $object_detected runtime paths.
  • Generation of the persistent profile selector and per-profile enable flags.
  • Rejection of a profile referencing an unknown mod ID.
  • Deterministic, ASCII-safe stable mod IDs.
  • Copy-and-transform deployment without source-file modification.
  • Refusal to overwrite a non-empty output folder while preserving its content.

In the 2026-08-24 review, all 40 core tests passed, but the project coverage gate failed: line coverage was 81.3% against a 90% minimum, and branch coverage was 75.8% against an 85% minimum. The prototype has meaningful behavioral tests, but exceptional and boundary paths remain under-tested.

6.7 Incomplete Work and Why It Cannot Be Generalized Yet

  • Only a standard EFMI Tools pattern can be recognized conservatively; handwritten, older, or deeply nested INI layouts cannot be transformed generically.
  • Static text analysis cannot prove that every draw, resource binding, command list, and side effect is controlled by the enable flag.
  • Keeping multiple mods resident may add parsing, resource, memory, or VRAM cost; no real-game benchmark was completed.
  • Same-character mods may share hashes, resources, or registration behavior. An enable flag of zero still requires real-environment validation that no conflict remains.
  • The controller currently uses numeric profile IDs and provides no in-game visual profile names or UI.
  • Regression testing across EFMI versions, reload behavior, crash recovery, and long-running profile switching was not completed.
  • The prototype intentionally avoids automatic deployment. A release would still need a backup, rollback, upgrade, and uninstall lifecycle.

The architecture is a stronger long-term direction, but not a finished converter safe for every mod.

7. Shared Conclusions from All Three Routes

7.1 Confirmed Facts

  • In-game changes to global persist can cause d3dx_user.ini to update.
  • The manager can safely back up, hash, and atomically replace configuration files.
  • Mod namespaces and global persist declarations can attribute a subset of root-config values to a particular mod.
  • When the game is fully closed, a precise variable merge has a smaller risk surface than a whole-file replacement.
  • A temporary post bridge can express the intent to assign runtime values, but file tests alone cannot prove its execution order.
  • Resident gating avoids namespace disappearance caused by repeatedly deleting and copying mod folders, but requires complete and reliable transformation of third-party INI behavior.

7.2 Unconfirmed Facts

  • Whether every relevant XXMI/EFMI version always saves and reloads in the same F10 order.
  • Whether a bridge assignment is overwritten by [Present] or another command list in each target mod.
  • Whether a verified disk value guarantees immediate visual synchronization of all resources.
  • Whether multiple same-character resident mods are free of hash, resource, performance, and registration conflicts in the real game.
  • Whether a transformation rule can be broad enough for arbitrary third-party INI files without also making unsafe edits.

7.3 Why Process-Memory Injection Was Not Used

None of the prototypes reads or writes game-process memory. They do not inject DLLs, install hooks, bypass anti-cheat, or capture background keyboard input. Beyond account and security risk, such an approach is fragile across game updates, makes state ownership difficult to validate, creates a large maintenance burden, and would turn a file-oriented mod manager into a fundamentally different runtime tool.

8. Clues for Future Research

The following directions remain useful, but should stay experimental and be tested in an isolated environment:

  1. Prefer offline restoration. Productize only the precise merge from v3.8.1 B as an explicit “game fully closed” operation; compare the root config before launch and after exit without promising runtime switching.
  2. Require a mod-provided state manifest. Let authors declare persistable variables, ranges, application order, and F10 requirements instead of asking the manager to infer arbitrary scripts.
  3. Build versioned adapters. Create a real syntax/AST model for known EFMI Tools generator versions rather than using generic regular expressions against all third-party INI files.
  4. Add read-only diagnostics first. Report variable ownership, duplicate assignments, [Present] write sites, unguarded overrides, and potentially conflicting hashes without changing files.
  5. Benchmark resident sets. Measure launch time, F10 reload time, RAM/VRAM, frame time, and stability with 2/4/8 same-character mods resident.
  6. Create a reversible deployment manifest. Record each generated copy, source hash, transformation-rule version, and controller version for verification, upgrade, and complete uninstall.
  7. Keep directory persistence separate from root-variable restoration. A directory junction preserves files the loader writes inside a mod folder; it does not automatically solve EFMI-root d3dx_user.ini or memory-only state.

9. Current Released Feature Boundary

The later released Windows directory-junction deployment solves the “deep copy” problem between repository mods and the loader's Mods directory: both paths refer to the same directory, so files written inside that linked mod folder can persist naturally.

It is not any of the experiments described on this page:

  • It does not restore EFMI-root d3dx_user.ini.
  • It does not parse or overwrite arbitrary mod $variables.
  • It does not generate a post bridge.
  • It does not press F10 automatically.
  • It does not keep multiple mods resident and rewrite their INIs.
  • It does not access game-process memory.

This distinction matters. Directory junctions are a released file-deployment mechanism; v3.8.1–v3.8.2 were internal-state experiments that did not reach universal reliability.

10. Archive Verification Record (2026-09-19)

Experiment Current review result What this does not prove
v3.8.1 Hot Injection Core 28/28 and Updater 21/21, 49/49 total; complete WinUI build with zero warnings and zero errors Reliable F10 hot replacement in the real game
v3.8.1 Persistent State Core 37/37, DataStore 7/7, and Updater 26/26, 70/70 total; coverage gates passed Reliable two-F10 bridges for every mod
v3.8.2 EFMI Resident Core 40/40, DataStore 7/7, and Updater 26/26, 73/73 total; WinUI build passed; core coverage 81.3%/75.8%, below the 90%/85% gates Safe resident conversion across real EFMI versions, arbitrary third-party mods, and long play sessions

Passing unit tests establish only that pure parsing, merging, transformation, and path-protection behavior matches the test cases. Real-game validation, performance validation, and cross-version compatibility remain separate work.

11. Final Assessment

The v3.8.1–v3.8.2 work was not simply a sequence of failed attempts. Each route narrowed the problem:

  1. Whole-file snapshots proved that the file layer can be preserved safely, but the granularity is too broad and runtime reload direction is uncontrolled.
  2. The global persist branch proved that a subset of values can be attributed and merged safely while offline, but runtime ordering and mod side effects remain outside the manager's control.
  3. The resident EFMI branch removed the need to restore the root config, but moved the hard problem into complete third-party INI gating, compatibility, and performance validation.

The most accurate current conclusion is: universal, automatic, and reliable runtime persistence of internal state across arbitrary third-party XXMI/EFMI mods cannot currently be promised as a released feature. The verified code and negative results remain valuable starting points for offline precise restoration, author-declared state interfaces, or versioned EFMI transformers.

12. Related Pages

开始 / Start

使用手册 / Guides

发布记录 / Release Notes

历史页面与实验 / Historical Pages and Experiments

项目 / Project

Clone this wiki locally