# 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 = ...`).