Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
7f5f8d0
Prevent duplicate Spud detour targets
Guffawaffle Aug 29, 2026
90b99cc
Merge branch 'dev' into feature/spud-duplicate-target-enforcement
Guffawaffle Sep 2, 2026
7972ec4
Route duplicate detours to the mod log
Guffawaffle Sep 4, 2026
c973421
Document Spud diagnostic handler lifetime
Guffawaffle Sep 4, 2026
eaa6240
Add shortcut to restore Fleet Commander ability confirmations
Guffawaffle Sep 11, 2026
5b51066
Default FC confirmation recovery to Ctrl+Alt+F8
Guffawaffle Sep 11, 2026
59d19f3
Add boolean settings controller and shared non-inserting FC adapter
Guffawaffle Sep 11, 2026
cb5513b
Add native FC confirmation view with owned callbacks and lifecycle gu…
Guffawaffle Sep 11, 2026
180eb6c
Handle unlabeled settings roots and refresh views after shortcut writes
Guffawaffle Sep 11, 2026
898d9c5
Correct bundled SPUD overwrite-size documentation
Guffawaffle Sep 11, 2026
9ad2a5d
Shorten native confirmation retry labels
Guffawaffle Sep 11, 2026
250c7af
Document native confirmation label width constraint
Guffawaffle Sep 11, 2026
6bd364f
Replace FC recovery shortcut with native settings control
Guffawaffle Sep 11, 2026
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
b18b0a4
Preserve user TOML while saving the instant-warp mode asynchronously
Guffawaffle Sep 12, 2026
3a68f85
Cover Unicode key coordinates and missing TOML settings
Guffawaffle Sep 12, 2026
bbb0031
Pass idle quits through and exercise native save lifecycle
Guffawaffle Sep 12, 2026
b3bfe89
Publish force-close cancellation before deadline setup
Guffawaffle Sep 12, 2026
743873f
Combine reviewed settings and persistence foundations
Guffawaffle Sep 12, 2026
05ecfc4
Add rebuildable native mod settings navigation foundation
Guffawaffle Sep 12, 2026
2da1e50
Protect native settings views during reentrant reads
Guffawaffle Sep 12, 2026
ab1539e
Keep each settings request protected across nested writes
Guffawaffle Sep 12, 2026
a523ec0
Support runtime numeric settings with per-key save recovery
Guffawaffle Sep 13, 2026
d36cf89
Merge runtime save improvements into settings navigation
Guffawaffle Sep 13, 2026
aa35167
Make debounce fixture tolerate delayed observers
Guffawaffle Sep 13, 2026
28bb1b1
Merge runtime writer fixture correction into settings navigation
Guffawaffle Sep 13, 2026
1059781
Add complete managed method signature resolution
Guffawaffle Sep 17, 2026
c445307
Resolve runtime persistence quit hooks across client updates
Guffawaffle Sep 17, 2026
1b3e6fd
Merge branch 'feature/preserving-toml-editor' into feature/mod-settin…
Guffawaffle Sep 17, 2026
ece440f
Merge branch 'fix/fc-ability-confirmation' into feature/mod-settings-…
Guffawaffle Sep 17, 2026
f4f22c8
Require every native confirmation hook before enabling settings
Guffawaffle Sep 17, 2026
a122287
Keep mod navigation unavailable when a required hook is rejected
Guffawaffle Sep 17, 2026
378ff44
Merge branch 'dev' into fix/fc-ability-confirmation
Guffawaffle Sep 18, 2026
1b95675
Merge remote-tracking branch 'origin/dev' into feature/spud-duplicate…
Guffawaffle Sep 19, 2026
62399bc
Expose bounded detour preflight for macOS hook families
Guffawaffle Sep 19, 2026
f2f8e9a
Isolate combined SPUD validation package from prior cache
Guffawaffle Sep 19, 2026
1f6cea9
Integrate shared hook validation and enable macOS runtime persistence
Guffawaffle Sep 19, 2026
fcb361e
Merge branch 'feature/spud-duplicate-target-enforcement' into fix/fc-…
Guffawaffle Sep 19, 2026
a7e97f0
Bring native confirmation foundation onto validated macOS hooks
Guffawaffle Sep 19, 2026
a737922
Align confirmation framework with current typed setting contracts
Guffawaffle Sep 19, 2026
114c119
Align navigation with current confirmation and Mac hook foundation
Guffawaffle Sep 19, 2026
33eea8d
Align navigation with current modular settings adapter and persistence
Guffawaffle Sep 19, 2026
97fc45f
Keep navigation adapter independent of feature-specific controls
Guffawaffle Sep 19, 2026
1c128c0
Trim trailing whitespace in hook fixture build definition
Guffawaffle Sep 19, 2026
c576288
Merge branch 'feature/spud-duplicate-target-enforcement' into fix/fc-…
Guffawaffle Sep 19, 2026
14b8486
Preserve UTF-8 confirmation retry labels
Guffawaffle Sep 19, 2026
29400f9
Align confirmation foundation ancestry with current navigation adapter
Guffawaffle Sep 19, 2026
fbd9236
Align standalone confirmation adapter with current reentry protections
Guffawaffle Sep 19, 2026
97fa89e
Merge branch 'feature/spud-duplicate-target-enforcement' into feature…
Guffawaffle Sep 19, 2026
a5b791a
Document validated Mac runtime setting persistence
Guffawaffle Sep 19, 2026
1c86147
Carry current confirmation foundation and fixtures into navigation
Guffawaffle Sep 19, 2026
0c87cb4
Merge branch 'feature/preserving-toml-editor' into feature/mod-settin…
Guffawaffle Sep 19, 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
43 changes: 43 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,25 @@ jobs:
- name: Build
run: xmake build -y stfc-community-mod

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

- name: Report compiler cache
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 @@ -477,6 +492,34 @@ 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: Test loaded Mach-O hook boundaries
shell: bash
run: |
xmake build -y macos-hook-extent-tests
xmake run macos-hook-extent-tests

- name: Verify ARM64 debug core
if: ${{ matrix.arch == 'arm64' }}
shell: bash
run: |
xmake f -p macosx -a arm64 -m debug --target_minver=14.6 -y
xmake -y mods
xmake f -p macosx -a arm64 -m release --target_minver=14.6 -y

- name: Report Swift module cache
shell: bash
run: |
Expand Down
112 changes: 112 additions & 0 deletions docs/MOD_SETTINGS_FOUNDATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# 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.

Eight weak view records 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 hook bodies are preflighted for signatures, distinct addresses, exact
Windows unwind-table entries and at least 64 bytes of native extent. Hooks remain
inert until installation completes. Other platforms omit this control; support
awaits their own native extent and runtime evidence.

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.
78 changes: 78 additions & 0 deletions docs/MOD_SETTINGS_NAVIGATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# 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` instances. 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.

Current build261 metadata exposes both root and parent-taking `AddCategory`
overloads on `SettingsContext`, plus parent-taking toggle/selection builders.
The Windows bridge calls that native builder and adds boolean rows through the
existing confirmation adapter. It restores owned text overrides on category
unbind/rebind and page destruction; titles use the same scoped human-text override
as existing confirmation labels. No global localization hook is installed.
Four substantive category/page lifecycle hooks are installed only when registered
pages exist. Current x64 bodies are 366, 250, 572 and 608 bytes respectively, each
larger than SPUD's 24-byte overwrite. Other platforms omit the native UI pending
their own hook evidence. Metadata/builds alone do not validate presentation or
callback lifetime; repeated navigation/pooling remains a runtime gate.

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 current native bridge shares the `ModConfirmationSettings` patch installation
and its debug installation switch. Disabling that patch disables both native UI
surfaces. Settings retain their own identity and persistence independently of it.
The shared native adapter currently supports eight simultaneously bound mod
boolean rows across pages. Plan populated groups within that existing limit;
catalog registration does not itself guarantee native widget capacity.

For a temporary Windows debug navigation fixture, launch with
`STFC_MOD_SETTINGS_NAV_TEST=1`. It builds Mod Settings > Infrastructure Test >
Nested Group and mirrors the existing FC setting owner. It does not create a
second preference. A separate Infrastructure test toggle holds only an in-memory
fixture value, proving that multiple rows use different owners. Leave the FC
switch alone when checking labels, nesting and Back; it writes the real FC
preference if intentionally clicked. The synthetic toggle writes no file. The
environment option is absent from release builds and defaults off. Remove it and
restart to return to the empty production catalog. No data is cleared.
For the read-callback lifecycle check, additionally set
`STFC_MOD_SETTINGS_NAV_REENTRY_TEST=1`. The synthetic reader once releases its
own bookkeeping and rebinds the same native widget. A bounded PASS/FAIL log checks
that the in-flight slot is not reused. This probe does not run for real settings.
That option also adds a second synthetic toggle. Changing the Infrastructure test
toggle once invokes the second setter, which releases and rebinds the first row
while both requests are active. A separate nested-write PASS/FAIL log verifies
that the outer request's slot stays protected. Revisit afterward to check readback.

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 supports its one known mode setting. This work does
not add arbitrary TOML browsing, a second save worker, automatic config hot reload,
sliders/selection abstractions without a consumer, or speculative profiler options.

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