Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c3aa4bc
Check startup config output before replacing files
Guffawaffle Sep 12, 2026
3ba601e
Exercise startup save failures and permission retention
Guffawaffle Sep 12, 2026
a8ed7bc
Capture inherited permissions before the first save
Guffawaffle Sep 12, 2026
4d1a476
Retain missing-file creation coverage alongside ACL fixtures
Guffawaffle Sep 12, 2026
eb56fa0
Run config-save fixtures on native Windows and macOS CI
Guffawaffle Sep 12, 2026
48b48e5
Merge branch 'dev' into feature/config-save-rewrite
Guffawaffle Sep 18, 2026
fd15fb5
Add shared IL2CPP runtime boundary helpers with contract tests
Guffawaffle Sep 22, 2026
733997c
Narrow shared invocation helpers around existing loading screens
Guffawaffle Sep 22, 2026
d6c10b7
Guard existing IL2CPP class lookup when metadata is unavailable
Guffawaffle Sep 22, 2026
c09cb04
Add reusable native settings framework with Fleet Commander confirmat…
Guffawaffle Sep 27, 2026
f15d1c7
Add reusable native Mod Settings navigation groundwork (without held …
Guffawaffle Sep 27, 2026
6373d77
Preserve player-authored TOML for runtime setting saves (without held…
Guffawaffle Sep 27, 2026
fe4d792
Align native settings foundation PR with upstream dev
Guffawaffle Sep 28, 2026
32cc0e2
Align settings navigation with updated foundation
Guffawaffle Sep 28, 2026
d024f4c
Align native settings hook installation with upstream patch switches
Guffawaffle Oct 2, 2026
5dda512
Refresh checked startup config saves against current upstream
Guffawaffle Oct 2, 2026
25d78b5
Protect staged configuration contents and follow dangling links
Guffawaffle Oct 2, 2026
834f7d1
Merge branch 'feature/config-save-rewrite' into feature/preserving-to…
Guffawaffle Oct 2, 2026
d9fcec1
Give runtime persistence its own patch registry switch
Guffawaffle Oct 2, 2026
2395ba5
Reject settings path retargeting during staged edits
Guffawaffle Oct 2, 2026
2d9039b
Merge branch 'fix/fc-ability-confirmation' into feature/mod-settings-…
Guffawaffle Oct 2, 2026
c0119a0
Merge branch 'feature/preserving-toml-editor' into feature/mod-settin…
Guffawaffle Oct 2, 2026
7f9ea43
Align settings groundwork documentation with current adapters
Guffawaffle Oct 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ jobs:
- name: Build
run: xmake build -y stfc-community-mod

- name: Test confirmation setting contracts
shell: pwsh
run: ./tests/run-confirmation-settings.ps1

- name: Test x64 trampoline relocation
shell: pwsh
run: |
Expand All @@ -218,6 +222,17 @@ jobs:
shell: pwsh
run: sccache --show-stats

- name: Test startup config saves
shell: pwsh
env:
PACKAGE_DIR: ${{ steps.xmake_cache_paths.outputs.package_dir }}
run: |
$header = Get-ChildItem -LiteralPath (Join-Path $env:PACKAGE_DIR 't/toml++') -Recurse -Filter toml.h |
Where-Object { $_.Directory.Name -eq 'toml++' } | Select-Object -First 1
if (-not $header) { throw 'Built toml++ package not found.' }
./tests/run-config-save.ps1 -TomlInclude $header.Directory.Parent.FullName
./tests/run-settings.ps1

- name: Package
shell: pwsh
run: |
Expand Down Expand Up @@ -521,6 +536,20 @@ jobs:
shell: bash
run: sccache --show-stats

- name: Test startup config saves
shell: bash
env:
PACKAGE_DIR: ${{ steps.xmake_cache_paths.outputs.package_dir }}
run: |
set -euo pipefail
TOML_HEADER=$(find "$PACKAGE_DIR/t/toml++" -path '*/include/toml++/toml.h' -print -quit)
test -n "$TOML_HEADER"
bash tests/run-config-save.sh "$(dirname "$(dirname "$TOML_HEADER")")"
bash tests/run-settings.sh
- name: Test confirmation setting contracts
shell: bash
run: bash tests/run-confirmation-settings.sh

- name: Report Swift module cache
shell: bash
run: |
Expand Down
111 changes: 111 additions & 0 deletions docs/MOD_SETTINGS_FOUNDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Boolean settings foundation and native FC control

The controller and Fleet Commander preference adapter back a Windows x64 native
confirmation-page control. Mod-owned TOML
persistence and the Community Mod category are separate work.

`settings/boolean_settings.h` is independent of Unity and storage. Definitions have
a stable ID, readable label, read callback, and immediate-write callback. Registry
IDs are unique and registration freezes on first lookup. All operations belong to
the constructing thread; there is no polling, background work or disk I/O.

Consumers must keep unknown state separate from a boolean. `ReadResult` carries
availability, an optional positive value, and an adapter generation. A snapshot
also carries controller identity, revision and lifecycle epoch. Passing a stale
or foreign snapshot rejects the request. `RenderScope` suppresses user-write
handling around native binding/refresh callbacks. Reentrant application is Busy.

Writes re-read before applying, skip already-satisfied values and verify readback.
Failed or uncertain writes never trigger an automatic reverse write. The returned
snapshot contains a fresh authoritative read when available; `Unverified` must
not be rendered as successful application. `AppliedVerified` is local verification,
not a claim of cloud durability.

## Fleet Commander adapter

`FleetCommanderConfirmationSetting()` provides the native UI setting. ON means show confirmation. Reads use the existing
PersistentPrefsManager's `GetBool(key, false, false)`: the final false prevents
insertion of a missing preference while preserving the game's default. Writes use
the native FC setter. The adapter never instantiates managers, invokes abilities,
forces cloud saves, or enumerates other preferences.

Metadata is resolved lazily and checked before access. Two weak handles detect
replacement of the preference manager or saved-data object without retaining
account data. Unavailability invalidates the observed generation. One process-wide
root holds the constant, non-sensitive preference key; no work runs while idle.

The UI calls `InvalidateFleetCommanderConfirmationSession()` before the native
preference manager's RegisterEvents (initialization/reload), session-start handler,
and cloud-load entry. It immediately invalidates live view snapshots. These are
substantive functions; neither the tiny OnApplicationReload wrapper nor the
LifecycleUpdatedEventHandler save-timer path is hooked. Exact-client account
transition validation is still required; object identity alone is insufficient.

The prototype recovery shortcut has been removed. Use the native settings row;
legacy `enable_fc_ability_confirmation` entries are no longer consumed.

## Standalone controller tests

Windows (clang++ with the installed C++ toolchain):

```powershell
clang++ -std=c++23 -Wall -Wextra -Werror -I mods/src tests/boolean_settings_test.cc -o boolean_settings_test.exe
./boolean_settings_test.exe
```

On Unix, use the equivalent compiler invocation with `-pthread`. Keep executables
outside tracked source. These tests cover the pure state machine, not native ABI,
account lifecycle wiring, frame timings, or cloud persistence.

Before extending the UI, measure the baseline and candidate with the same scene,
FPS cap and diagnostics: no scheduled closed-menu work or per-frame allocations;
initial target <=1 ms added normal bind/refresh work at p95, <=2 ms per normal
operation. These are proposed UI acceptance budgets, not measured P1 results.

## Native UI adapter (P2 candidate)

The first registered control is `[MOD] Confirm Fleet Commander abilities`, under
the existing confirmation category. ON means show confirmations; OFF means skip.
The adapter is independent of mod hotkeys and does not install a global localization
hook. It overrides TextLocalizer after native binding and clears its own overrides
on release/rebind, using weak ownership records rather than matching visible text.

`BooleanView` retains the displayed snapshot. Rendering suppresses writes; stale
clicks conflict; an uncertain apply remains unresolved until a subsequent bind.
Rejected writes with known readback retain that value and show a retry message.
Unknown values suppress both native switch/state visual nodes while retaining the
label. The prefab must prove that those nodes are descendants of the row and do
not contain the label; otherwise that UI is unsupported. Exact visual validation
of this behavior remains a release gate.

Weak view records sized from the page plan bound bookkeeping. Native contexts own rows/delegates;
there are no strong roots retaining historical settings pages. Native release
clears records, with dead-record reclamation on binding as a fallback. A successful
write refreshes other live framework views. No polling or file work is scheduled.

Each callback registration owns a permanent MethodInfo copy with replaced direct,
virtual and runtime-invoker pointers. Matching native schema supplies reflection
metadata only; the donor MethodInfo remains untouched. Closed delegates must point
to that owned descriptor. The native setter delegate is deliberately inert:
only a live widget's explicit change handler can submit its displayed snapshot.
Reflection and refresh callbacks cannot authorize writes. This does not claim a
general managed-method registration API.

All seven hooks resolve managed signatures and require distinct target addresses.
Hooks remain inert until installation completes. The native control supports
Windows x64 and macOS; other platforms omit it.

Additional standalone tests:

```powershell
clang++ -std=c++23 -Wall -Wextra -Werror -I mods/src tests/boolean_view_test.cc -o boolean_view_test.exe
./boolean_view_test.exe
clang++ -std=c++23 -Wall -Wextra -Werror -Wno-unused-parameter -I mods/src -I third_party/libil2cpp tests/native_boolean_callback_test.cc -o native_boolean_callback_test.exe
./native_boolean_callback_test.exe
```

These cover view failure transitions and owned native callback invocation pointers.
They do not establish delegate construction, DynamicInvoke, Unity pooling, unknown
prefab presentation, account transitions, cloud durability or frame-time budgets
on a running game. Those require the exact candidate artifact, not the earlier
play prototype's successful tests.
61 changes: 61 additions & 0 deletions docs/MOD_SETTINGS_NAVIGATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Rebuildable mod settings pages

This foundation separates presentation placement from a setting's owner. The
intended native path is Settings > Mod Settings > group > setting. Group names
and final membership are deliberately undecided; moving a control must not rename
its stored setting or introduce another copy of its value. Confirmation controls
continue to belong on the native confirmation page.

`PageCatalog` holds stable page IDs, labels, parent IDs and references to existing
`BooleanSetting`, `ChoiceSetting`, `SliderSetting` and `ActionSetting` adapters. Parents register first; invalid parents, duplicate
pages and conflicting setting owners are rejected. The same setting can appear
on different pages, with the same authoritative read/write adapter. Registration
freezes at the first build. Definitions and setting owners outlive their views.

The catalog builds a parent-first plan once during installation; each new native
settings context receives fresh managed pages from that plan. Empty branches are omitted,
including an empty root. Building a plan neither reads nor writes settings and
retains no Unity objects. Views reuse `BooleanView` for guarded rendering, stale
request rejection and authoritative readback. A released view cannot authorize
another write. Rebuilding reads current state when each new view binds.

The native adapter must create fresh managed contexts from this plan, avoid
duplicate roots within one context, and release any temporary roots on failure.
Pooled widgets must clear owned label/state overrides before reuse. No setting
registration may install an additional copy of an existing widget detour.

The shared native adapter supports Windows x64 and macOS and creates boolean,
selection, slider and action rows through validated managed builders. It restores
owned text overrides on category unbind/rebind and page destruction, using scoped
human-text overrides without a global localization hook. Optional category/page,
heading and value hooks install only when their registered controls need them.

Historical build261 measurements covered four category/page lifecycle methods.
Current Windows client270 static measurements cover those methods and the heading,
action, selection and slider families; every selected SPUD overwrite window fits
its method extent. Those disk measurements do not establish live relocation,
callback lifetime or native presentation. Exact artifact navigation/pooling smoke
and supported Mac native extent/execution evidence remain qualification gates.

Register through `ModPages()` before settings installation. The production catalog
is empty: no final group layout, settings placement or new preference is shipped
by this infrastructure slice. This supersedes the earlier General > Community Mod
placement proposal; native confirmation placement remains unchanged.

The native adapter shares the `ModConfirmationSettings` registry entry, controlled
by default-enabled `[patches].nativesettingshooks` in all builds. Disabling it skips
native UI installation. Value records are sized from the registered page plan;
there is no fixed eight-row limit. Managed contexts own rows and delegates, while
weak records track bound views without retaining historical settings pages.

Persistence stays with explicit feature adapters. A live mod change and its
asynchronous save result are distinct; page construction never calls the TOML
writer. The current writer registers only instant-warp mode. Its installation is owned
separately by `[patches].runtimeconfighooks`. Choice, slider and action adapters are
included as navigation groundwork; a populated consumer owns its registrations,
validation and persistence. Page construction does not register arbitrary TOML keys.

Run `tests/run-settings.ps1` on Windows or `bash tests/run-settings.sh` on macOS.
The catalog fixture covers repeated builds, empty branches, registration failures,
shared setting identity, existing BooleanView readback/unbind semantics and UI-thread
ownership. The same runners retain the original boolean/view/callback fixtures.
Loading
Loading