Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ dist/
*.log
*.profraw
.DS_Store
AGENTS.md
.claude
168 changes: 168 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Agent Workflow

This repository builds a native macOS SwiftUI setup tool for creating a Sikarugir wrapper around an existing S.T.A.L.K.E.R. G.A.M.M.A. install. Treat it as a user-facing installer: small behavioral changes can affect real game installs, Wine prefixes, downloaded archives, and wrapper settings.

## Current Project State

- Active branch during the last cleanup pass: `refactor-and-cleanup`.
- The conservative refactor/cleanup slice in `refactor-and-cleanup-plan.md` is complete.
- The main implementation is Swift 5.9 targeting macOS 13.
- The package has three source areas:
- `sources/GAMMASetupTool/`: SwiftUI app, state model, app settings, and user flow.
- `sources/GAMMASetupCore/`: shared setup models, preflight checks, path helpers, wrapper setup engine, and installation logic.
- `sources/GAMMASetupEngine/`: command-line backend launched by the GUI.
- The local build script creates `dist/GAMMA Setup Tool.app` and embeds `gamma-setup-engine`.

## Standard Workflow

1. Start with repository state.
- Run `git status --short`.
- Read the relevant files before editing; do not assume previous context is current.
- If there are uncommitted changes you did not make, preserve them and work around them.

2. Scope the task.
- Identify whether the change affects UI-only behavior, setup request construction, setup engine behavior, persisted settings, wrapper detection, build packaging, or tests.
- Prefer the smallest change that satisfies the request.
- Avoid broad rewrites unless the user explicitly asks for them.

3. Follow the existing split.
- Put SwiftUI layout and controls in `ContentView*.swift` or `Components.swift`.
- Put derived UI state in `AppModel+Computed.swift`.
- Put selection and flow actions in `AppModel+Actions.swift`.
- Put setup request construction, process execution, and event handling in `AppModel+Engine.swift`.
- Put cross-target setup data and behavior in `GAMMASetupCore`.
- Put CLI argument parsing and top-level command dispatch in `GAMMASetupEngine/main.swift`.

4. Edit conservatively.
- Keep behavior-preserving cleanup separate from behavior changes when practical.
- Keep UI copy direct and user-facing; this app is a wizard, not a marketing page.
- Keep renderer strings stable: `d3dmetal`, `dxvk`, and `dxmt` are serialized into setup requests and tests.
- Keep setup stages stable unless the UI progress mapping is updated with tests.

5. Verify the changed surface.
- Run focused checks first when possible.
- Run `git diff --check` before finishing.
- Run `./test.sh` for normal code changes. It covers Swift model tests, setup engine CLI tests, and a build smoke test.
- If a task only changes docs, `git diff --check` is usually enough.

6. Report honestly.
- Mention tests run.
- Mention tests not run and why.
- Always state whether `dist/GAMMA Setup Tool.app` was rebuilt during the task.
- Call out any behavior-sensitive areas touched, especially wrapper recreation, downloaded resources, Wine registry settings, or `winetricks`.

## Build And Test Commands

- Full verification:

```text
./test.sh
```

- Build app bundle:

```text
./build.sh
```

- Build and run app bundle:

```text
./build.sh run
```

- Clean local app build output:

```text
./build.sh clean
```

- SwiftPM build:

```text
swift build
```

- SwiftPM run:

```text
swift run GAMMASetupTool
```

Prefer `./test.sh` before handing off a code change because it matches this repo's non-SwiftPM test harness and build smoke test.

## Hard Rules

- Do not run the GUI or setup engine against a real GAMMA install unless the user explicitly asks for that validation.
- Do not intentionally modify files under a user's GAMMA install, Anomaly folder, Wine prefix, Sikarugir app, Homebrew installation, or cache while testing unless the user approved the exact live-system operation.
- Use dry-run behavior, temporary directories, and fake tools for setup engine tests.
- Do not remove or rewrite user-created wrappers while debugging. Wrapper recreation is user-visible and can destroy local prefix state.
- Do not change the required `winetricks` group casually. `corefonts`, `d3dx9_43`, `d3dx11_43`, `d3dcompiler_47`, and `vcrun2026` are the expected wrapper baseline.
- Do not change renderer option semantics without checking all paths: UI controls, `SetupConfiguration`, request construction, engine application, and tests.
- Do not write to `user.ltx`. The setup tool reads game resolution for context only.
- Do not commit build output, logs, module caches, local reports, temporary archives, or generated app bundles.
- Do not commit local machine paths except in ignored local notes. Repository docs should use placeholders for user-specific paths.
- Do not hide failing tests by weakening assertions around setup behavior. If behavior changed intentionally, update tests to state the new contract.

## Behavior-Sensitive Areas

- Existing targets: normal setup must refuse to inspect, update, or overwrite an existing app. Explicit engine replacement remains a separate destructive operation.
- Display settings: forced Wine display mode writes Retina/DPI compatibility keys; default Wine mode removes only keys managed by this tool.
- Renderer setup: D3DMetal is the default; DXVK and DXMT have separate config and HUD/log options.
- Short Wine drive mapping: path mapping is based on the detected macOS install path and `ModOrganizer.ini`.
- `mo2.bat`: generated launch script sets ModOrganizer Qt variables and selected renderer environment.
- Cached downloads: engine/template resources may come from Sikarugir's app support folder or this tool's cache.
- Verbose logs: saved logs live in `~/` and should remain opt-in.

## UI Rules

- Keep the wizard flow simple and default-driven. Advanced settings should not block the normal default path.
- Keep explanatory text short enough to scan inside the app window.
- Prefer existing reusable controls in `Components.swift` and spacing constants in `Layout.swift`.
- Keep state derivation in `AppModel` extensions instead of embedding complex logic directly in SwiftUI view builders.
- Any new user-facing option needs:
- a stored/default value in `AppModel` or settings as appropriate
- request serialization in `AppModel+Engine.swift`
- backend handling in `GAMMASetupCore` or `GAMMASetupEngine`
- tests for request construction when practical

## Testing Rules

- Add or update Swift tests in `tests/swift/` for pure model/configuration behavior.
- Add or update shell tests in `tests/shell/gamma_setup_engine_tests.sh` for CLI behavior and setup engine command effects.
- Tests should use temporary directories and fake executables rather than live tools.
- Keep tests deterministic; avoid network, Homebrew, Sikarugir, or real `winetricks` calls.
- When a change touches process output or progress stages, verify both human-readable messages and machine-readable event handling.

## Repository Todo

Keep this list current. Move items to a task-specific plan when actively working on them.

### High Priority

- Add focused tests for display mode transitions: forced mode writes managed keys, default mode removes only managed keys, and `user.ltx` remains read-only.
- Keep release packaging reproducible: verify `build.sh` embeds the backend, icons, SVG resources, `usvfs`, and bundled reticle-fix archive after resource changes.

### Medium Priority

- Split very large setup-engine helper sections only when doing so reduces risk and keeps behavior unchanged.
- Improve setup-engine test fixtures for fake Sikarugir templates and cached engine archives.
- Add a small developer note for the release process if releases continue to be built outside CI.
- Review user-facing error text for setup failures and make sure each failure has a concrete recovery action.
- Keep README setup-engine details synchronized when engine behavior changes.

### Low Priority

- Consider a small manual QA checklist for the app flow with screenshots or notes for each wizard page.
- Consider adding a lightweight changelog once release cadence becomes regular.
- Consider extracting repeated SwiftUI row patterns only if duplication starts obscuring behavior.

## Definition Of Done

- The requested change is implemented or the blocker is clearly documented.
- Relevant tests pass, or skipped tests are named with a concrete reason.
- `git diff --check` passes.
- Docs and README updates are included when behavior or commands change.
- No unrelated user changes were reverted.
- The final response names the files changed and the verification performed.
- The final response explicitly states whether the app bundle was rebuilt.
67 changes: 33 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ GAMMA Setup Tool does not install G.A.M.M.A. itself. It expects G.A.M.M.A. to al

## Description

The tool auto-detects a `stalker-gamma-cli` installation and creates an `.app` wrapper that is ready to run. If you installed G.A.M.M.A. another way, you will be asked to select a folder that contains `ModOrganizer.exe`. Keep in mind that the tool does not perform any validation of the G.A.M.M.A. installation itself.
The tool asks for the folder that contains `ModOrganizer.exe`, then creates an `.app` wrapper that is ready to run. Keep in mind that the tool does not perform any validation of the G.A.M.M.A. installation itself.

There are several options to tweak, but changing them is not required. You can just go through the flow with the defaults; that was the goal.

Expand All @@ -20,11 +20,11 @@ You can re-run the setup for an already-created wrapper as many times as you wan

## What It Does

The app reads your `stalker-gamma-cli` config and `ModOrganizer.ini`, then creates a native macOS app wrapper that launches G.A.M.M.A. through `ModOrganizer.exe`.
The app uses your selected `ModOrganizer.exe` path, then creates a native macOS app wrapper that launches G.A.M.M.A. through ModOrganizer.

The guided flow handles:

- Environment checks for `stalker-gamma-cli`, Homebrew, Sikarugir, `winetricks`, Anomaly, GAMMA, and ModOrganizer files.
- ModOrganizer folder selection.
- Required winetricks dependencies.
- Wine prefix options + Optional fixes

Expand All @@ -50,24 +50,13 @@ macOS may require approving the app in System Settings because the release is no

The Display section is for choosing whether Wine should use its default display behavior or expose a specific resolution to Windows apps. This is mainly useful when using BetterDisplay or another HiDPI display mode where macOS may show a 1080p desktop while the backing pixel mode is larger.

The tool detects:
The tool detects the current macOS display mode, including HiDPI/backing resolution when available.

- the current macOS display mode, including HiDPI/backing resolution when available
- the current game resolution, read only for context, from the detected Anomaly path:
`Default Wine` is the default for new wrappers and keeps Sikarugir's normal display behavior. `Forced` writes Wine's Retina mode and DPI settings so Wine presents the selected resolution as a plain Windows display.

```text
<anomalyPath>/appdata/user.ltx
```

`anomalyPath` is resolved from `ModOrganizer.ini`.

`Forced` is the default for new wrappers. It writes Wine's Retina mode and DPI settings so Wine presents the selected resolution as a plain Windows display.

`Default Wine` keeps the same Wine display behavior the wrapper used before this setting existed. If setup is re-run after using `Forced`, choosing `Default Wine` removes the Wine display keys managed by the setup tool.

For a BetterDisplay `1080p HiDPI` desktop with the game also set to `1920 x 1080`, use `Forced` with the detected `1920 x 1080` option.
If setup is re-run after using `Forced`, choosing `Default Wine` removes the Wine display keys managed by the setup tool.

The setup tool never writes `user.ltx`; change in-game resolution from the game itself.
For a BetterDisplay `1080p HiDPI` desktop, use `Forced` with the detected `1920 x 1080` option only when you want Wine to expose that exact display mode.

For deeper technical notes on macOS scaling, Wine DPI behavior, and monitor geometry, see [DPI awareness, monitor geometry](https://github.com/elseform/gamma-setup-tool/wiki/DPI-awareness,-monitor-geometry).

Expand Down Expand Up @@ -111,11 +100,11 @@ sources/GAMMASetupTool/

The app is split into:

- `AppModel.swift`: preflight state, setup execution, process streaming, and option serialization.
- `AppModel.swift`: setup state, process streaming, and option serialization.
- `Components.swift`: reusable SwiftUI rows, tips, icons, and wizard step metadata.
- `ContentView.swift`: wizard layout and screens.
- `GAMMASetupToolApp.swift`: app entry point.
- `sources/GAMMASetupCore/`: shared setup engine models, path helpers, preflight logic, and installation services.
- `sources/GAMMASetupCore/`: shared setup engine models, path helpers, CLI preflight support, and installation services.
- `sources/GAMMASetupEngine/`: the setup backend launched by the GUI.

The Swift package builds both the GUI and the `gamma-setup-engine` backend.
Expand All @@ -124,26 +113,28 @@ The Swift package builds both the GUI and the `gamma-setup-engine` backend.

`gamma-setup-engine` performs these operations:

- Installs or verifies Homebrew tap `sikarugir-app/sikarugir`, Sikarugir Creator, and `winetricks`.
- Reads `stalker-gamma-cli` settings from `~/Library/Application Support/stalker-gamma/settings.json`.
- Finds the active GAMMA profile and its `ModOrganizer.exe`.
- Reads `<gammaPath>/ModOrganizer.ini` to preserve the expected short Wine drive mapping.
- Installs or verifies Homebrew tap `sikarugir-app/sikarugir` and Sikarugir Creator during wrapper installation, before wrapper creation.
- Uses the selected `ModOrganizer.exe`; the CLI can still read `stalker-gamma-cli` settings from `~/Library/Application Support/stalker-gamma/settings.json` as a fallback.
- Reads `<gammaPath>/ModOrganizer.ini` for context only and never modifies it. Default installation uses Wine's standard `Z:` host-path mapping.
- Creates a Sikarugir app wrapper.
- Downloads or reuses cached Sikarugir template and engine archives.
- Extracts the engine into the wrapper.
- Initializes the Sikarugir Wine prefix inside the wrapper.
- Enables D3DMetal by default, or DXMT/DXVK when selected.
- Keeps MoltenVK-CX, MSync, and ESync enabled.
- Applies mouse/raw-input compatibility overrides when selected, or restores Wine defaults when unchecked.
- Detects game resolution from `<anomalyPath>/appdata/user.ltx` for display context only.
- Uses Wine Sikarugir 10.0 and D3DMetal by default, or DXMT/DXVK when selected.
- Sets `WINEESYNC=0` and `WINEMSYNC=1` on newly created wrappers.
- When a Wine display resolution is selected, writes Wine Retina/DPI compatibility settings.
- Sets the wrapper launch path to `/mo2.bat`.
- Creates a short Wine drive mapping for the detected macOS install location.
- Installs required Wine dependencies with `winetricks`: `corefonts`, `vcrun2022`, `d3dcompiler_42`, `d3dcompiler_43`, `d3dcompiler_46`, `d3dcompiler_47`, `d3dx9`, `d3dx10`, `d3dx11_42`, and `d3dx11_43`.
- Installs any extra winetricks verbs requested by the user.
- Applies DLL overrides for DirectX and Visual C++ runtime DLLs as `native,builtin`.
- Creates `drive_c/mo2.bat`, which sets ModOrganizer Qt rendering variables before starting `ModOrganizer.exe`.
- Marks the wrapper as managed by this tool.
- Advanced installation can select another Windows `.exe` and append optional single-line launch flags to its generated batch file.
- Creates a Finder alias named `Configure <wrapper name>` beside the wrapper, targeting the wrapper's `Contents/Configure.app`.
- Advanced installation can add `G:` at the directory containing the selected GAMMA folder when existing ModOrganizer paths require it.
- Custom launch executables and working directories use that `G:` mapping when they are under its root, with Wine's standard `Z:` host path as the fallback outside it.
- Updates ModOrganizer's `usvfs` binaries from bundled files when selected.
- Installs bundled GPTK4 D3DMetal binaries by default.
- Installs required Wine dependencies with `winetricks`: `corefonts`, `d3dx9_43`, `d3dx11_43`, `d3dcompiler_47`, and `vcrun2026`.
- Reuses a compatible installed `winetricks` CLI when available and otherwise downloads one current script into the setup tool's shared cache without modifying the Homebrew installation.
- Applies DLL overrides for DirectX and Visual C++ runtime DLLs, preferring native `concrt140` with Wine's built-in implementation as fallback.
- Creates `drive_c/mo2.bat`, which sets ModOrganizer Qt rendering variables before starting `ModOrganizer.exe`; optional launch flags are appended without being split or re-quoted.
- Refuses to overwrite an existing target unless the setup engine is explicitly invoked with replacement enabled.

### Logs And Cache

Expand All @@ -167,6 +158,14 @@ Downloaded Sikarugir assets are cached in:
~/Library/Caches/stalker-gamma-sikarugir-setup
```

The shared `winetricks` script and its installer payloads are cached beside the setup tool's `settings.json`:

```text
~/Library/Application Support/gamma-setup-tool/cache/winetricks
```

New wrappers still install the required components into separate Wine prefixes, but reuse payloads from this cache instead of downloading them again.

If Sikarugir Creator has already downloaded the template or engine, the setup engine reuses those local assets from:

```text
Expand Down
8 changes: 5 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
set -euo pipefail

ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
APP_VERSION="0.69"
APP_VERSION="0.75"
BUILD_DIR="$ROOT_DIR/dist"
INTERMEDIATES_DIR="$BUILD_DIR/intermediates"
APP_DIR="$BUILD_DIR/GAMMA Setup Tool.app"
Expand Down Expand Up @@ -79,14 +79,16 @@ cp "$INTERMEDIATE_ENGINE_BINARY" "$ENGINE_BINARY"
chmod +x "$ENGINE_BINARY"

cp "$SOURCE_RESOURCES_DIR/Anomaly.icns" "$RESOURCES_DIR/GAMMASetupTool.icns"
mkdir -p "$RESOURCES_DIR/mods"
cp "$SOURCE_RESOURCES_DIR/Anomaly.icns" "$RESOURCES_DIR/Anomaly.icns"
cp "$SOURCE_RESOURCES_DIR/MO2.icns" "$RESOURCES_DIR/MO2.icns"
cp "$SOURCE_RESOURCES_DIR/mods/D3DMetal DXMT Reflex Reticle Fix v2.7z" "$RESOURCES_DIR/mods/D3DMetal DXMT Reflex Reticle Fix v2.7z"
if [[ -d "$SOURCE_RESOURCES_DIR/usvfs" ]]; then
rm -rf "$RESOURCES_DIR/usvfs"
cp -R "$SOURCE_RESOURCES_DIR/usvfs" "$RESOURCES_DIR/usvfs"
fi
if [[ -d "$SOURCE_RESOURCES_DIR/gptk4" ]]; then
rm -rf "$RESOURCES_DIR/gptk4"
cp -R "$SOURCE_RESOURCES_DIR/gptk4" "$RESOURCES_DIR/gptk4"
fi
cp "$SOURCE_RESOURCES_DIR/github.svg" "$RESOURCES_DIR/github.svg"
cp "$SOURCE_RESOURCES_DIR/discord.svg" "$RESOURCES_DIR/discord.svg"

Expand Down
Loading