Files
gitjs-privet-mir/AGENTS.md
T
2026-10-17 06:38:50 +03:00

6.8 KiB

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.

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:

find . -type l ! -exec test -e {} \; -not -path './.build/*'

Prerequisite: the generator

util/step/generateDialect calls the sibling dialect repo:

$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:

(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

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 <field> = "<field>" into struct F in dialect.swift (ASCII-alphabetical order) if you cannot regenerate first, otherwise the build fails on F.<field> being unresolved.
  • dialect.swift is generated — edits there are overwritten on the next util/build-mac. Change the source (dialect.yml / components/) instead.
  • Validate dialect.yml after edits (e.g. ruby -ryaml -e 'YAML.load_file("dialect.yml")'). Every should-branch label (1. Upon each ...) MUST end with a colon — the parser (components/yml/ymlFun.swift) blindly dropLast()s it, so a missing colon silently eats the label's last character in the generated about comment. Note the file can never pass a strict YAML parser: condition and reaction lines (under if: / then:) contain unquoted : (e.g. c.countText = "Count: '\(c.count)'"), which standard YAML rejects. So treat a parser complaint pointing inside an if:/then: block as expected; any complaint elsewhere (like a missing branch-label colon) is a real bug.

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