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, setDEVELOPMENT_TEAM = TG8X9PP8VTon 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-rowSKShapeNodesparkline 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);buycharges the post-impact price. - Paid information —
buyForecast(for:)(Bronx contact, 70% accurate, 1-day) + the existing freeRumor. - Reputation — hidden
rep(0…100, starts 50) moved bychangeRep; drivesfenceBonus,heatMult, and shark terms. - Investments —
Investmentenum (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) setssharkRate(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
- Create
Games/<Name>/<Name>Scene.swift+<Name>Sim.swift(mirror Dope Wars). - Add both files to the Xcode target in
Arcade85.xcodeproj/project.pbxproj(hand-maintained — there is noproject.yml/xcodegen step; edit the pbxproj or use Xcode's "Add Files to Arcade85"). - Register the slot in
Shell/GameCatalog.swift. - Build (simulator) to verify.