Files
Arcade85/AGENTS.md
T

8.1 KiB

Arcade85

A 5-in-1 iOS collection of faithful HD remakes of five classic TI-85 calculator games. Native SwiftUI + SpriteKit, one shared shell, each game a Scene (SpriteKit) + Sim (pure Swift engine) pair.

This file is the operational runbook for an agent building/deploying Arcade85 on the Mac Studio without the human in the loop. Read it top to bottom before touching signing, git, or the build. It follows the same pattern as ~/dev/KyleCode/AGENTS.md (same Gitea, same ~/dev/.env token, same signing identity).


1. Repo layout

Arcade85/
  Arcade85.xcodeproj/        # hand-maintained (NOT xcodegen) — edit via Xcode/pbxproj
  Arcade85/
    Arcade85App.swift        # @main
    Shell/                   # app chrome: RootView, GameHostView, GameCatalog,
                             #   Theme, Haptics, HighScoreStore
    Games/
      SceneArt.swift         # shared procedural texture/sprite helpers
      DopeWars/  DopeWarsScene.swift + DopeWarsSim.swift
      Lander/    LanderScene.swift   + LanderSim.swift
      Phoenix/   PhoenixScene.swift  + PhoenixSim.swift + PhoenixData.swift
      ZTetris/   TetrisScene.swift   + TetrisSim.swift
      ZMercury/  MercuryScene.swift  + MercurySim.swift + MercuryData.swift
      Galaxian/  (ported, dormant — see GameCatalog note)
      ComingSoonScene.swift
  README.md

Convention: every game is a *Scene.swift (SpriteKit, all UI/input) paired with a *Sim.swift (pure Swift state machine — no UIKit/SpriteKit imports). The Sim is the engine and is where mechanics live; the Scene only renders and routes touches. Keep them separate. Dope Wars' DopeWarsSim is the reference for a well-factored Sim (seeded RNG, a separate extRng stream for non-classic extensions, #if DEBUG debug hooks, a messages: [String] queue the Scene drains one-per-tap).

The original TI-85 sources this was remade from live in sibling folders under ~/dev/TI/ (lander/, zetris/, phx4.0ti85/, zmercury-ti85/, galaxian-ti85/). They are not part of this repo.

2. Signing

  • Team: TG8X9PP8VT (Kyle Albanese). Identity: "Apple Development: Kyle Albanese (5SRT78F8BG)". Bundle ID: com.albanese.arcade85.
  • Automatic signing (CODE_SIGN_STYLE = Automatic), GENERATE_INFOPLIST_FILE = YES (no Info.plist file exists — don't add one).
  • The current pbxproj does not pin DEVELOPMENT_TEAM; the identity in the login keychain is used. If a device build ever fails to sign, set DEVELOPMENT_TEAM = TG8X9PP8VT on the target (don't switch to manual profiles).

Environment prerequisites (why this works)

Commands run as the user's own launchd job in gui/501 (Aqua session), so the login keychain is readable by xcodebuild/codesign. Verify before a device build (do NOT change anything if it fails — stop and tell the user):

launchctl managername        # must print: Aqua
security find-identity -v -p codesigning /Users/fung/Library/Keychains/login.keychain-db
# must list: "Apple Development: Kyle Albanese (5SRT78F8BG)"

Hard rules: never revoke/recreate the Apple Development cert, don't touch keychain settings, keep automatic signing, never commit *.p8 or *.env secrets.

3. Build

Run from /Users/fung/dev/TI/Arcade85.

Simulator (no signing — default for quick checks)

xcodebuild -project Arcade85.xcodeproj -scheme Arcade85 \
  -destination 'generic/platform=iOS Simulator' -configuration Debug \
  -derivedDataPath build/dd build CODE_SIGNING_ALLOWED=NO

Success: ** BUILD SUCCEEDED **. (Verified 2026-09-16.)

Device (real iPhone, auto-signed)

xcodebuild -project Arcade85.xcodeproj -scheme Arcade85 \
  -destination 'generic/platform=iOS' -derivedDataPath build/dd \
  -allowProvisioningUpdates build

Install/launch a paired device (list with xcrun devicectl list devices):

APP=build/dd/Build/Products/Debug-iphoneos/Arcade85.app
xcrun devicectl device install app --device <UDID> "$APP"
xcrun devicectl device process launch --device <UDID> com.albanese.arcade85

There are no automated tests in this project. A passing build is the check. For gameplay, run in the simulator and play, or use the Dope Wars debug env vars below.

Dope Wars debug env vars (DEBUG builds only)

DopeWarsScene.didMove(to:) reads ProcessInfo env vars for screenshot/demo poses: ARCADE_AUTOPLAY=1 (auto-start a run), ARCADE_DEMO=1 (deterministic day-12 demo), ARCADE_DEMO_VIEW=buy|chase|tier3|over|ride|keypad|sharkchase|endless. The DopeWarsSim also exposes debug* mutators (wallet, debt, price, tier, held, forced RNG rolls) gated behind #if DEBUG.

Dope Wars — wave-1 mechanics (the "make it dynamic" rework)

The classic uniform per-day price roll was replaced with a momentum + mean-reversion model: each day a product carries momentum (0.50) of yesterday's deviation from its per-borough anchor (boroughAnchor/methAnchor/ chinaWhiteAnchor) plus a ±noisePct (0.15) random walk, with event shocks (decaying at shockDecay 0.70) injected on top. Tuning constants live in the // MARK: Wave-1 tuning block — change them there, not inline. Other wave-1 systems, all in DopeWarsSim:

  • Price history / sparkline — spark: [[Int]] (last 7 days per product) feeds the per-row SKShapeNode sparkline on the board.
  • Market impact — marketImpact() nudges today's local price by the share of the day's volume you traded (buy pushes up, sell pushes down); buy charges the post-impact price.
  • Paid information — buyForecast(for:) (Bronx contact, 70% accurate, 1-day) + the existing free Rumor.
  • Reputation — hidden rep (0…100, starts 50) moved by changeRep; drives fenceBonus, heatMult, and shark terms.
  • Investments — Investment enum (coat +30, fence auto-sell, shark protection); buyInvestment (Bronx only).
  • Chase bribe — chaseBribe() + bribeCost (RUN/FIGHT/BRIBE in the chase sheet).
  • Shark favors — Favor (risky no-profit delivery), acceptFavor/refuseFavor/ resolveFavorDelivery; good-standing cap (sharkCap, doubled by protection) sets sharkRate (7% vs 10%). The Scene mirrors each with a sheet/mode (.contact, .investments, .favor, .bronxMenu, chase BRIBE). Keep the Scene/Sim split — mechanics in the Sim, pixels in the Scene.

4. Git & Gitea

Remote: Gitea https://git.kwalb.com/kyle/Arcade85 (branch main). No git credentials in the keychain — use the API token from ~/dev/.env inline, and let the credential prompt fail to store (harmless). The repo is created on first push:

TOKEN=$(sed -n 's/^GITEA_API=//p' ~/dev/.env)
curl -s -X POST -H "Authorization: token $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"Arcade85","private":false,"default_branch":"main"}' \
  https://git.kwalb.com/api/v1/user/repos
# then:
git push "https://kyle:$TOKEN@git.kwalb.com/kyle/Arcade85.git" main

API: curl -H "Authorization: token $TOKEN" https://git.kwalb.com/api/v1/repos/kyle/Arcade85/…

Only commit/push when asked. Stage only intended files. Never stage *.p8 or *.env.

.env — do not source it

~/dev/.env holds GITEA_URL, GITEA_API (token), GITEA_USERNAME, GITEA_EMAIL plus other service keys. Do not source it — a later line is a bare https://claude.ai/magic-link… URL (not key=value) which breaks source in zsh. Read values with sed:

TOKEN=$(sed -n 's/^GITEA_API=//p' ~/dev/.env)

5. .gitignore (already in place — don't remove)

build/, DerivedData/, *.xcuserstate, xcuserdata/, *.moved-aside, *.hmap, *.ipa, *.dSYM.zip, *.dSYM, .DS_Store, .build/, .swiftpm/.

Add *.p8 and *.env to this ignore list if/when they could appear here (none do today, but the rule is: never commit signing keys or secret files).

6. Adding a game

  1. Create Games/<Name>/<Name>Scene.swift + <Name>Sim.swift (mirror Dope Wars).
  2. Add both files to the Xcode target in Arcade85.xcodeproj/project.pbxproj (hand-maintained — there is no project.yml/xcodegen step; edit the pbxproj or use Xcode's "Add Files to Arcade85").
  3. Register the slot in Shell/GameCatalog.swift.
  4. Build (simulator) to verify.