Reverse-Engineering

Weftan, a backup plan that became a Rust rabbit hole

Weftan started as a fun backup plan. If TALA died hard, could I still have the layout engine around in some usable form?

TALA arranges diagrams for D2. Recovering its behavior and rebuilding it in Rust seemed like an interesting thing to try. That eventually became Weftan, and then a larger work-in-progress project called d2-rs.

Weftan was faster than the Go version of TALA when that was an external proprietary binary. The slightly annoying part is that TALA built into D2 was faster still. So naturally I started thinking about rewriting more of the surrounding thing too.

Recovering the behavior

The early work used TALA release artifacts: symbols, debugging information, disassembly, and comparisons with the executable. Reconstructed Go code informed the Rust implementation. This was recovery work, and the provenance matters; I did not independently invent the same layout engine by looking at a few pictures.

Then TALA’s source became available in D2 v0.9.0. That changed the situation quite a bit. There was now an upstream source implementation to follow and compare against, instead of relying entirely on recovered behavior.

There is a lot hidden inside “put the boxes somewhere sensible.” Containers, labels, edge routes, and the order in which equally reasonable choices get considered can all affect the final result. Two diagrams can look basically the same while disagreeing on a lot of coordinates.

So the useful comparison was to give both implementations the same measured graph and seed, then compare the actual geometry. When something differed, there was a smaller question to investigate than “why is this picture weird?”

Matching a corpus

The recorded Weftan baseline reached exact matches on all 120 successful single-seed comparisons in the checked-in corpus. That included 2,964 boxes and 974 routes, along with their corresponding labels and node icons. One large-grid case hit the work limit in both engines and was recorded separately.

I was happy with that. I also want to keep the size of the claim attached to it: those are the cases in that corpus, with that reference version and comparison setup. It does not mean every possible D2 input will behave identically.

Being able to reproduce a mismatch is useful even before it is fixed. It gives the next round of work an actual target. Otherwise it is very easy to keep improving the appearance of one example without knowing whether another example got worse.

The comparison machinery ended up being a useful result of the experiment in its own right.

The plugin tax

Weftan plugs into D2 as an external layout process. D2 has to pass a graph across that boundary and get the result back. Built-in TALA does not pay that same process and protocol cost.

There are two different comparisons here. Against the old external proprietary Go TALA binary, Weftan was faster in my testing. Against TALA built into D2, the end-to-end measurements went the other way. The baseline changed, and that distinction matters.

Plugin overhead is a plausible explanation for at least some of the gap with built-in TALA, but I have not isolated it well enough to say it accounts for the entire difference. “Rust” on the box does not make the complete workflow faster by itself.

That leaves Weftan in a perfectly reasonable place as a fun experiment. The backup-plan motivation got me started, and I got a recovered engine, comparisons, and a much closer look at the behavior out of it.

It also left me curious about removing that boundary altogether.

Following upstream in Rust

The next rabbit hole is d2-rs: a pure-Rust implementation targeting native execution and WebAssembly, with the layout engines running in the same process. The TALA work from Weftan has moved into that effort.

I am interested in following upstream behavior and exploring parallel execution where it makes sense. Having the surrounding pipeline in Rust should also make it easier to investigate costs without the external plugin boundary sitting in the middle of every layout.

This is still under construction. There are working source-processing and rendering paths, but plenty of compatibility work remains. A working SVG example is a nice milestone; it does not establish that the renderer, all the diagram features, or the other engines are interchangeable with D2.

I also do not have a speedup to announce for the rewrite. That is something to measure as it develops.

For now, Weftan is public. It was a fun backup plan, it taught me things, and it apparently gave me another project to work on.

10:34 am / Rust , D2 , Reverse engineering , Wasm

Could the Crazy Taxi Arcade TAS use one fewer passenger?

I got curious about whether a Crazy Taxi tool-assisted speedrun could be slightly faster with one fewer passenger.

I do not really have TAS skills. This was a “could that work?” rabbit hole that led to looking at passenger tables, destination selection, and the game clock. I have not made a faster run.

The tips are where the real TAS money is. Chaining Crazy Throughs and drifts can pile up money while carrying a single passenger. SJ’s run aims for the Crazy License, reaches $20,000 with four passengers, and uses the long Police Station trip for a huge combo chain. The run ends when the clock reaches zero. So the idea is to earn enough money from tips with fewer fares, then have less clock left to burn off.

The specific idea starts after the first drop-off at Cable Car Stop TOP, the plaza at the top of the hill. In SJ’s four-passenger route, the next pickup there goes to West Side Beach, followed by a passenger going to Popcorn Mania, and then one going to the Police Station.

My idea was to leave the plaza and grab the side-street passenger going to FILA instead of taking the nearby passenger to the beach. FILA puts me near the next Police Station pickup, so I could go from that drop-off to the final passenger without doing the beach-to-Popcorn Mania fare in between. FILA appears as “Shoe Rack” in the Android data I used for the investigation.

That is why three passengers seemed promising: skip a whole fare and its extra pickup and drop-off, while keeping the long Police Station trip for earning tips. The side-street detour and the changed route could affect traffic and combo opportunities, so I would still need to show that three passengers can earn enough. Fewer stops only help if the money works out too.

The clock is part of the problem

Picking up a passenger adds time to the Arcade clock. A successful delivery can add more. So changing the passenger route also changes how much time the game hands back to you.

The symbol-rich Android release was useful for investigating this. Its code shows a distance-based time allowance at pickup, with a multiplier that changes as play goes on, plus a delivery-rank bonus at drop-off.

In the calculation in my notes, a proposed three-passenger route adds about 12.56 fewer seconds than the four-passenger reference route. That comparison assumes the early-game multiplier and the best delivery judgement for each passenger.

That is less added game time, which made the idea interesting enough to investigate further. It is not a measured 12.56-second improvement in a TAS. Driving, passenger animations, and when the clock actually advances still have to be accounted for.

Which destination do you get?

The Android investigation also showed that passenger destination selection follows a counter. It cycles through three destination slots, with each slot lasting 600 passenger-manager updates, roughly ten seconds at 60 updates per second.

The choice is latched when the passenger event activates. Waiting until boarding finishes to reason about it would put the timing at the wrong point.

That makes route planning more interesting. Reaching a passenger at a different time can select a different destination, which changes the trip and the clock allowance. Picking three convenient dots on a map is not enough.

To look at this spatially, I forked the noclip.website viewer and loaded the GameCube Arcade city map with the extracted pedestrian pickup locations overlaid on it. Credit to noclip.website for the viewer; my additions turned it into a research map for this passenger-routing rabbit hole. It is much easier to reason about a side-street pickup when I can actually see where it sits in the city.

The extracted passenger records match the Android reference records, which is useful evidence for understanding the data. It does not prove that every timing behavior carries over to the GameCube executable.

Maybe later

The next step would be to verify the relevant behavior in the GameCube version and try the route in an actual TAS workflow. There is still a gap between a promising calculation and inputs that produce a faster run.

For now this is a curiosity with some code and notes behind it. I need to sit down with it when things are a bit less crazy and I have time to learn the TAS side properly. I might revisit it then. One fewer passenger sounded like a small question. Apparently it came with homework.

10:34 am / Crazy taxi , Reverse engineering , Games , Tas

Plainize Clip

I made Plainize Clip, a tiny macOS app that cleans the current clipboard and quits.

The short version is that I still wanted the old Plain Clip shape: copy weird text, launch the app, paste something boring.

Not a clipboard manager. Not a menu bar app. Not a background process sitting around in RAM forever. I launch it through Spotlight, it rewrites the pasteboard as plain text, and then it exits.

The annoying clipboard problem

Copying text on macOS is often not just copying text.

Sometimes it brings formatting. Sometimes it brings weird invisible characters. Sometimes it brings smart quotes, hard-wrapped lines, non-breaking spaces, tabs, and whatever else got dragged along from a PDF, web page, chat app, or ticketing system.

Usually I do not want a whole clipboard workflow. I just want the pasteboard to stop being annoying.

So Plainize Clip does the small version:

  1. Read the general pasteboard.
  2. If there is text, clean it.
  3. Write back plain text only.
  4. Quit.

That is the whole normal launch path.

Launch, clean, quit

The important behavior is that Plainize Clip is faceless.

There is no Dock icon to manage after the cleanup. There is no persistent process. There is no history database. If the current pasteboard has text, it cleans it with the saved preferences and replaces the pasteboard contents with text.

[... 608 words]

11:05 pm / Macos , Swift , Clipboard , Appkit , Reverse engineering , Codex