Trainyard v1 — Red Flag becomes an endless runner
Source: Sarah's direction of 2026-10-04. Trainyard replaces Red Flag in the Phone's Games folder. The underlying game is unchanged: a sentence arrives a word at a time and the player taps the mistake. It is now framed as an endless runner. The art pack is in Image-Assets/Phone-Games/Trainyard/ (140 PNGs at @3x, a runner-ready manifest and a QA report). An earlier bright two-track mockup (~/Downloads/Red Flag trainyard play screen.html) is superseded by the pack; its copy ("Tag the mistake", "That car was clean") is still useful.
Decisions taken with Sarah (2026-10-04)
- The runner is ambient. It runs on its own, the world scrolls, and the runner changes lanes, jumps or slides past obstacles by itself. Every tap goes to the words, so the game still tests reading alone.
- Endless; two strikes and the inspector catches you. A strike is a tap on a word that is not the error, or an error let through. The inspector meter's two pips fill, and the second strike ends the run. Every 12 sentences is a new train, and the pace steps up with each one.
- Clean trains stay. About a third of sentences have no error; letting one pass is a right call and scores.
- Colors stay as painted. The phone games are independent of the app's color restrictions, so the pack is imported without grading.
- The two adjustments are adopted. The inspector steps on the meter and in the world at the same moment, and results show trains reached.
- Strong readers get an out and a shift (see "Stations and express trains" below).
Decisions proposed here (defaults unless Sarah says otherwise)
-
Same content, new name. The sentences stay the corpus
red-flagfamily and its exportedred-flag.json(120 sentences: 30 openers, 50 with an error, 40 clean). No content changes.guide.json'smini_gamesentry gets the new label and adivergencenote through/cr-corpus. -
A new game key,
trainyard. Endless scores are not comparable with the old 12-sentence scores, so Trainyard starts a fresh personal best. Skill-log rows use the new key, still on Standard English Conventions. Red Flag rows already logged keep counting under "Red Flag". - Scoring: keep Red Flag's points (+100 per catch, plus 10 per word still to come, capped at +100; a clean train left alone +50; multiplier ×1…×5 on gains). **A wrong tap costs a strike and resets the multiplier, but no longer subtracts 150.** With strikes as the cost, a negative score would charge for the same mistake twice.
- Pace: train 1 starts at Red Flag's 350 ms per word. Each new train takes 25 ms off, down to a floor of 200 ms. Train 1 opens with 3 opener sentences. Each train keeps the bank's ratio of about a third clean. The session bag carries over, so sentences don't repeat until the bank runs out.
-
Layout follows the pack's play plate.
- The runner world fills the top ~58% of the screen: the environment plate, far and mid parallax, scrolling ground chunks, obstacles and trains in lanes, the runner from behind, the inspector behind, and near parallax and occluders on top.
- The HUD sits over the world: train number, score, multiplier, the two-pip inspector meter, and a 12-node progress row for sentences in this train.
- The sentence builds in the dark panel below, as railway placards that arrive one at a time with their arrival lights. Placards come in four widths (punctuation, short, medium, long), chosen by word length, and wrap onto rows.
-
Feedback:
- A catch: the correct burst, and the placard turns to its "correct" state with the fix.
- A wrong tap: the graffiti spray frames on the tapped placard, then the wrong overlay. The runner stumbles and the inspector state steps from far to closing to threat.
- A broken multiplier: the streak-break overlay.
- Getting caught: the inspector overtakes the runner in the foreground, then the results screen shows.
- The reveal line keeps Red Flag's: the error lights, its fix shows, and a few words of rule appear.
- Rendering: one SpriteKit scene for the world, the same approach as Coordinate Drop. SwiftUI draws the placards, HUD and overlays so the type stays crisp. The pace of the world rises with the train. Under Reduce Motion, the world holds still on the plate, parallax stops, and shakes become haptics.
- Type: the pack asks for an OFL face. Noto Sans Display Bold goes on the placards and HUD numbers, bundled from Google Fonts with its OFL text. The logo stays the pack's raster.
-
Art import as painted. An import script (
tools/phone-games/trainyard_import.py) copies only the files the game uses intoAssets.xcassets/Trainyard/under stable names, as single-scale @3x images, and checks that every manifest frame is present. It does not recolor. -
Red Flag goes away in the same change. Its view files, icon, generator script, UI test and
launch flags are removed, not kept beside Trainyard. The core content types (
RFSentence,RFBank,RFOutcome,RFScoring, the shuffle bag) stay because Trainyard deals the same sentences. Only the fixed-run builder is replaced.
Stations and express trains (for strong readers)
-
Every train ends at a station. After its 12th sentence the train pulls into a station, using
the pack's station plate. Two choices appear:
- Keep running: the next train starts, faster.
- Hop off: the run ends with the score banked plus a station bonus (100 × trains reached).
- How a run ends now counts. Results say "Hopped off at train 6" or "Caught on train 6". Hopping off keeps the best and the full bonus. Being caught keeps the score but no bonus. A strong reader can end a long run on purpose, and the choice itself is a risk call.
-
Express trains after the pace floor. Once the word pace reaches its 200 ms floor (train 7),
every further train is an express:
- the grace window after the last word shortens from 900 ms to 600 ms;
- the multiplier cap rises from ×5 to ×8;
- the HUD's train shell reads EXPRESS.
Strong readers get a harder game with a bigger payoff instead of an ever-longer one.
- The station stays calm. The station is the one place the run pauses, so it also gives a breath between trains. A station never ends a run by itself; the player chooses.
Phases and tasks
Phase 1 — Art import
-
Write
tools/phone-games/trainyard_import.py: copy the used files intoAssets.xcassets/Trainyard/as painted, under stable names, and check every manifest frame is present - Bundle Noto Sans Display Bold with its OFL text and register it with the app's fonts
Phase 2 — Endless rules (PeregrineCore)
-
Add
TYRunState: deals trains of 12 from the session bag (openers lead train 1, a third clean per train), tracks strikes (wrong tap or missed error), ends on the second strike, and keeps score and multiplier with the no-penalty wrong tap -
Add
TYPace: 350 ms per word on train 1, minus 25 ms per train, floor 200 ms, with a launch-argument override - Add stations and express trains: a station between trains with Keep running / Hop off, the station bonus, hopped-off vs caught endings, and express rules (600 ms grace, ×8 cap) past the pace floor
-
Add the
trainyardgame key toPhoneGameBestandSkillMap(Standard English), log entries throughPhoneGameLog, and keep oldredFlagrows readable - Unit tests: train composition, strike and catch rules, pace steps, scoring without the penalty, logging
Phase 3 — The runner world (SpriteKit)
-
Build
TrainyardScene: environment plate, far/mid parallax, looping ground chunks in random order, near parallax and occluders, at a speed tied to the train's pace - Spawn obstacles and trains in lanes ahead of the runner, and have the runner change lane, jump or slide on its own to clear each one (manifest timings)
- Drive the inspector from the strike count (far / closing / threat) and play the caught sequence on the second strike
- Reduce Motion: a still world on the plate, no parallax, shakes as haptics
Phase 4 — Screens and play
-
Build
TrainyardGame(adapted fromRedFlagGame): word-by-word arrival, tap at touch-down, grace window, reveal, train hand-off, player chip, logging and best - Build the play screen: HUD over the world, placards in the panel (four widths, wrapping, arrival lights, default / selected / correct / locked), reveal line, catch burst, graffiti spray and overlays, streak break
- Build the station screen (station plate, trains reached, Keep running / Hop off) and the title and results screens from the pack's plates (logo, PLAY, player chip; score, best, trains reached, hopped off or caught, catches / clean calls / misses, the run's sentences)
-
Swap the Games-folder tile to the Trainyard icon, add
-routeTrainyardand the-ty*debug flags, and update the live-session build's icon list
Phase 5 — Remove Red Flag and verify
-
Delete the Red Flag views, theme, fixture, icon, art script, launch flags and UI test, and replace
RFRunBuilder/RFRunStateuses; leave no file pointing at the old game - UI tests: the folder opens Trainyard, an autoplayed run reaches results through two strikes, and the player chip shows; screenshots on iPhone 17 Pro and iPhone SE
-
Stage the
guide.jsonlabel anddivergencenote for thered-flagmini game through/cr-corpus
Final review
Strengths
- The reading test, the content and the skill log stay exactly as they are. Only the frame changes, so nothing Sarah has sculpted in the corpus is touched.
- An ambient runner gives the runner fantasy (speed, a chaser, near misses) without adding a second skill that would muddy what a miss means.
- The pack's runner-ready manifest (lane anchors, z-order, timings, seamless chunks) maps directly onto a SpriteKit scene, and Coordinate Drop already proves that pattern in this app.
Weaknesses and edge cases
- Asset weight. The pack is 16 MB at @3x. Importing only what the game uses, as single-scale images, keeps it to roughly the used subset. The five environment plates and eight chunks are the bulk.
- Sentence length and wrapping. Standard sentences run up to 18 tokens. Four placard widths wrapping in the panel must fit 18 tokens on an iPhone SE. If they can't, placards scale down a step, and Phase 4's SE screenshot is the check.
- The perspective mix. The plates are perspective and the chunks are top-down, so the ground reads as a vertical scroller under a perspective skyline. If that looks wrong in motion, the fallback is to scroll the perspective plate's track region instead of the chunks.
- The station bonus can tilt choices. At 100 × trains reached, hopping off after train 1 pays little and after train 8 pays a lot. If players hop off too early or never, the bonus formula is the knob, and it sits in one tuning struct.
- The 120-sentence bank runs out around train 10 in one sitting. The bag reshuffles, so very long runs will repeat sentences. Express trains past train 7 make that less likely to matter, and more content in the corpus is the real fix.
Confidence: 80/100. The rules, content path and rendering approach are solid, and dropping the grading removes the biggest visual risk. The open risks are whether the perspective plate and the top-down chunks read as one world in motion, and how the station bonus tunes in play.