diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dc04111 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,142 @@ +# OGS Mahjong (`mj`) + +A macOS SwiftUI app for **OGS Mahjong**, built on the **Dialect v4** declarative +reactive engine (see the sibling `../dialect` repo — this project is a +downstream consumer of its code generator). + +Business logic lives in `dialect.yml` (YAML). A generator turns it into +`game/ver-mac/src/dialect.swift` (the runtime engine + `struct F` field +constants + component/should/effect registration). The Swift app shell +(`GameApp`/`AppDelegate`) and the SwiftUI views live outside the generated file +and talk to the engine through generated functions like `rootSet(...)`. + +Remote: `https://git.opengamestudio.org/mahjong/mahjong` · Version: `2.0.0` + +> Current `dialect.yml` is still the "Root / counter" template inherited from the +> dialect example — the Mahjong game logic has not been written yet. The app +> shell, packaging and symlink wiring are in place. + +## Structure + +``` +dialect.yml Single source of truth for the reactive logic. + Generates game/ver-mac/src/dialect.swift + +components/ SHARED source-of-truth for all components. + other/other.swift Shared helpers (otherPrintStderr, otherWriteFile, + otherSetupConsoleLogging, ...) + root/root.swift RootComponent (owns DialectController, registers + effects + shoulds, sets didSetup on setup()) + root/rootVM.swift RootVM (ObservableObject / @Published countText) + root/mac/view/RootView.swift Per-platform SwiftUI view + +game/ver-mac/ macOS app (SPM executable target "ogs-mahjong") + Package.swift SPM manifest (macOS 13+, swift-tools 5.9) + Info.plist Bundle id org.opengamestudio.Mahjong, exec ogs-mahjong + Resources/AppIcon.icns App icon + src/ + GameApp.swift @main SwiftUI App entry + AppDelegate.swift Creates RootComponent + RootVM, calls root.setup() + dialect.swift GENERATED — do not hand-edit (see chicken-egg below) + other/other.swift -> symlink components/other/other.swift + root/root.swift -> symlink components/root/root.swift + root/rootVM.swift -> symlink components/root/rootVM.swift + root/view/RootView.swift-> symlink components/root/mac/view/RootView.swift + +ref/swiftraylib Symlink to ../../swiftraylib — the raylib/SwiftUI + renderer sample the game will use for 3D rendering. + +util/ Build/run scripts (see Build & Run) + paths APP_MAC = .build/OGS Mahjong.app + build-mac generateDialect -> buildMac -> packageMac + run-mac runMac + rm-mac-geom Resets saved window geometry + step/ Sourced steps: generateDialect, buildMac, + packageMac, runMac + +history/gitjs/ GitJS "Glavred" history + the original JS source of the + web Mahjong, kept for reference — not part of the build. +``` + +## Symlink parity rule + +`components/` is the source of truth; `game/ver-mac/src/` contains real +directories whose files are **relative symlinks** back into `components/`: + +``` +src/other/other.swift -> ../../../../components/other/other.swift (4 up) +src/root/root.swift -> ../../../../components/root/root.swift (4 up) +src/root/rootVM.swift -> ../../../../components/root/rootVM.swift (4 up) +src/root/view/RootView.swift-> ../../../../../components/root/mac/view/RootView.swift (5 up) +``` + +SPM silently **skips** a dangling symlink (no error), so after moving +`components/` or a subdir, audit for broken links: + +```bash +find . -type l ! -exec test -e {} \; -not -path './.build/*' +``` + +## Prerequisite: the generator + +`util/step/generateDialect` calls the **sibling** dialect repo: + +```bash +$SDIR/../../dialect/util/run-generator --file=$SDIR/../dialect.yml +``` + +So `/Volumes/MOE/p/dialect` must exist and its generator must be built before +`util/build-mac` can regenerate: + +```bash +(cd ../dialect && util/build-generator) # once, or after dialect core changes +``` + +The generator resolves the `output:` path **relative to `dialect.yml`'s +directory**, which is why `dialect.yml` says `game/ver-mac/src/dialect.swift`. + +## Build & Run + +```bash +util/build-mac # generateDialect + swift build + package OGS Mahjong.app +util/run-mac # runs .build/OGS Mahjong.app/Contents/MacOS/ogs-mahjong +util/rm-mac-geom # reset the app's saved window geometry +``` + +`build-mac` sources `util/paths` then the `util/step/*` scripts in order: + +1. `generateDialect` — runs the sibling generator, writes `src/dialect.swift` +2. `buildMac` — `cd game/ver-mac && swift build` +3. `packageMac` — assembles `.build/OGS Mahjong.app` (binary `ogs-mahjong`, + `Info.plist`, `Resources/AppIcon.icns`) + +## Editing the dialect + +- Add/modify contexts and components in `dialect.yml`, then run + `util/build-mac` to regenerate `dialect.swift`. +- **Chicken-egg**: `struct F` (field-name constants) lives only in the + generated `dialect.swift`. Adding a new field to a context in `dialect.yml` + requires hand-patching its `static let = ""` into `struct F` + in `dialect.swift` (ASCII-alphabetical order) if you cannot regenerate first, + otherwise the build fails on `F.` being unresolved. +- `dialect.swift` is generated — edits there are overwritten on the next + `util/build-mac`. Change the source (`dialect.yml` / `components/`) instead. + +## Swift 6 + +The project currently builds in Swift 5 language mode +(`util/step/buildMac` has the Swift 6 flag commented out). Enabling +`-Xswiftc -swift-version -Xswiftc 6` fails today on the singleton/`@MainActor` +concurrency errors (`RootComponent.singleton`, `RootVM.shared` in +`components/root/`, and the `RootVM.shared!.countText = ...` oneliner in the +generated `dialect.swift`). Do not flip it on without addressing those. + +## Conventions + +- `game/ver-mac/.build/`, `build/`, `.DS_Store`, Xcode/Gradle artifacts are + git-ignored. +- Prefer editing files in `components/` (the real files); the `src/` entries are + symlinks. +- The generated UI callback plumbing is nonisolated; UI singletons are weak + (`RootComponent.singleton`, `RootVM.shared`) and are accessed from the + generated oneliners with force-unwrap (`RootVM.shared!.countText = ...`). diff --git a/components/root/root.swift b/components/root/root.swift index 7f17acd..97647f9 100644 --- a/components/root/root.swift +++ b/components/root/root.swift @@ -6,7 +6,6 @@ class RootComponent { Self.singleton = self rootRegisterEffects(ctrl) rootRegisterShoulds(ctrl) - otherSetupConsoleLogging(ctrl, "Root") } func setup() { diff --git a/game/ver-mac/src/AppDelegate.swift b/game/ver-mac/src/AppDelegate.swift index 73db6af..9d214e6 100644 --- a/game/ver-mac/src/AppDelegate.swift +++ b/game/ver-mac/src/AppDelegate.swift @@ -12,6 +12,7 @@ final class AppDelegate: NSApp.setActivationPolicy(.regular) NSApp.activate(ignoringOtherApps: true) + otherSetupConsoleLogging(root.ctrl, "Root") root.setup() }