Files
Arcade85/AGENTS.md
T

192 lines
9.1 KiB
Markdown

# 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) — deploy to KyPhone
The pbxproj does **not** pin a team, so a device build fails with
`Signing for "Arcade85" requires a development team` unless you pass
`DEVELOPMENT_TEAM=TG8X9PP8VT` on the command line. Use the shared ASC API key
(same as KyleCode) for headless provisioning:
```bash
xcodebuild -project Arcade85.xcodeproj -scheme Arcade85 \
-destination 'generic/platform=iOS' -derivedDataPath build/dd \
-allowProvisioningUpdates DEVELOPMENT_TEAM=TG8X9PP8VT \
-authenticationKeyPath /Users/fung/dev/KyleCode/AuthKey_8W73TQTXT2.p8 \
-authenticationKeyID 8W73TQTXT2 \
-authenticationKeyIssuerID 69a6de70-8630-47e3-e053-5b8c7c11a4d1 \
build
```
First run downloads the `com.albanese.arcade85` development profile (takes a bit);
later runs are fast. Success: `** BUILD SUCCEEDED **`, app at
`build/dd/Build/Products/Debug-iphoneos/Arcade85.app`, signed
`Authority=Apple Development: Kyle Albanese (5SRT78F8BG)`, `TeamIdentifier=TG8X9PP8VT`.
**KyPhone** (iPhone 17 Pro) = `00008150-001C38EE3A40401C`. Install + launch + confirm:
```bash
APP=build/dd/Build/Products/Debug-iphoneos/Arcade85.app
xcrun devicectl device install app --device 00008150-001C38EE3A40401C "$APP"
xcrun devicectl device process launch --device 00008150-001C38EE3A40401C com.albanese.arcade85
xcrun devicectl device info processes --device 00008150-001C38EE3A40401C | grep -i arcade
```
(Verified 2026-09-17: installed + launched, process running.)
**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/<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.