Blind accessibility mod for Cook, Serve, Delicious! 3!?
  • Game Maker Language 57.7%
  • Rust 12.9%
  • Python 9.9%
  • C 8.6%
  • C++ 5.5%
  • Other 5.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-26 08:49:02 +02:00
abbrev Access Delicious: screen reader accessibility for Cook, Serve, Delicious! 3?! 2026-09-23 21:36:26 +02:00
ext loads of shit 2026-09-25 18:05:16 +02:00
gml loads of shit 2026-09-25 18:05:16 +02:00
installer add windows installer 2026-09-26 08:49:02 +02:00
patch loads of shit 2026-09-25 18:05:16 +02:00
patreon add windows installer 2026-09-26 08:49:02 +02:00
recipes Buffers, verbosity keys, correct food names, autosave 2026-09-24 00:25:09 +02:00
sounds/Voices loads of shit 2026-09-25 18:05:16 +02:00
tools Orders grouping, Shift+D, trash/perfect-stop voicing, fixes; state-based test driver 2026-09-24 13:15:00 +02:00
vendor Access Delicious: screen reader accessibility for Cook, Serve, Delicious! 3?! 2026-09-23 21:36:26 +02:00
.gitignore add windows installer 2026-09-26 08:49:02 +02:00
game.mk Orders grouping, Shift+D, trash/perfect-stop voicing, fixes; state-based test driver 2026-09-24 13:15:00 +02:00
Makefile loads of shit 2026-09-25 18:05:16 +02:00
README.md add windows installer 2026-09-26 08:49:02 +02:00
run.sh loads of shit 2026-09-25 18:05:16 +02:00

Access Delicious

Screen-reader accessibility mod for Cook, Serve, Delicious! 3?! (Windows, Steam build 14196546 / v1.02.03). Speech goes through PRISM (NVDA, JAWS, UIA, SAPI… on Windows; under CrossOver, the prism_crossover backend + macOS helper). Windows is the only target; development and testing happen on a Mac under CrossOver. (A Mac-native prototype was archived outside the repo, in ~/src/claude/access-delicious-mac-archive.)

Read this before working on the mod. Game logic questions: read the decompiled source in re/win_gml/CodeEntries/ first (local only: re/ holds the game's decompiled code and is not in git), then go to the screen in-game and screenshot it to see what a sighted player actually sees.

How it works

The Windows data.win is GameMaker bytecode (not YYC), so UndertaleModTool can decompile and recompile it. The mod is GML injected into the game plus a small DLL.

  • gml/gml_Object_obj_csd3a_*.gml — the controller object's events. Created from O_initvars Create, persistent.
    • Create: binds the DLL (external_define), then the lib methods run (see below).
    • Step_1 (Begin Step): control-socket commands, mod hotkeys (cleared with keyboard_clear before the game's Step events see them), key-injection queue.
    • Step_2 (End Step): the announcers, then text_end_frame().
  • gml/lib/*.gml — all mod functions, as methods on the controller (name = function(...) {...};), concatenated alphabetically into Create by the patcher. Create-time statements in a lib file may only use things defined in files that sort before it.
  • ext/accessdelicious.c → accessdelicious.dll (mingw): PRISM speech, a log file, and the development TCP control server.
  • patch/patch.csx — the UndertaleModCli script that builds the patched data.win:
    1. imports the mod code (lib concatenated into Create);
    2. appends the controller's creation to O_initvars Create; 2b. prepends with (obj_csd3a) hotkeys_once(); to O_foodbrain Begin Step: the game reads order/station keys there, before the persistent controller's Begin Step, so mod hotkeys (Shift+number) must be handled and cleared from inside it;
    3. before/after every instance_deactivate_all(...): obj_csd3a.before_pause(); and instance_activate_object(obj_csd3a); (tutorial popups deactivate everything; gameplay objects can't be read while deactivated, so state is snapshotted just before);
    4. rewrites every draw_text* call (exact variants only; not draw_texture_*) to obj_csd3a.dt_text*(object_index, ...) so the mod sees every string drawn, by which object and where (lib/text.gml).

Modules (gml/lib)

file covers
text.gml text capture model (text_of(obj), text_new), grace 12 frames
fallback.gml speaks unhandled drawn text (queued); silent list; subtitle objects
menus.gml the game's widget framework (all Menu children): focus, values, positions, locked items, alerts, descriptions
routeday.gml / territory.gml route/day and territory map menus (custom-drawn); day medals and road-closed gates from global.MODEchefforhire
pause.gml in-day pause menu (O_pausemenu)
todaysmenu.gml Today's Menu and the main menu's Food Catalog (FoodCatalog): slots, Begin, setup/filters carousels, food grid, food card (actions, stats) — state machine documented at the top
splash.gml day card (O3_newsplashstartend): start (route, day, hint pages) and end: a results list walked with Up/Down (splash_items: medal, YUM, % to next level, money, CC, orders, points breakdown, menu, route), medal rules
cook.gml recipe/prep pane: title, recipe lines, keys (per page; locked steps), placed ingredients, board info, batch size
orders.gml customer tickets (O_foodbar): new, cooking/ready/about to burn/burnt/leaving, walked out, boxed, served (result), impatience; truck status (driving/arriving/stopped)
holding.gml holding stations (O_holdingstationbar): lifecycle (incl. ruined by mistakes) + status
saving.gml "Autosaving" while O_savinggame (the spinning pickle) writes the save; queued after the menu change that starts it
recipes.gml generated (recipes/gen.py, from re/): food/stage recipes that vary per customer; those orders say "Wants" instead of "Recipe"
buffers.gml arrow-key buffers during a day: Menu Board (board_items), Orders (order_detail per active order)
board.gml Shift+B: the menu board computed like the prep pane draws it (also read when F9 opens it)
settings.gml ini settings, hotkeys, pause snapshot, per-handler error isolation, help
ctl.gml development control commands, ctl state (JSON for tools/drive.py)
access_menu.gml Options > Gameplay > Accessibility: speech rate, subtitles, station keys, board sort, order number voice and volume (patch.csx 2c/2d)
sounds.gml the mod's own sounds (sounds/ → <game>/accessdelicious/sounds/, streamed, played one at a time): order number when an order is ready, in a voice from sounds/Voices
upgrades.gml Upgrade Truck (UpgradeMenu): focused node (name, cost/level, on/off, grid row/side, "needs the one above"), description once per node, purchases/toggles/newly unlocked, refusals (locked, max, too few parts, S on unowned); focuses the first node on open (the game ignores Enter until an arrow)
trinkets.gml Customize Truck (TrinketsMenu): modes, trinket list (name, poster/statue, price, owned/new/placed, unlock condition), filter row, refusals (locked, can't afford, truck full, nothing to edit), purchases, placing/choosing with area, row/column and the nearest trinket per direction (left/right/above/below; the bottom counter's rows are behind/in front; "overlapping" within 90 px)
abbrev.gml generated (abbrev/gen.py, run by make): full names for abbreviated ingredients/foods, expand_name(name, food); edit abbrev/hand.tsv

Keys (mod)

  • Shift + holding-station key (F1..F8 by default, global.KEY_HS<n>): station status
  • Shift+O orders, grouped by food and state, most urgent first ("Falafel leaving 1, 2; Blancmange ready 3") · Shift+I order numbers, most impatient first ("3, 1, 5": leaving, then patience stage, then time left before walking out) · Shift+ (1..7) one order in detail (stage, station, mistakes, patience) · Shift+B menu board (needed/makes/cooking, any time) · Shift+D truck: distance to the next stop or customers remaining, stop N of M · Shift+R re-read the open cooking pane + placed so far (keys always included) · Shift+K the pane's keys · Shift+W what the open order wants (recipe lines; twice within 250 ms: only the lines still to do, from the pane's placed slots stackCKarray) · Alt+K keys spoken when a pane opens on/off (ini speech/keys) · Shift+M medals (today's count, then targets; also in the pause menu; territory map: totals) · Shift+H keys for this screen
  • During a day, buffers: Left/Right switch (Menu Board, Orders: one item per Shift+O group) and read ": "; Up/Down move through items (built on each press; "empty" if none). Arrows pass to the game while paused, on the day card, in tutorial popups, and when bound to Page Turn or an open pane's ingredient (buffers.gml).
  • End-of-day card: the headline (medal) is announced; Up/Down walk the results list, most important first (the game only uses Left/Right there). Enter skips the tally, then continues.
  • Shift+T subtitles on/off · Shift+= / Shift+- (or Shift+PgUp/PgDn) speech rate, only if the PRISM backend reports SUPPORTS_SET_RATE (csd3a_can_rate; else "Speech rate is set in your screen reader") · Left Ctrl stop speech (not swallowed: Ctrl is the game's default auto-serve key)
  • Options > Gameplay > Accessibility holds the same settings (plus the Menu Board sort: holding station order, or greatest need, which also orders Shift+B; Order Number Voice, Off or a voice, default Samantha; and Order Number Volume, 0–100%, default 50%. Changing either previews the game's ready sound with a random number, cutting off the last preview). Saved in accessdelicious.ini.
  • Order ready: alongside the game's cooked sound, the order's number (sounds/Voices/<voice>/N.ogg; Evan and Samantha made by their generate.sh: say, normalized, about 4 dB louder). Voices are the folders with a 1.ogg, found at runtime (sounds_voices): a new folder needs no rebuild. Gain = volume^0.67 × the game's UI sound volume, so 50% ≈ plain say and 100% = +4 dB. Streams are in no audio group: the master volume applies, group mutes don't.
  • The user prefers letter keys over F-keys for mod functions.

Build, deploy, run, test

make deploy          # build DLL + patched data.win from the ORIGINAL (depot copy), copy into the bottle
./run.sh             # relaunch CSD3 in the CrossOver "Steam" bottle (Steam must be running there)
./run.sh --quiet     # the same for test drivers: speech only logged ("(quiet)"), master volume 5%
tools/ctl.py quiet off   # hand a quiet game to the player (speech on, their volume); "on" goes back
make restore         # put the original data.win back
  • Windows single-exe installer: installer\build_installer.bat [version] (Rust, MSVC, CMake, Ninja) → build\AccessDeliciousInstaller.exe, a wxWidgets window like the other mods' installers but with no download: it carries this mod version (the xdelta patch from the untouched data.win to dist\data.win, a release accessdelicious.dll built with MSVC, PRISM, the voices). The original data.win comes from CSD3A_ORIG, default the Steam install's data.win.orig (else data.win). --cli, or --install/--uninstall [--game DIR] for scripts. It refuses to run while CSD3.exe is open. cargo test --release -- --ignored with CSD3A_E2E_DIR (a folder with a CSD3.exe and the original data.win) runs the real patch.
  • Release: make release VERSION=x.y → build/AccessDelicious-x.y.zip (~1.1 MB, no game data): access-delicious-setup.exe (installer/setup.c, xdelta3 decoder from vendor/xdelta compiled in, GPLv2), accessdelicious.xdelta (ORIG → patched data.win, ~450 KB), a release accessdelicious.dll (-DCSD3A_RELEASE: control server only if accessdelicious.dev exists in the game folder) and the PRISM files. The installer checks both SHA-256s (build/release/hashes.h), keeps data.win.orig, and offers update/remove when run again. UI is message boxes, the folder picker and a wait window; --yes skips them and prints to stdout (tests under wine). Under Wine it hard-links prism-crossover-ttsd (CopyFile drops the executable bit). A game update changes ORIG. make check-game compares Steam's public build with game.mk; make update (tools/update_game.sh) downloads the new depot with SteamCMD (cached login STEAMUSER, default guilevi), keeps the old data.win in backup/, syncs the CrossOver install, re-decompiles into re/, regenerates abbrev/recipes, writes game.mk, then deploys and builds the release. Test before shipping; add the game's version number to GAMEVER in game.mk by hand (it isn't in the game data).
  • Game install used for testing: CrossOver bottle /Volumes/T7/Bottles/Steam (external SSD "T7"), game copied from the SteamCMD depot (~/src/claude/tools/steamcmd, user guilevi cached) plus a hand-written appmanifest and steam_appid.txt.
  • The bottle's graphics backend must be DXMT (D3DMetal fails GameMaker's CheckMultisampleQualityLevels with E_INVALIDARG dialogs). CrossOver's wine CLI needs CX_BOTTLE_PATH=/Volumes/T7/Bottles; the bottle config overrides CX_GRAPHICS_BACKEND.
  • Log: <game folder>/accessdelicious.log (previous run .log.1); game stdout: ~/Library/Logs/access-delicious/wine.log.
  • Control channel: tools/ctl.py <cmd> (127.0.0.1:47700; CSD3A_TCP=host:port overrides) — ping, room, insts, vars <Obj|id|global>, get <target> <var>, screen [obj], ui, hs <n>, orders, key <k>, keys <k>..., chord shift 1 (keys held together: modifier tests), hold enter 130 (one key held N frames: hold-to-buy), upgrades (the upgrade tree), board, order <key>, expand <name>, mods, say <text>. One client at a time: while play.py runs it holds the connection and ctl.py times out (the game isn't hung — check the log is still growing).
  • tools/drive.py start|play|day: the test driver. Reads ctl state (JSON: screen, menus, Today's Menu, stations, orders, board, the open pane's recipe as data), never spoken text, so wording changes don't break it. Loads save slot 0 (1S_…, the test save; --slot), fills Today's Menu to the day's points, plays (serves, makes specials, preps stations the board needs), and recovers: actions with no effect are blocked for a while; a screen that doesn't change gets Enter (Escape only in panes/cutscenes; on the front end it returns to the title). The day-1 tutorial needs F4, which it can't press. tools/nav.py mainmenu|prep|orders still drives the tutorial; start_day.sh and play.py are the old text-scraping drivers.
  • The user's own game is in another save slot (2S): never load it from a driver.
  • Injected keys only work while the game window has focus (the runner drops simulated key state otherwise; control commands like get still work). If keys do nothing, run tools/focus.sh first. The user typing in Terminal takes focus away.
  • Screenshots: tools/winshot.sh [out.png] (CGWindowList, owner CSD3.exe, then screencapture -x -o -l <id>).
  • A GML runtime error shows a blocking "Code Error" dialog: check for it (window title) after launches. Keep waits short (~30–45 s).

UndertaleModTool compiler traps (all hit in practice)

  • Calls to newly added global script functions bind wrongly (runtime "<unknown_object>.name not set") → everything is controller methods.
  • Indexing a call result (f(x)[0]) fails: "Invalid expression on accessor" → a temp variable.
  • [$ key] compiles as an array index → variable_struct_get/set.
  • Avoid ?? and [? key] → nz(), ds_map_find_value/ds_map_set.
  • mod is an operator (not a variable name).
  • A function can't call itself or others in its own script (moot with methods).
  • UndertaleModCli hangs unless stdin is /dev/null (the Makefile does this).
  • Menu callbacks and on_back can't be mod methods: the framework runs them with script_execute, which in this runtime takes script indices only (a method ran as camera_create: Code Error). Route through a game script patched to hand off to the mod (patch.csx 2d: options_menu_callback with user data [100+, event]).
  • Never a built-in global as a struct key: { room: ... } assigns the built-in room, i.e. room_goto; ctl state sent the game to ROOM_initvars on every call, which re-ran O_initvars and stacked menu music players (music heard several times). Instance built-ins (x, y, visible) are fine.
  • Runtime: instance ids are ref values — compare/sort with real(id); ds_map_keys_to_array returns undefined for an empty map (map_keys()); errors inside array_sort callbacks escape try/catch.

Game facts worth knowing

  • Widget arrays: [0] type, [1] internal name, [11] parent, [12] id, [22..] per type (table in lib/menus.gml). Lists are [count, items]. visible = on screen.

  • Holding station status: 5 prep open, 1 cooking, 10 ready, 11 expired, 12 ruined (made with mistakes: only a black flash, an icon and a sound); freshness 6..0 every LV_HSfreshalarms frames, then a doubled final warning (O_holdingstationbar_Alarm_3).

  • Board: O_recipepane.LVCSD3_HSsprites[1..] = [sprite, food, ?, name, servings per batch, ?, batches needed]; "Needed" = batches × servings per batch.

  • Medals (O3_newsplashstartend_Other_11): bronze <15 bad; silver <8 bad+average+sick; gold none.

  • Order LV_status: 0 waiting, 5 pane open, 1 cooking, 2 cooked, 3 burnt (already lost: the ticket slides off and O_foodbar_Other_14 counts it bad and destroys it), 6 leaving, 100 served. While cooked, LV_cooktimeangle < -270 is the red flashing timer (~5 s before it burns). Lost orders (burnt, walk-outs) are destroyed without ever showing 100: the mod notices them vanish; result from LRCHK_errors (0 perfect, 1–2 average, 3+ bad). Multi-stage special orders (ice cream: Cone Prep → waffle iron → scoops) go 5→1→2→5→… Cooked (2) means "serve now" only when LV_stagephase + 1 == LV_endingstagephase (O_foodbar_Other_17); otherwise the key opens the next pane (Puff Puff special, ending 8: a topping step). The mod says "cooked, needs prep" for those (serve_next in orders.gml).

  • Truck global.CSD3FTstatus: Begin → SetupTravel → Travel → Arriving → Stop → (queue empty) Wrapup/SetupTravel. An order finished while not stopped is boxed (LV_takeout, status back to 0) and served with its key at the stop.

  • Ingredient buttons: one O_NEWingredientbuttonshelper pair per key; LVT_lockdown 0 usable, 2 greyed (step recipes). Pages: other pages' buttons are moved ~2000+ px off screen; the page-turn helper is LV_controlkeycode 2002 (LVT_pagecurrent), Space turns.

  • An opened order can't be cancelled: Enter (button 2001) stays locked until LVNEW_ingredientcount >= LV_ingredientminimum (Alarm_5), and station keys are ignored. The mod voices a locked Enter with the usable keys ("Enter locked: place an ingredient first. Now S Butter").

  • Opening an order while it cooks (status 1) pulls it out raw: LRCHK_errors += LV_rawfoodtilt (O_foodbar_Other_10) → the mod says "pulled early". Wait for "Order N ready".

  • Recipe box rows: ingredient columns (each an RCP_ar alias, "(2)" = twice) or an instruction ("After placing the ingredients, Dunk & Cook.") drawn in fragments; the instruction's action is a step button (label contained in the line). The pane also draws LS_abbrev (the food) small on the title row, which may equal an ingredient ("Crab Legs").

  • Pane titles are the recipe variant ("German Pretzels") or a step ("Standard Prep", "Cone Prep"); LS_foodname is the food.

  • Ingredient names: each has a button label LS_ingredient ("Onion Sht.") and an alias LS_ingredalias (the recipe line; sometimes spelled out, "Onion Shoot") in SCRIPT_foodpopulate_*. LV_ingredientkeycode is only the icon subimage: there is no master ingredient table, so ~330 abbreviations ("P.Sugar") are expanded from abbrev/hand.tsv.

  • Recipe lines draw O_recipepane.RCP_ar[slot][1] aliases; each button helper's LV_attachedto is its slot, so each line maps exactly to a key; slots 1–8 page 1, 9–16 page 2, 17+ page 3.

  • F9 (Today's Menu) opens a prep pane with LV_whichprepst 109: the board only, no keys.

  • Quitting a day from the pause menu destroys the day's objects while deactivated: per-day state keyed to the O_foodbrain instance resets when a new one appears.

  • Boxed orders (LV_takeout) switch to status 6 the moment the truck stops and walk out ~9 s later: serve them first at each stop.

  • Order status 6 (leaving) can still be served (O_foodbar_Other_17 takes 0 or 6) and goes back to 0 when its food turns up (Other_23). Holding station key on a cooking/ready station: LVT_trashnow 1 = "trash?" (sound only, 120 frames), second press = 2, thrown out.

  • Stop end: O3_stopwordsflash.LVW_texttype "PerfectStop" when a stop had no average/bad. Day totals = global.O3perfecttmp/averagetmp/badtmp (finished stops) + Operfect/Oaverage/Obad (current stop, reset per stop); Osick, Odelicious are per day.

  • HUD sign: O_foodbrain.LVTT_LCDnumb + LVTT_LCD ("12 MI. Away", "5 Remaining"); stop counter LVCSD_stopdisplay of CSD3routes[rest][(stage-1)*20+10].

  • O_autoservebutton (drawn "CTRL") exists while Whisk and Cleaver can auto-serve: silent, reported by Shift+O.

  • LS_foodname is copy-pasted wrong for some foods in SCRIPT_foodpopulate_*: Satay (282), Food Truck Beans (292) and Food Truck Fries (291) are "Fries"; Chicken Tikka, Soto Ayam and Seafood Boil are "Brownies"; Egg Rolls is "Pretzel". Orders are named from global.FOODGLOSS[LV_foodcode][0] (the name the menus show) for codes below 400 (order_food). Station codes are food + 1000.

  • Saves: O_savinggame (MainMenu on global.do_save, e.g. backing out of route select; the loading screen after a day; quitting). Quitting a day from the pause menu does not save.

  • Orders: LV_isthisanHSfood 0 = special order (SO badge); "either" foods come both ways, so announcements add "special". An HS order whose station is empty opens a prep pane.

  • End card LV_tempvarsplash: [0] delicious [1] perfect [2] average [3] bad [4..9] orders by menu points [10] money [11] bonus mult [12] CC [13] levels gained [14] YUM level.

  • Prompts built as menu_create(..., "<owner> <name>", obj) (clear all, buy, unlock, add to menu, not enough CC) draw their own text: listed in menu_text_objects (menus.gml). The buy price is a sprite (food_item[14] CC); balance is global.CURRENCY_foodtokens.

  • Test-rig only: an injected F4 quits the game (keyboard_key_press(vk_f4), or an osascript key code 118), modded or not: clean game_end, no ScreenKey flag, Alt not held. The user's real F4 works fine. Test with CSD3A_STATIONS=1,2,3 tools/play.py.

  • Silent failures the mod must voice: station keys ignored while a pane is open; orders unservable from empty/expired stations; opening an expired station throws it out.

  • Upgrade Truck (UpgradeMenu, the "upgrade menu"; YUM level ≥ 5, else FoodtruckLockedAlert): a Menu whose focus_widget is a tree node (like routeday/territory). 18 nodes built in upgrade_menu_create; data global.upgrades[key]: [2] name, [3] description, [4] cost (number or per-level array), [5] level (negative = toggled off), [6] level cap, [7] kind (1 prep station, 2 holding station), [8] FTupgrademods row. Node [6]: 0 locked, 1 available, 2 bought. Arrows move along the node's grid row/column, wrapping (__upgrade_scroll_x/y). Buy = hold Enter/Space (held_duration; rising padc_01 loop, then _15_truck_upgraded; too poor: error sound + token highlight only). S toggles a bought station on/off (upgrade_menu_toggle_upgrade). Currency global.CURRENCY_upgradetokens (truck parts). The panel draws name/description with draw_text* (captured). Not voiced by the mod yet.

  • Customize Truck = trinkets (TrinketsMenu; YUM level ≥ 3, else TrinketsLockedAlert; entered via O_loadingscreen, deactivates Menu). Cosmetic only. trinket_state: 0 mode list (Up/Down over mode_labels: Show All / For Sale / Inventory / Edit-Decorate / Screenshot Mode), 1 trinket grid (6 columns, trinket_selected; filter row above the top row), 2 edit/place, 3 screenshot. global.TRINKET_catalog[i] (99): [0] status (0 locked, 1/3 owned, −N = N CC, −(1000+N) = N parts), [3] name, [4] unlock condition text, [5] "Poster"/"Statue". Buying: BuyTrinketAlertMenu (a Menu). Placement is grid-based: trinket_areas Top (32×5; statues only on its bottom row), Left/Right (posters only), Bottom (statues only); each area's [11] ds_grid holds FTD_objectplace indices. Edit keys: arrows step to the next empty cell (wrap), Shift next area, Space pick up (edit select), Enter/Escape confirm, Backspace delete, Ctrl grid, F1 clear all. New trinkets auto-place at a random free spot. Not voiced yet; plan: modes, grid (name, status, cost/unlock), buy, then spoken positions ("Left side, row 4 of 17, column 2") while placing.

Next

  • End card results list (splash_items: YUM, % to next level, perfect day, money/CC after an Enter skip, computed as Other_10 does): not yet heard live (replay a day, once skipping the tally).
  • Auto-serve (Ctrl) feedback. Standard mode: an HS order whose station is empty leaves almost at once; each walk-out is said twice ("leaving!", then "walked out"): maybe drop one.
  • Abbreviations: built, not yet heard live. 40 guesses in abbrev/hand.tsv are marked ? for the user to check. Only ingredient/food names are expanded (keys, recipe lines, placed, Now:, orders, holding stations); recipe instruction lines and board names are not.
  • An HS order whose station ran out opens as a make-to-order pane: maybe say so up front.
  • Speaker names for dialogue; save-slot wording ("00, date (time)").
  • Customer walk-outs; chores (later days).
  • Today's Menu: upgrade/random-day panel text, unlock prompt (no locked food found yet), actually buying (only opened and cancelled), not-enough-CC alert untested. Day 3 needs a 3-point menu.
  • Upgrade Truck and Customize Truck: done and tested on save 3 (3S); not yet heard by the user. Buy prompt price reads "Buy X for 5 CC, you have 86".