# Arcade 85 A 5-in-1 iOS collection of faithful **HD remakes** of five classic TI-85 calculator games. Native **SwiftUI + SpriteKit**. The games (from the original sources in this repo's sibling folders): | Game | Original | Status | |------|----------|--------| | **Lunar Lander** | Tiger/Istar, 1997 (`lander/LANDER.C`) | ✅ Playable | | **ZTetris** | J. Mårdell, 1998 (`zetris/ZTETRIS.ASM`) | ✅ Playable | | **Phoenix** | P. Davidson, 2008 (`phx4.0ti85/`) | ✅ Playable | | **Dope Wars** | J.E. Dell, 1984 · engine via J. Gannon's NerdWars, 2000 | ✅ Playable | | **ZMercury** | P. Davidson, 2004 (`zmercury-ti85/`) | ✅ Playable | | **Galaxian** | P. Davidson, 1998 (`galaxian-ti85/`) | 💤 Ported, dormant | All five collection slots are playable. Galaxian was fully ported, then swapped out of the line-up for a trading game (too much overlap with Phoenix); its code stays in the project and swapping one `GameCatalog.swift` entry restores it. The trading slot first shipped as NerdWars (the TI-85's Drugwars clone), then was restored to the classic **Dope Wars** setup it secretly was all along — see the Dope Wars section. ## Running it Requires Xcode 16+ (developed against Xcode 26). ```sh open Arcade85/Arcade85.xcodeproj # pick an iPhone simulator, then Cmd-R ``` Or from the command line: ```sh cd Arcade85 xcodebuild -scheme Arcade85 -sdk iphonesimulator \ -destination 'platform=iOS Simulator,name=iPhone 17' build ``` There is no third-party dependency and no code-signing needed for the simulator. The project uses Xcode "synchronized file groups", so **new files added under `Arcade85/Arcade85/` are picked up automatically** — no `.pbxproj` editing. ## Project layout ``` Arcade85/Arcade85/ Arcade85App.swift @main entry point Shell/ the collection UI, shared across every game Theme.swift palette + fonts (SwiftUI + SpriteKit mirrors) Haptics.swift feedback helpers HighScoreStore.swift per-game best scores (UserDefaults) GameCatalog.swift game metadata + SKScene factory RootView.swift home screen (starfield + game cards) GameHostView.swift full-screen SpriteView host + back button Games/ SceneArt.swift procedural gradients, starfields, symbol + sprite textures ComingSoonScene.swift placeholder scene for the four unbuilt games Lander/ LanderSim.swift faithful port of LANDER.C (pure logic) LanderScene.swift HD rendering, multi-touch thrusters, game flow ZTetris/ TetrisSim.swift faithful port of ZTETRIS.ASM (pure logic) TetrisScene.swift HD rendering, DAS touch controls, game flow Phoenix/ PhoenixData.swift sprites (images.asm) + campaign (levels.asm), verbatim PhoenixSim.swift faithful port of the Z80 game logic PhoenixScene.swift HD rendering, drag-to-fly control, shop, flow Galaxian/ (dormant — swapped out of the catalog for Dope Wars) GalaxianData.swift sprites, formation patterns + 20-level table, verbatim GalaxianSim.swift faithful port of GALAXIAN.ASM GalaxianScene.swift HD rendering, drag-to-fly + laser button, flow DopeWars/ DopeWarsSim.swift the NerdWars engine restored to Dell's classic setup DopeWarsScene.swift night-city trading board, event cards, cop chase, flow ZMercury/ MercuryData.swift terrain tiles, map, sine table + sprites, verbatim MercurySim.swift faithful port of the multi-file Z80 sources MercuryScene.swift HD terrain scroller, parallax, drag-to-fly, flow Assets.xcassets/ app icon (procedurally generated) + accent color ``` ## Design approach Each game is split into a **pure simulation** and a **SpriteKit scene** that renders and drives it. The simulation reproduces the original's mechanics exactly; the scene is where the HD glass goes (neon vector art, particles, haptics, touch controls). This keeps the remakes honest — the feel comes from the original numbers, not an approximation. ### Lunar Lander (the reference implementation) `LanderSim` ports `LANDER.C` line-for-line in the original integer space: - the Dape/Istar landscape generator (random walk + erosion smoothing passes), - the 10-wide landing pad carved 4 below the surrounding terrain, - gravity, the ±2 thruster impulses, horizontal friction, velocity clamping, - the 8-pixel-footprint landing/crash test (must be flat under all 8 columns with descent speed ≥ −3), and - the original scoring: `((90 − maxFuel) × fuel / maxFuel) × maxVel`. The sim ticks at the original's real pace — LANDER.C ends every loop pass with `delay(20)` ≈ 200 ms (the same `delay()` whose 200-unit calls are the ~2 s message pauses), i.e. **~5 loops/sec** — with rendering interpolated between ticks so motion stays smooth. Taps shorter than one tick are latched so they still burn for exactly one loop, like one keypress in the original. Deliberate HD liberties (everything else is the original's integer rules, which hold verbatim on the Moon setting): - The field is **128×96** instead of 128×64 — extra sky above the same 0…32 terrain for a phone-shaped playfield and a longer descent. Every rule is height-relative, so nothing else changes. - **Soft walls.** In the original the screen edge was fatal; on touch that turned a mistimed start tap into an instant "lost in space". Now the top and sides stop the lander (velocity zeroed) instead of ending the run, the lander spawns with 7 px of ceiling headroom, and a tap that starts a run is consumed by the transition — it can never double as a first-tick burn. Crashing into terrain is the only way to die. - A **GRAVITY** setting — the remake's third difficulty knob, added because the original's self-braking lateral friction leaves little to fight: - **MOON** — the original: pull 1/tick, drift damped every tick; - **MARS** — pull 1,1,1,2 (avg 1.25), damping every *other* tick; - **EARTH** — pull 1,2 alternating (avg 1.5), **no damping** — Newtonian drift you must counter-burn, landing scores **×3** (Mars ×2). The patterns are phase tables over the tick counter, so the sim stays integer-exact. The scene renders the actual TI-85 8×8 lander bitmap as crisp merged neon pixels and adds on-screen thruster pads (with true multi-touch so you can fire lateral + main thrust together), a live V-SPEED / FUEL / SCORE HUD, and ready / landed / game-over flow. The original's adjustable **FUEL (30–80)** lives on the ready screen next to **MAX SPEED (4–7)** — the original allowed speed 3, but there terminal velocity *equals* the safe-touchdown limit and the game cannot be lost, so the floor is 4. The classic's own defaults (40/7) are its hardest speed setting, so the remake defaults to a gentler **60 fuel / speed 4 / Moon**, remembers the player's last choice, and teaches the rule on screen (`V-SPEED · SAFE ≤3` plus stepper hints). The score formula already pays more for harder settings; the gravity multiplier extends that to the new worlds. A 20-assertion standalone harness pins the spawn headroom, wall clamps, all three pull/damping patterns, the score multipliers, and that speed 4 remains losable. ### ZTetris `TetrisSim` ports `ZTETRIS.ASM` mechanically — several of its tables are embedded **verbatim**: - the 28-word `BitData` piece table (7 pieces × 4 rotations, four (dy,dx) offsets packed 2 bits each), decoded exactly like the ASM's `Uncrunch`; - the well geometry: 10 columns, 16 visible rows + 2 rows of hidden headroom, spawn at the original (6,16), no wall kicks, moves fail silently; - `levelcnts` gravity (level 0–20), converted at the TI-85's real interrupt timing: one main-loop iteration = 4 HALTs ≈ 20 ms, so level 0 = 0.8 s/row; - scoring: line clears `4/10/30/120 × (level+1)`, plus the hidden units counter (`scoreU`) — +1 per Down/Drop step *including the blocked one* — carried into the displayed score ÷10 on each new piece, exactly as `NewB` does it; - level = lines ÷ 10 (only increasing, capped at 20), start level 0–9; - "start high" trash (0–5 → ×2 rows), each row punched with 5 random gaps; - the 16 per-level background patterns, tiled on the rails beside the well. The scene adds the HD glass: colored beveled blocks, a ghost landing outline, next-piece preview, DAS auto-repeat on held ◄ ► (0.167 s delay, 50 ms repeat), held soft drop, tap hard drop, both rotation directions, pause, and line-clear flash/score-pop feedback. The sim has a standalone test harness (68 assertions) covering piece decode, drop scoring, clears, leveling, trash rows and top-out. ### Phoenix The deepest port: the entire Phoenix 4.0 campaign and economy, extracted from the Z80 sources file by file: - **Campaign** (`levels.asm`): all 31 combat waves + 9 shop stops, with every power/movement/weapon/fire-rate value and the four pattern paths (circle, oval, figure-eight, huge oval) transcribed literally; `L_GOTO` chains inlined. - **Enemy AI** (`emove.asm`): the synchronized rectangle formation wobble (byte-timer, odd-tick moves, 17-tick phases, −60 entry descent), bosses (descend then 83/90-px sweeps), bouncers, pattern followers driven by the original's bit-reversed-timer fractional velocities (`Div_A_16`), rampagers with the exact trigger columns, and the 4-path swoop system. - **Weapons**: player F1–F5 with the original damage/speed/offsets (including F4's alternating sides and F5's arcing swing shots), enemy weapons from straight drops to the 7-angle-bucket aimed big/huge shots. - **Economy**: money as dmg-0 "bonus bullets" dropped every 4–11 kills, $ value by difficulty (100/50/25), cash cap 9900, and the shop's 7 items with the original prices and purchase rules (shield cap 16, one-time weapons that auto-select, companion refill). - **Companion** (`helper.asm`): position-queue follower, 10-damage shots every 8 frames, its own 16-point shield and explosion. - **Scoring** (`score16.asm`): shields×1000 + time bonus (19000 counting down every other frame) + difficulty/speed bonus + cash, awarded on finishing. - **Sprites** (`images.asm`): all 44 bitmaps rendered as neon pixel art, including the 16-wide bosses and the 8-frame explosion. Touch layer: drag anywhere to fly (original ±2/±1-px-per-frame speed limits still govern the ship), always-on autofire at the original 10/4-frame cadence, tap weapon selector, pause, and full title → shop → victory/game-over flow at the original 25/29/35 fps speed options. Sim verified by a 172-assertion standalone harness (progress additions covered by a follow-up suite). **Progress scoring (remake addition).** The original only scored a *finished* campaign (`score16.asm`), which on the phone meant the high score sat at 0 until you beat all 31 waves. The sim now tracks `wavesCleared` and `totalEarned` (lifetime $, unspent or not), and every run posts `waves × 400 + earnings` — a death shows `WAVE n/31 · SCORE`, and a victory adds the untouched score16 tally (shields/time/bonus/cash) on top, itemised as a PROGRESS line in the classic victory breakdown. The 2:1 TI-85 window is drawn **full-bleed** with a **2× vertical stretch on entity positions** while the sprites themselves keep a mild 1.25× arcade-monitor stretch — the battle fills the phone instead of sitting in a letterboxed strip, and the pixel art stays crisp. Sim geometry and collisions are untouched — the visible window itself can't widen, because the original parks and spawns entities just outside the y 32…96 band. ### Galaxian `GalaxianSim` ports `GALAXIAN.ASM` (v3.0) at its ~35 fps frame rate: - **Formation choreography**: each slot's pattern byte is *both* entry time and formation depth — an enemy triggers when `timer & 127 == 128 - depth`, descends 1 px/frame, and the 128-frame timer wrap locks it into formation exactly at its row. The whole formation then sways ±32 px on timer bit 6. - **Swoops**: the original's 8-stage dive state machine (types 5–12: up/out, arc over, dive at double speed), started by the R-register roll (`R < 19/128` on every 4th frame), aimed away from the screen edge, and rejoining the entry queue at the bottom for −1 point. - **20 levels** from `levels_jump_table`: plain waves, the ring formation, mixed weak/strong waves (13 power-1 + 7 of the level type), bouncing cross trainers (exactly half of which can flee, −50), aim-shooting "operator" ships, patrolling 10-hit bosses, and two 98-hit superbosses that deploy drones mid-fight (−200 each). - **Weapons**: one cannon bullet per press from a 10-slot pool (+10 per hit, +100 per kill), and 9 lasers that wipe an entire column but never hurt a boss. Enemy bullets come in 5 drift types picked by your position. - **Scoring** exactly as documented: +200 per level, +25 per boss hit, +1000 per boss kill, −16×16 per death, +5000 victory. The scene adds drag-to-fly (4-way, original 1 px/frame speed), always-on autofire, a big LASER button with the remaining count, wave banners, and the 9-lives / 9-lasers HUD. Sim verified by a 129-assertion standalone harness. ### ZMercury `MercurySim` ports the multi-file 2004 sources — a fully deterministic horizontal cave-shooter (the original contains *no* randomness at all): - **Terrain scroller**: 1 px/frame leftward; every 8 frames a new 8-px tile column enters from a 64-column looping map (9th map row = wave triggers). The buffer's polarity quirk (bits set = open sky) and the one-byte offset between sprite space and terrain space are both preserved. - **Waves**: straight flyers, aimed-fire phoenixes, sine-bobbing spinners, turret bases mounted on their map pad tiles (ceiling *and* floor), spin-T sine crossers, an open-space gate that halts the map until the field is clear, and a 50-hit boss that holds the right edge firing bullet pairs. - **Damage model**: 100 shield points; every ship corner touching rock costs 1 per frame, enemy bullets cost 5, rams cost 1 (and hurt the enemy back). Kills pay +1 immediately and +20 when the explosion finishes — the original's BCD scoring, faithfully odd. - **Aimed fire**: the shallow-cone rule — bullets angle up/down only when the vertical offset beats deltaX/4 — plus per-slot fire stagger. The scene renders the terrain from prebuilt map-column textures that slide with the sim's scroll counter — full-bleed, positions/terrain stretched 1.9× vertically (sprites 1.35×), every rock pixel facing open air lit as a glowing rim over a dark rock mass — plus drifting star layers, a slower stalactite silhouette band for cave depth, and the original ~28.6 fps (`timer ≥ 7` against the ~200 Hz interrupt). Sim verified by an 81-assertion standalone harness. ### Dope Wars The genre swap for the fifth slot — and a restoration story. The engine is the verbatim port of **NerdWars** (J. Gannon, 2000), the TI-85's only Drugwars-style game, decoded from its `.85p`. Reading the BASIC revealed that Gannon had kept John E. Dell's original **Drug Wars (1984)** item variables `C,H,A,W,S,L` — Cocaine, Heroin, Acid, Weed, Speed, Ludes — and merely rescaled the price bands and renamed the nouns. `DopeWarsSim` reverses the re-skin: Dell's classic setup on Gannon's proven engine. - **The month**: $2,000 cash, the classic **$5,500** debt to the loan shark growing 10%/day, a 6%/day bank (shark and bank live in the Bronx), a 100-pocket trench coat, and 30 subway rides between six boroughs (Bronx, Ghetto, Central Park, Manhattan, Coney Island, Brooklyn); each ride rerolls the six prices plus one 0…20 event roll. - **Market** (Dell's iconic bands): Cocaine 15000–30000 · Heroin 5000–14000 · Acid 1000–4500 · Weed 300–900 · Speed 90–250 · Ludes 10–60, bent by events: cheap heroin at 850–1999, "Cops made a big bust!" heroin at 18000–42999, $80,000+ cocaine manias, $2 pharmacy-raid ludes, weed and acid floods, a subway mugging (keep 2/3), $200-coat and $400-gun offers (Baretta / .38 Special / Saturday Night Special), free drugs on a dead dude — and the weed on the sidewalk that always ends in one last wild trip. - **Officer Hardass**: carrying 50+ units can trigger a chase — run (50/50; escaping rerolls the day, possibly straight into another event) or fight with a gun; every fight round draws return fire (+3 wounds, 50 = death), and dropping the whole squad loots Hardass's body ($750–1999) with a $1,000 doctor offer if you can afford it. - **Scoring**: your **final net worth in dollars** (cash + bank − debt) is the high score — "$13.4M" beats "$2.1M", the way a trading game should brag. (Dell's `√(net ÷ 31.5)` 0–100 rating is still computed in the sim for the curious; the old 0–100 stored best migrates to $0 once.) - **The market grows with you (remake extension).** Below $100k net worth the game is exactly the classic. Cross **$100k** and the ludes slot retires — any stock is bought out at yesterday's price — and **METH** moves in at $2,000–16,000, the widest swing on the board. Cross **$1M** and speed retires for **CHINA WHITE** at $40,000–160,000, the whale product that dwarfs even a cocaine mania. Shifts are one-way, announced as street-word cards on arrival, and bring their own events on formerly idle rolls of the 0…20 table: a Jersey meth-lab explosion spikes local product to $30–48k (the tier-2 face of the old $2-ludes roll), a superlab dump floors meth at $400–999, and the cartel flooding the docks drops China White to $8–20k. The dead-dude loot names the tier's product — yes, you can find china white on a dead dude in the subway. - **The streets got real (round-5 extensions).** Each borough now prices its own way — multipliers on the daily band rolls (Bronx neutral; event prices are absolute news and ignore the bias): heroin and speed run cheap in the Ghetto, acid and weed in the Park, everything's sleepy-cheap on Coney, Manhattan's rich buyers pay up to +30%, Brooklyn's fences pay a little over — so routing (buy in the Park, sell in Manhattan) is a real strategy, and the subway sheet captions each borough's economy. Some days **word on the street** names a product and a borough for tomorrow — ride there to find out if the tip was good (×3 spike or ÷3 glut) or street garbage (about 1 in 4). And the debt finally has teeth: run a **$15,000+ tab past day 10** and you get one warning before the shark's two leg-breakers start jumping you — the classic chase engine with his collection money as loot, amber sodium strobe, and a nastier ending ("THE SHARK COLLECTED") if they put you down. Rumors and shark muscle roll on a separate RNG stream, so classic price/event sequences per seed stay byte-identical. - **Month lengths**: 30 / 60 / 90 days or **endless** on the ready screen — endless has no clock and posts no score (interest caps keep marathon runs from overflowing). Amount sheets have a MAX chip, and tapping the number opens an in-game keypad to type any amount directly (DONE trims to the limit). - **Engine quirks kept**: the dead-dude loot roll can never grant cocaine (`int(rand*5)` is 0…4 over the cheap-to-dear order); the shark fronts at most $5,000 *per visit* but unlimited visits; the final ride ends the month with no fresh roll. One NerdWars quirk was *dropped* with the re-theme: trading matched items **by price** (`If F==C …`), which could fill/empty several pockets at once — no longer authentic to the classic fiction, so trading is by item, the way Dell's original worked (the harness pins the new behaviour with a regression test). The scene wraps it in a night-city board — skyline silhouettes with lit windows, sodium-lamp glow, gradient market rows with day-over-day ▲▼ movement arrows and reroll flashes (arrows sit out a tier-shift day, since yesterday's prices belong to retired products) — plus quantity-chip trade sheets, SUBWAY / SHARK / BANK / END actions, the original's Pause screens as tap-through event cards, and a RUN / FIGHT chase overlay under red/blue strobe light. The ready screen states the rules as a plain bulleted list and carries the 30/60/90/∞ month picker. Sim verified by a 133-assertion standalone harness covering the classic engine end-to-end plus every extension: tier mechanics (bands, liquidation, one-way + double promotion, tier events and their tier-1 no-ops, tier-aware loot naming), borough bias math and event-absolute pricing, rumor spawn/payout/garbage/missed/final-day gating, the shark warning → chase → loot/death paths, and month-length / endless / interest-cap behaviour. ## The HD art pass A collection-wide visual up-level (round 3 of feedback: "still a little too simple… as if they are straight ports"). Shared toolkit in `SceneArt.swift`: radial glow textures, gradient panel textures, programmatic particle emitters (spark bursts, thrust/trail flames, ambient dust), vignettes, screen-shake and flash actions. Per game, all render-layer only — every sim stays verbatim: - **Lander**: full-bleed field; terrain rebuilt as gradient rock with speckle grain and a lit crust hugging the skyline, over a distant ridge silhouette; particle exhaust jets + engine glow instead of triangle flames; pulsing pad light-cone and sequenced approach beacons; touchdown dust ring; crash flash + spark burst + tumbling debris + screen shake; nebula blooms, two-depth starfield, drifting dust, vignette. - **Phoenix**: full-bleed field (2× position / 1.25× sprite stretch); sprites shaded top-lit so they read as bodies, not flat clusters; engine trail and banking tilt on the ship; particle explosions; screen shake + red flash on hits; WAVE / BOSS banners; two scrolling star layers over static far stars. - **ZMercury**: full-bleed canyon (1.9× / 1.35×); terrain columns re-rendered with glowing rim pixels on every air-facing edge over dark rock; a slow stalactite/stalagmite silhouette band behind the real walls; engine trail, scrape sparks + shake, kill sparks synced to the original explosion frames, and a proper going-out blast. - **Dope Wars**: built HD from the start (see its section). - **ZTetris**: untouched — it set the bar. ## Porting notes Tip from the ZTetris port: the 1990s sources use extended-ASCII + CRLF, which makes `grep` treat them as binary — strip with `tr -d '\r' < FILE | tr -c '[:print:]\n\t' '?'` first, then the ASM tables (pieces, speeds, scoring) can be read and ported directly. NerdWars (the Dope Wars engine) arrived as a `.85p` Graph Link file rather than source — but that format's ASCII mode stores TI-85 BASIC nearly as plain text, so a ~20-line decoder mapping the few special bytes (0xD6 newline, 0x1C the STO→ arrow, 0x19 ≥, 0x1A negate, 0x10 √) recovers the entire listing. ## Notes - Portrait-only, status bar hidden, targets iOS 17+. - The app icon is generated by `scripts`-free CoreGraphics (see the git history / the 1024px asset in `Assets.xcassets`). - `#if DEBUG` launch hooks exist for screenshots/tests and are stripped from Release builds: - `ARCADE_DIRECT_GAME=` — open straight into a game. - `ARCADE_AUTOPLAY=1` — start the run immediately (any game). - `ARCADE_DEMO=1` — freeze a staged mid-game pose (any game). - `ARCADE_DEMO_VIEW=buy|chase|tier3|over|ride|keypad|sharkchase|endless` — with Dope Wars' demo, pose the buy sheet, cop chase, tier-3 board (Meth + China White), net-worth game-over, borough-captioned subway sheet, amount keypad, leg-breaker chase, or an endless-mode board. - `ARCADE_SHOP=1` — open Phoenix's shop stocked with cash. - `ARCADE_GAMEOVER=1` — pose Phoenix's death card mid-campaign (progress score). e.g. `SIMCTL_CHILD_ARCADE_DIRECT_GAME=lander xcrun simctl launch booted com.albanese.arcade85`