# 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): ```bash 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) ```bash 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) ```bash 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`): ```bash APP=build/dd/Build/Products/Debug-iphoneos/Arcade85.app xcrun devicectl device install app --device "$APP" xcrun devicectl device process launch --device 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:** ```bash 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: ```bash 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//Scene.swift` + `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.