{"slug": "decompiling-fast-food-tycoon-2-drawing-the-world", "title": "Decompiling Fast Food Tycoon 2: Drawing the World", "summary": "A developer decompiled the 2001 business simulator Fast Food Tycoon 2 by running an agentic loop built on GPT-6.1 Sol Medium, using Ghidra to decompile fastfood2.exe's 4,046 functions into roughly 9.5MB of C-like text that was loaded into SQLite and queried with a custom script, decomp_index.py. The agent inspected the running game through the Windows ReadProcessMemory API to locate values such as cash, camera position and selected building, and controlled the game by patching its Windows message handler so commands ran on the game's own UI thread without altering the executable on disk. The work documents the renderer's dirty-area redraw, diagonal map-cell draw passes, temporary pixel marking for outlines of hidden characters, and final 16-bit color-plus-lighting conversion with a repeating dither pattern.", "body_md": "I spent the last few days decompiling my favorite computer game: Fast Food Tycoon 2. There's a very good chance that you have not heard of this oddball 2001 business simulator. But when I was a kid in 2004, I picked it out of a lineup of discounted games in a mall and it hasn't left my psyche ever since.\n\nTo briefly describe the gameplay: you build a pizza empire in a living, isometric city, renting restaurants and warehouses, hiring staff, designing pizzas, and managing your supply chains. Customers then judge your food, service, and reputation. Rival companies compete with you through advertising, political influence, and organized crime.\n\nFor me, the game always felt ahead of its time, with a surprising amount of world fidelity.\n\nTo decompile it, I set up an agentic loop on my computer using GPT-6.1 Sol Medium. We used Ghidra to decompile `fastfood2.exe`'s 4,046 functions into about 9.5MB of C-like text (function addresses, field offsets, constants, and calls).\n\nThis raw code was loaded into SQLite and wrapped with a script to allow finding related functions without having to read through large amounts of text:\n\n```\ndecomp_index.py grep    \"0xe8\\) = \"  # find field writesdecomp_index.py callers 0x4ed360     # find callersdecomp_index.py string  \"fnt\"        # find font references\n```\n\nIn Windows, there's an API to read process memory, `ReadProcessMemory`, which allowed the agent to inspect the running game and compare memory values with what appears on screen. For example, finding the memory address for cash, the camera position, or the selected building. And then the decompiled code was queried for the functions that changed those values.\n\nTo control the game, the Windows message handler was patched so that commands could be sent to run on the game's own UI thread. The agent could click buttons, move the camera, and advance the simulation without changing the executable on disk. An early breakthrough came from using a crawler to click through the game's interface and discover the rest of the unknown controls.\n\nAt this point, I steered the agent with my curiosity, and we went digging.\n\n## The Overworld\n\nA city is a painting plus a few hundred sprites. Below, we see Berlin at 12:30.\n\nUnderneath this fully rendered scene, the map file `ber.tga` has streets and plots (and Brandenburg Gate's shadow) but no buildings or people or cars.\n\nTo draw the overworld, FFT2's renderer draws the scene into a buffer in memory.\n\nFirst, it works out which screen areas need repainting, like where a person moved, or if the camera position has changed. It restores the background in those areas, and then draws the scene over it. Each pixel holds a 16-bit colour value and a separate lighting byte.\n\n```\ndirty_areas = find_areas_that_changed()draw_background(dirty_areas)\nfor draw_pass in scene_draw_passes:    for cell in diagonal_map_order:        draw_parts_for_this_pass(draw_pass, cell, dirty_areas)\n```\n\nThe renderer does several passes to control which objects cover other objects. It visits map cells diagonally (or, I suppose, isometrically). To keep hidden people visible (their outline can be seen through walls), the renderer marks their shape with a temporary pixel value before drawing objects on top of them. Later passes draw a yellow outline around hidden body parts and a white outline around hidden thought icons, and then finally fill the visible parts.\n\nHere's a complete redraw.\n\nFinally, the renderer combines each pixel's color with its lighting value and writes to the final 16-bit display buffer. This conversion applies a repeating dither pattern to draw intermediate shades.\n\n```\nmark_shape_with_temporary_value()draw_covering_objects()outline_covered_parts(outline_colour)fill_parts_where_temporary_value_remains()\n```\n\nI generated another cool graphic to show the efficiency of the renderer. Pixels that changed in the last 0.6sec are highlighted (cars, walkers, swaying tree tops, blinking signs). Most of the picture is not changed because the dirty-region system limits what needs repainting.\n\n## Lighting\n\nDaylight follows a table of 25 hourly brightness values: 70 at night, rising sharply around dawn to 251 during the day, and then falling at dusk.\n\nLamps respond to this light level. Their glow thresholds vary slightly with their object IDs, so they switch on in small groups. Their light pools stay at least 160 so that patches of pavement are bright throughout the night.\n\n```\nlight = interpolate_hourly_table(clock, brightness_setting)\nfor lamp in lamps:    show_glow = light <= 160 + (lamp.id & 7)    draw_lamp(lamp, glow=show_glow)    draw_light_pool(lamp, light=max(light, 160))\n```\n\nThe `id & 7` gives eight possible threshold offsets (0-7). With FFT2's default brightness, standard lamps switch on around 18:25–18:30 and off around 05:35–05:40. Other object families use lower thresholds so their glows appear later as the city gets darker.\n\nIt all ends up supporting a really satisfying day-night-cycle feel.\n\nEach pizzeria stage has its own light mask, stored in e.g. `h_pizz_a1.m.spr`. Each visible pixel in this mask holds a brightness value. The renderer compares that value with the light already at the same position and keeps whichever is higher. So while the rest of the building darkens, the windows and sign stay bright!\n\nA separate file, `h_pizz_a1.l.spr`, provides the window colours used at night. The renderer draws those colours, then applies the light mask (as mentioned earlier, colour and light remain separate until the final step when the pixels are drawn on screen).\n\nThese light curve values may be altered by the brightness setting so it works out like:\n\n```\nlight = base + (251 − base) * brightness\n```\n\n## The Cast\n\nAnything that moves is stored in an `.ani` file which is a sprite sheet and named animation sequences. Most of the internal text I came across was written in German (language of the developers).\n\nHere's the frame-by-frame breakdown of an office worker eating in a restaurant.\n\nAs FFT2 is a pixel-based isometric game from 2001, every animation is pre-drawn.\n\nThe \"walker update\" logic compares the next node's position with the current one, and picks the direction animation from the two signs: `(−,+)` is 1, `(0,+)` is 2, `(+,+)` is 3, etc.\n\nThe named animation sequence also stores how far the sprite moves per frame. For example, the waitress's `gehen4` moves `(+1.25, 0)` every frame, and `gehen3` moves `(+0.884, +0.442)`, and so on. The tick adds this vector on every frame advancement so that feet and ground move in sync.\n\nMaps have predefined pavement nodes (for walkers) and street nodes (for vehicles).\n\nThe errand boy is the fastest walker at 27px per second, compared with the tourist (16.2px/sec) and the celebrity (13.8px/sec).\n\n## Inside a Branch\n\nThe restaurant view uses the same render process as the overworld, drawing flat images into a buffer, storing color and light separately but indoor light stays at 248 regardless of the hour (while daylight outside reaches 251).\n\nThis view uses separate, larger artwork for the characters, and uses a special draw order to place people sitting on furniture. When the chair back faces the camera, the renderer draws the main chair image, then the character, and then the back of the chair (or the \"front\" of the chair from the camera's POV).\n\nThere's a special sprite for the pizza on the table. I will forgive the animators for making everyone have the same eating pattern.\n\n## Hidden Details\n\nI came across many quirks in my research and I'll list a few favorites.\n\nPeople keep their daily habits. When citizens are created, they receive a schedule. These schedules don't reset each day, so an office worker assigned to a particular cinema for the afternoon keeps returning there. Likely contributing to the \"real feel\" of the city.\n\nThese schedules contain stereotypes:\n\n- Goths stay home until 09:00.\n- Students have a 40% pub allocation for midnight–03:00.\n- Big shots have a 70% opera allocation for 18:00–21:00.\n\nThe game calls itself \"Pizza3\". As seen in a city config file `Init-File für Pizza3-Stadt`.\n\nAnd finally, I was able to solve a childhood mystery: how the heck is this game measuring and scoring my pizza-making ability? Let's review now that I've unlocked this knowledge.\n\nThe game divides the pizza into 24 regions to score how evenly spread the toppings are, looks for matching pieces on opposite sides, and checks which ingredients go together (and different customer groups get different ratings).\n\nThere's even a plagiarism check (!) based on ingredient weights. So rearranging a competitor's pizza (a strategy I've definitely tried before) doesn't fool it. A copied recipe is marked \"not original\" and misses out on +30 taste points for yuppies, goths and tourists, and +5 for big shots.\n\nAll this detail, and very little in-game guidance for it. As I recall, since I lost it during a house move, FFT2 shipped with a ~100 page manual. This was back when it was fine for games to be inherently challenging!\n\nA cool note: the images and videos that I generated for this post used some of FFT2's decompiled fonts. Also, some content still has \"PC2\" visible because I actually call this game Pizza Connection 2 because that's the European name!", "url": "https://wpnews.pro/news/decompiling-fast-food-tycoon-2-drawing-the-world", "canonical_source": "https://healeycodes.com/decompiling-fast-food-tycoon-2-drawing-the-world", "published_at": "2026-10-24 00:00:00+00:00", "updated_at": "2026-10-24 18:16:41.700471+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "ai-tools"], "entities": ["Fast Food Tycoon 2", "GPT-6.1 Sol Medium", "Ghidra", "SQLite", "ReadProcessMemory", "decomp_index.py", "fastfood2.exe"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/decompiling-fast-food-tycoon-2-drawing-the-world", "markdown": "https://wpnews.pro/news/decompiling-fast-food-tycoon-2-drawing-the-world.md", "text": "https://wpnews.pro/news/decompiling-fast-food-tycoon-2-drawing-the-world.txt", "jsonld": "https://wpnews.pro/news/decompiling-fast-food-tycoon-2-drawing-the-world.jsonld"}}