Worthify / Journal

Rebuilt in Rust

Published October 14, 2026 · Worthify engineering

Are closed source tools really closed anymore

The same keys take two balls to the same goal: one in Linux Neverball, one in Rust on macOS. An elevator caught us dividing by 1,000.

Neverball is GPL-licensed, so we can share this experiment under its terms. Engine source remained withheld. The first build reused licensed artwork and courses; a fresh runtime investigation below recovered pixels without opening the source images.

— views
Linux reference Rust / macOS
The licensed Neverball reference and independent Rust version respond to the same recorded ten-coin route inputs, side by side.
Same keys. Two engines. A 21-second excerpt from the same-key ten-coin run. Licensed Neverball reference; implementation source withheld during construction. Fixed logical time.Watch the full run and inspect the differences

Neverball gives you a ball on a tilting floor: collect coins, open the goal and get there without rolling off the edge. A small correction can become a large regret. The game below is our Rust reconstruction. In the opening side-by-side comparison, the Linux original is on the left and Rust on macOS is on the right. Both receive the same keys. The original engine source stayed withheld while we built the replacement.

We wanted to share a game you could actually play. Neverball’s GPL license gives us permission under its terms, including the applicable source and notice obligations. We withheld the engine implementation source and worked from the compiled executable and runtime. The first build reused licensed artwork and compiled courses; the later artwork-recovery experiment is described below. Neverball is an open-source reference, not proprietary or specially obfuscated software. Understanding and modifying software you own for your own use is a supported ReAgent use case; applicable licenses and other rights still matter.

Play the Rust reconstruction

Choose Play and a level set, then select a course. Enter starts; arrows or mouse movement tilt; Escape pauses. 1/2/3 change camera mode, S/D rotate, and Shift rotates faster. Touch controls sit beneath the canvas. On a phone or tablet, tap Enable tilt, allow motion access, and hold still to center; Center tilt resets your neutral angle. The first course needs ten coins to open its goal.

Open Neverball in Rust on its own · Licenses and differences

The floor has a lot to answer for

That familiar floor hides several jobs. Something loads the course, places the coins, moves the ball, handles collisions and decides where the camera looks. Together, those calculations make the game feel like Neverball. A new engine can draw the right scene and still get almost every one of them wrong. We wanted to find out how much of that behavior we could reconstruct from the executable and the running game.

ReAgent is Worthify’s reverse-engineering workspace. It lets us inspect an executable, follow the code that uses a value, ask focused questions about what it does and work against a connected runtime. For this experiment, that runtime was a Linux desktop running the reference game. ReAgent supplied binary analysis and reference access; separate coding agents authored the Rust implementation and comparison tooling.

Nice ball. Show me the bytes.Edited walkthrough · 18:54
ReAgent’s chat opens the Worthify Decompiler and runs probes in its connected VM. The fresh Decision One run finishes with 41 coins; Rust reaches success on the same update. Across 4,144 compared batches, the maximum position separation is below three millionths of a world unit.

Download the walkthrough · Narration, sources and credits

Jump to a chapter · 6 stops inside the binary
ReAgent / run the reference
Full ReAgent VM workspace with Neverball running beside the Decision One controller’s live position, velocity, target and latency panel.
ReAgent’s connected VM during the fresh Decision One run. The native game has 12 coins; the adjacent panel shows recovered position and velocity, target 11 and a 133 ms model round trip. The model chooses targets, while authored code supplies the steering.

Our reference was Ubuntu package 1.6.0+git20180603-3build2. Its stripped x86-64 executable had lost debugging symbols, but it could still load a level and react to our inputs. In ReAgent’s connected desktop we could change a camera mode, repeat a key sequence and return to the calculation that might explain the result. The running game gave us somewhere to check our interpretation. Attribution and experiment scope.

Where did that coin come from?

Start with a problem a modder might recognize: the level file contains thousands of numbers, and changing the wrong one ruins the level. A screenshot can show where a coin appears. The executable can show which record supplied its position. We uploaded the game to ReAgent and located the level reader, then inspected its generated decompilation alongside the assembly. A decompiler turns machine instructions into a more readable approximation; the result still needs interpretation.

The reader checked the 0x4c4f53af signature that identifies the compiled SOL format, then walked its material records. Comparing those reads with the compiled files gave us the ball start, goal, coin records and links between triangles and textures. The first course yielded 2,320 triangles and eighteen item records. Each triangle’s corners linked a vertex, a normal and a texture coordinate, telling us where the image belonged on the surface. We could recover the authored alignment instead of eyeballing the arrows on the floor.

ReAgent / understand level data
Full ReAgent workspace with chat on the left and the Worthify Decompiler’s native course-reader output on the right, highlighting the SOL signature 0x4c4f53af.
ReAgent’s chat opens the course reader at 0x17fc0 in the Worthify Decompiler. The selected comparison checks the SOL signature 0x4c4f53af; the adjacent answer identifies the version check and count reader.

One question in ReAgent was particularly useful: did material flag 0x200 add bytes to a record? The completed answer identified an extra four-byte integer and four-byte float, with the relevant reads linked. Miss those eight bytes and every later field lands in the wrong place. For a reverse engineer, the value is a specific answer tied to code. For a modder, it is the difference between a usable parser and a file full of plausible nonsense.

ReAgent / answer a parser question
Actual ReAgent chat beside Assembly with the optional integer and float reader calls highlighted
ReAgent connects the parser question to the instructions: flag 0x200 selects an integer read at 0x18a0f and a float read at 0x18a1f. Together they consume eight extra file bytes. The destination offsets belong to the memory record, so they cannot be used as file positions.

We wrote a Rust parser, simulation and renderer around those inputs. The artwork and authored course geometry came from the licensed original data. Recovering the format let the new engine use them; it did not make us the authors of the textures. Compiled-data method.

0.8 is not 80% opacity

Reading the right number is only half the job. The checkerboard ball had material alpha 1.0, or 100%, and texture alpha 127 out of 255, about 49.8%. Its material RGB coefficients were 0.8. Treating those color coefficients as “80% opacity” gives the renderer the wrong instruction. Rear and front drawing passes, with their own depth handling, also affect the finished image. A recovered value is useful once you know what consumes it.

Worthify / original technical illustration
Technical joke showing the difference between material RGB 0.8, material alpha 1.0 and texture alpha 127 divided by 255
Original illustration using recovered material and texture values. The texture percentage alone does not describe the finished ball’s appearance.

The teleport effect exposed another bad interpretation. We drew material flag 0x400 as triangles and joined its stars into enormous bright facets. An exploding lampshade is a memorable debugging aid. The executable draws small point sprites whose sizes change with distance. Its jump camera contracts the field of view toward the teleport midpoint, then opens it again. We also read destination X as the jump radius. Recovering the record layout, drawing operations and camera calculation corrected those mistakes. Rendering notes.

ReAgent / inspect camera behavior
Actual ReAgent chat and Assembly tracing field reads into the camera calculation, with the square-root instruction highlighted
The camera investigation follows field reads into a ratio, squared components and the square root at 0x1bd22. ReAgent keeps the chat explanation beside the actual instructions. The native lift is partial here; Assembly supplies the remaining trace, which we compared with camera behavior in the running reference.

This is the investigation loop ReAgent helped us follow: find the code behind a visible behavior, ask what a particular read or calculation means, exercise the reference, then compare our implementation. The camera view above matters because the camera moves. A still frame cannot tell you whether Chase, Lazy and Manual modes follow the right rules over time.

A filename is not a texture

Our first Rust build used licensed artwork directly. We went back to a fresh ReAgent project and Linux VM with a stricter question: could we recover the pixels without opening the original images? The recorded construction commands inspected the executable and runtime, leaving engine source, image files and the earlier Rust implementation unopened. The reference game loaded its installed data normally.

We asked ReAgent where those pixels enter OpenGL, the graphics interface used by this build. The chat found five calls to glTexImage2D, including the generic uploader at 0x104e0, and traced the separate image-decoding path. Those calls provide a useful place to observe decoded pixels. A string such as “back/gui.png” supplies a filename, not the picture itself. These game textures come from external installed resources. The executable supplies the loading and rendering behavior; running it supplies the decoded pixels.

That investigation also caught a mistake in our own rough disassembly listing. A fifteen-byte function at 0xe1c0 looked like part of the PNG reader because our sweep had lumped neighboring instructions together. ReAgent’s function records and the actual instructions identified it as a jump to strcmp, a string comparison. The decoder begins at 0xe1d0. Plausible labels make lousy evidence. Keeping the chat connected to the binary gave us a way to challenge them.

ReAgent / locate texture uploads
Actual ReAgent chat distinguishing observed texture-upload calls from incorrectly inferred function boundaries
The new investigation corrected its own rough function labels using ReAgent’s binary records and disassembly. This is a crop of the real chat, before the texture-loader decompilation completed. Function references remain available in the workspace.

Pixels still need rendering rules. The Worthify Decompiler’s native output for the uploader at 0x104e0 led us back to the instructions that set CLAMP_TO_EDGE on both texture axes and LINEAR filtering by default, with a conditional mipmap branch. We checked the numeric enum names against public OpenGL headers after the first chat answer mixed them up. Those are settings a Rust renderer can implement. Later material code may change them, so the uploader alone does not settle every surface’s final appearance.

ReAgent / recover rendering settings
Actual ReAgent chat links a texture uploader to its OpenGL edge and linear-filter settings
A focused renderer handoff in the real chat, linked to the inspected function. This crop shows its fixed upload-time settings; later material settings are a separate question.

Two capture-helper drafts failed before a separate coding agent wrote and tested the working helper. ReAgent’s chat compiled it, launched the instrumented game in its VM, inspected the desktop and read the capture records. The first version recovered 69 menu uploads and explicitly skipped sixteen grayscale-format uploads. After we added those formats, a fresh course-start run recovered all 97 uploads it observed, including fourteen grayscale or grayscale-with-alpha textures. That is a finite set of visited states, not the entire game’s artwork.

ReAgent / inspect captured pixels
Full ReAgent VM workspace showing the recovered checker texture beside its 512×256 RGBA8 readback metadata and uniform alpha 127 measurement.
ReAgent’s connected VM displays fresh texture event 23 beside its readback metadata: 512×256 RGBA8, 524,288 bytes, and alpha 127 at all 131,072 pixels. Upload and capture errors are both zero. The image keeps OpenGL row zero first, without a vertical flip.

The checkerboard ball is the useful test. Its 512 × 256 captured texture holds alpha at 127 across every pixel: 127 ÷ 255, or about 49.8%. The new extraction preserves that grayscale-with-alpha upload as ordinary RGBA pixels. After capture ended, a separate comparison against the licensed artwork identified the texture and its vertical orientation. We now use the recovered pixels in the Rust game’s normal ball-material loader. The public helper and exact-pixel fixtures make this part of the experiment inspectable.

Runtime capture / actual recovered pixels
Actual recovered checkerboard ball, grass, goal and coin-green-small.jpg pixels arranged with dimensions and alpha values
Actual decoded pixels from the running reference, arranged and scaled for this sheet. Asset identification happened after construction; captured rows are vertically flipped for display. The filenames identify the matching resources, not every material’s runtime role. Rust now loads the captured ball texture; other game resources remain licensed reuse.

Same keys. Now make it count.

Once the course looked familiar, we had a harder question: would the ball take the same route? We gave the original and Rust the same saved schedule of 368 key-down and key-up events. Input frames ran at 60 Hz, each spanning one or two fixed 90 Hz physics updates. The original game’s NBR replay recordings supplied positions to compare against. Rust started from the compiled level’s initial state and calculated its own movement. Each application rendered its own frames; we encoded the video at 30 fps and added labels outside the game pixels. Feeding those positions into Rust would have made a lovely video and a useless experiment.

Actual Linux Neverball beside native Rust on macOS, receiving the same recorded key schedule. Both collect ten coins and reach the goal. Each application renders its own frames; shared licensed textures, models and levels are disclosed. The run includes the goal celebration; rendering differences remain visible. Fixed logical time, 30 frames per second. Artwork © Robert Kooima, Jean Privat and Neverball contributors, GPL-2.0-or-later. Input transcript: Enter at 0.5 seconds; Manual camera at 2.67 seconds; arrow inputs from 3 seconds. The saved route supplies 368 exact key-down/key-up events, including releases.

Download the side-by-side video

The first diagonal run drifted 0.54 world units from the reference. We returned to the binary’s gravity and contact calculations. After corrections, the comparison measured zero distance between all 2,423 corresponding position samples on the completed ten-coin route, including the upward goal celebration. Coin counts match throughout, and both reach success in the same recorded sample. An earlier version crossed the goal one tick early; that difference is fixed, and its failed comparison stays in the record. A convincing GIF was not enough to catch it.

An elevator course made the problem less forgiving. We matched twenty coins and four switch toggles, yet drifted 0.677 world units after repeated bounces. The binary rounds a path duration to integer milliseconds, then multiplies by 0.001f32. Dividing by 1,000 changed the floating-point result. We also recovered how the engine samples the elevator’s path during contact and orders its collision arithmetic. With those corrections, the comparison measured zero distance across all 764 position samples on that run. Algebraically equivalent was not behaviorally equivalent here. Comparison report.

We also handed the compiled Rust comparator and recorded inputs to ReAgent in a separate VM. Its chat ran the comparison and inspected the result: 764 update groups, zero reported position distance and no coin-count mismatches, with twenty coins and four switch changes. The Rust state advanced from the course and key inputs; the original’s recorded positions served only as comparison targets. This route ended during play, so it says nothing about that course’s win or fall-out paths. A replay that merely assigns recorded positions would not answer the same question.

ReAgent / compare Rust behavior
Actual ReAgent chat displays measured Rust comparison results and switch, coin and impact events
ReAgent ran the independently advancing Rust comparator in its connected VM and inspected the output. The retained JSON reports zero distance on this finite route. This crop contains the measured table and events; it does not claim bitwise or whole-game equivalence.

The Rust implementation grew to include moving and rotating platforms, switches, growing and shrinking balls, jumps, three camera modes, Normal and Challenge play, progress, records, ball selection and replay management. All 183 packaged gameplay levels load and run idle simulation: 165 in seven ordinary sets, plus eighteen test levels. The initial independent input comparisons spanned nine courses, including actual teleport, shrink and elevator-switch events. One course was completed in that initial set of comparisons. Loading every level and matching those runs are useful results; they do not amount to exhaustive parity. Source, comparisons and remaining differences.

Give the ball a budget

The controller collected eight coins, then kept steering into a fence. ReAgent's memory investigation gave us a way to observe the failure in numbers. The ball-state probe defined for this ReAgent investigation reads position, velocity and radius; an executable-derived counter supplies the coins. A read-only adapter carries those observations into the controller while the original game keeps running.

ReAgent / probe live memory
Full ReAgent VM workspace after executing the scripted GDB probe, showing raw ball-state bytes and position, velocity and radius before and after one update.
A fresh scripted GDB probe runs in ReAgent’s connected VM. The terminal records position at ball+0x24, velocity at ball+0x30 and radius at ball+0x78 before and after one update. The script closes its game process after recording that update.

Worthify Decision One is our open model for choosing among actions an application supplies. Give it text state, a question and a menu of options, and it returns a selected option with scores conditional on that menu. Here, code filters candidate coins and teleporter destinations using recovered course geometry and current observations. Decision One selects a target; our navigation and feedback steering turn the choice into keys.

That division also keeps inference bounded. The steering loop runs in code, and Decision One is called when another target needs choosing. Single-candidate transitions and final goal navigation run directly in code. An earlier live teleporter run used 14 classifications and 8,055 input tokens in total; each request scored its menu in one forward pass, without generating a long text response. We ran the public weights locally, so this controller used our GPU time without a per-token API charge.

The longer routes exposed mistakes that a few key presses missed. One automatic transition targeted a coin hanging above the floor and kept steering beneath it until time ran out. Both engines reproduced the timeout. After we corrected the target filter, the earlier chat-launched live run finished with 41 coins against a goal of forty, with a median model round trip of 133 milliseconds. Together with the earlier live fenced course, the independent Rust comparisons cover 7,708 batches: both engines reach success on the same batch in each run, with maximum position separation about 0.0000011 world units and matching coin counts, radius and tilt. The recordings distinguish these live runs from earlier recorded-key comparisons. These routes put the reconstructed behavior under pressure; they do not cover every possible game state.

Rust / actual course and avatar views
Actual native Rust gameplay: Roundcoins floor reflection, Easytele teleport, Adventure shrink and Reactor avatar
Four actual native Rust views: Roundcoins’ reflective floor and elevator course, Easytele’s teleport effect, a shrunken ball falling in Adventure, and the layered Reactor avatar. These are standalone Rust frames, not paired original comparisons. Shared licensed geometry and artwork are disclosed; labels sit outside the game images.
Pop the hood: offsets, UVs and the bits that fooled us

ReAgent’s decompiled level reader exposed the SOL signature check and material-reading loops. We compared those reads with the compiled files, whose header and table counts located the records. Our extractor accounts for the entire 162,716-byte first-course file. File offsets below are zero-based; in-memory offsets in decompiled code describe a different layout.

One arrow triangle is a concrete example. Its 16-byte record at byte 133840 contains [9,1962,1963,1964]: material 9 and three corner-record indices. Each corner links a texture coordinate, normal and vertex. Resolving those links placed the arrow over the authored floor. The Rust renderer flips V to 1−V for its image coordinates and repeats the texture; that adaptation is our code, while the original UV values came from the file.

The executable was our only implementation-code input. Its generated decompilation and runtime observations were available, alongside separately disclosed licensed artwork and compiled level data. Neverball’s engine source and existing engine reimplementations remained withheld. Possible model prior knowledge remains a limitation; this was not an audited sealed clean room. Asset-authoring sources were obtained later solely to meet redistribution obligations.

Recovered values and independent rendering choices
ValueWhere it came fromWhat we used
Ball transparencyMaterial alpha 1.0; texture alpha 127/255 (49.8%).Recovered inputs; rear/front layer passes and depth handling affect the visible result.
Texture alignmentTriangle table byte 102096; 2,320 × 16-byte records.Per-corner UV/normal/vertex indices; our renderer adapts V to its image coordinates.
Ball / goalBytes 144556 / 144540: ball (1,0.2505,−1), radius 0.25; goal (1,0,−17), radius 0.75.Recovered setup, displayed floats rounded.
Coins / clock18 item records at byte 144180; level metadata fields goal=10 and time=9000.Runtime supplied the interpretation: ten coins and ninety seconds.
Camera50° FOV from the reference configuration; following and mode changes from binary calculations.Checked with the same recorded mode and movement keys.
Material colors / shineCoin diffuse (1,1,0.2,1); ball shininess 20; binary-derived light values.Recovered material and lighting inputs used in our renderer.
Rendering rulesBinary-derived shadow projection, floor-mirror passes and layered ball drawing.Recovered rules; HUD proportions and gradients are calibrated against reference frames.
Teleport effectBinary point-sprite flag 0x400; jump FOV = configured FOV × 2 × abs(elapsed − 0.5).Small stars and the contracting/reopening view; render interpolation can still shift a frame.
Original + Rust / recovered teleport rendering
Original Linux Neverball and native Rust during the same Easytele jump input frame
The original and Rust at input frame 260 in Easytele. The recovered radius field and point-sprite operation restore the beam and stars; the jump camera contracts the view. Client interpolation and visible particle differences remain. Each application rendered its own image from the retained input sequence.
Worthify / decoded level diagram
Schematic top view of 18 decoded coin records and the ball and goal coordinates in Neverball Easy01
An original Worthify diagram of the recovered item records, not a screenshot. Z runs horizontally and X vertically; height, surfaces and collisions are omitted. The compiled level supplied the positions, item values and goal radius.
Worthify / application evidence
Original Linux Neverball and native Rust intro and starting lane at 0.4 and 2.6 seconds
Genuine paired frames at 0.4 and 2.6 seconds. Both use the same compiled course setup. The independently rendered images remain intact; labels sit outside them.
Worthify / application evidence
Original Neverball and native Rust collecting coins and following the route at thirteen and twenty-two seconds
The route at thirteen and twenty-two seconds. Recovered geometry and UV links place the textures; each engine calculates movement and renders the view independently.
Worthify / application evidence
Original Neverball and native Rust reaching the goal and displaying results at 28.7 and thirty seconds
The goal and result at 28.7 and thirty seconds. Both engines collect ten coins and finish the course. Artwork © Robert Kooima, Jean Privat and Neverball contributors, GPL-2.0-or-later.

Rust is a language. Compatibility is the job.

Our ball now runs through code we can change. Other rewrites reach that freedom by different routes: a source port, a compatible interface or a reconstruction from an executable. Calling all three “rewrites” hides the evidence their authors had to work with.

From static evidence and runtime observations to a tested replacement
An explanatory diagram of the experiment. Static inspection and separately collected runtime observations constrain hypotheses; comparisons can still expose unobserved differences.

The useful question is what survives the move: a shell user’s habits, a script’s assumptions, a compiled animation or a multiplayer protocol. These projects tackle different parts of that problem.

Selected Rust implementations and their contribution to the article
ProjectWhat the authors builtWhy it belongs in this story
FishA shell migration from C++ to Rust, with source available.A strong maintenance example; access to implementation changes the task.
uutils coreutilsRust implementations targeting GNU command behavior.Compatibility includes exact output and exit codes; the project still documents differences.
RuffleA Rust Flash runtime for desktop and browser delivery.Keeps supported compiled Flash content usable; it does not recover the Flash authoring tool.
Mine Bombers ReloadedA Rust game engine from an author-described reconstruction.A niche reconstruction with original resources still required.
PumpkinA Rust Minecraft server implementation.Protocol and gameplay compatibility extend beyond visible screens; the project remains under heavy development.
FeatherAn unfinished Rust Minecraft server implementation.Its inactive, non-production-ready status limits adoption as a replacement server.

Mine Bombers Reloaded is the niche case with a personal reason to exist: a childhood DOS game brought back through a Rust engine, with original resources still required. It is a familiar reason to do unfamiliar engineering: wanting to play something again. The linked reconstruction account comes from the author; it is not an independent fidelity score.

Space Cadet Pinball’s reconstruction used executables and a public PDB, a debug file that supplies information a stripped executable lacks. Its engine still needs original game data. The repository and a Linux build discussion show the next problem after reconstruction: assembling dependencies so someone can actually play.

OpenLoco’s September 2025 release announced that every original function had been implemented and the remaining hooks removed. December’s release then removed dependence on the original memory-address layout. First recover the behavior, then shed the old architecture’s constraints. That second milestone opens up broader platform builds.

An engine you can meddle with

OpenMW independently implements an engine and editor for Morrowind. Its Lua interface gives modders another route into game behavior, and version 0.51 added custom magic effects. Engine-patch mods built for the original, including MWSE and MGE, do not transfer automatically. You still need the original licensed game content.

OpenRCT2 reimplements RollerCoaster Tycoon 2, whose original engine was written in x86 assembly. Its additions include cooperative multiplayer, broader platform support and editing tools. Rebuilding the engine creates room to change it, while the project still requires original game files. These are C++ engine examples, distinct from the Rust cases above.

REFramework takes a more surgical route: extend the running RE Engine game. Its Lua and native-plugin interface exposes objects, types and methods, so a modder can change one behavior while the existing engine supplies the rest. The hooking documentation also identifies native methods and wrappers that cannot be hooked through that API. Understanding the boundary makes the tool useful.

Rust takes a shot at Dust II

The Rust case is Frostty’s CSGO Rewrite, published as CS-Craft. Its Bevy client reads maps, models, textures and weapon data from the player’s CS:GO installation. The repository contains its own movement, collision and simulation code. The project describes local 5v5 bot matches with buying, bomb objectives and match results, and supplies a Windows release.

The interesting part sits below the Dust II screenshots. Movement defaults include gravity 800, friction 5.2, air acceleration 12 and an eighteen-unit step. Its simulation runs at 64 ticks per second. Familiar movement and game rules let the project combine that gunplay with streamed Minecraft terrain. Rush B. Bring a pickaxe.

Call it a playable prototype. The authors list multiplayer networking, grenades and wall penetration as unfinished; their CS:GO/MW2 combination remains a plan. They credit Claude for Frostty and describe a from-scratch implementation with no Valve code. Those provenance statements are the authors’ account. The code license also grants no rights to CS:GO’s assets.

The video below supplies a separate comparison: Gabe Follower’s earlier community recreation used S&box and Source 2. It is not footage of the Rust project. His retrospective describes weapons and animation work, then a request from Valve to stop. A recognizable game can survive an engine change; permission to deliver it is another part of the job.

Gabe Follower · February 22, 2023

I’ve Ported CS:GO to Source 2 myself… (Because Valve Didn’t)

Separate Source 2 recreation, not CS-Craft or Rust footage. Weapons at 4:49; animations at 7:01; the creator’s account of Valve’s request to stop at 8:46.

Watch on YouTube

Closed source can still leave a paper trail

Neverball gives us a shareable experiment. Its executable, running game and compiled data supplied the evidence. The broader point applies to what survives inside an executable: branches reveal decisions, arithmetic reveals calculations, and strings or imports can connect those calculations to files and dependencies. Decompiled names and types may remain uncertain. Enough useful evidence can survive compilation to support an independently written implementation of the behavior you need.

Obfuscation raises the cost. It can tangle control flow, remove helpful names and hide constants until execution. A working reference may still expose saved files, errors and state changes that narrow the possibilities. A binary branch can distinguish two explanations that look identical on the ordinary screen. What matters is which questions your evidence can answer.

Several implementations can satisfy the same finite observations. Rare errors, damaged files, timing, specialized hardware and states we never reach can break a replacement that passed the ordinary route. Our one-tick goal difference was small, measurable and fixable. It is also a good reason to avoid announcing that any software can now be reconstructed trivially.

Your server is private. Your behavior has an audience.

A SaaS product removes the server executable from the customer’s machine, but leaves an interface the customer can use. An authorized user can measure accepted inputs, returned outputs and state changes across a sequence of interactions. Public APIs, permitted client traffic and legitimate exports add detail. Those observations can support an independent implementation of a useful workflow. They do not reveal private datasets, the backend architecture or every production condition.

Spacebar implements portions of a Discord-compatible server interface. Its FAQ and self-hosting discussion expose gaps, including voice-related features. That is what makes the case useful: familiar endpoints get you somewhere, while an entire service includes more than endpoints. A connecting client demonstrates one compatible path through the service; it does not reveal Discord’s private production operation.

Then the original client changes. A replacement has to track the changing interface, or provide its own client and accept the migration work. Joel Spolsky’s Fire and Motion offers historical context for the time changing platform requirements can consume. Compatibility is ongoing work when the reference keeps moving.

A SaaS replacement can ace the demo and still lack abuse defenses, migration tooling or disaster recovery. Somebody has to run the service on its worst day. Our same-key game comparison could measure a particular route; a service operator needs to handle routes no customer has shown them yet.

Your compiler does not issue licenses

Getting the ball to move is an engineering result. Distributing the game is also a rights question. Our GPL release retains the artwork’s attribution, notices and preferred-form sources. Reconstructing behavior does not cancel copyright, contract terms or other applicable rights.

The U.S. Copyright Office distinguishes a game’s ideas and methods from protectable expression. That distinction is a reason to examine exactly what a replacement reproduces, not permission to copy every visual choice or distribute another creator’s assets. Copyright Office guidance.

In Tetris Holding v. Xio, the court examined copied Tetris expression in Mino. Independently written code did not settle the visual-expression question. The decision concerns those facts, rather than supplying a universal ban on similar mechanics or a blanket permission slip. “But I wrote it myself” is not the complete analysis.

The bnetd litigation adds a service-specific caution. The appellate opinion addressed contract and anti-circumvention issues involving Battle.net compatibility and authentication. “Interoperability” alone did not settle those facts. Davidson v. Jung.

Preservation accounts also make the ownership question personal. Stephen Brennan’s pinball essay argues for preservation while acknowledging the rights attached to the original art and data. He proposes source escrow as one possible response. Space Cadet Pinball on Linux.

The hard part starts after it runs

Putting the game in a browser gave us one more lesson. The first browser build downloaded 82MB just to assemble the level catalogue. We had reinvented the loading screen. The generated catalogue is now 83kB; choosing a level loads that course’s geometry. The ball was already behaving. Delivery still needed work.

The clouds also sprouted red and black gaps in a Mac WebKit run. Requesting an opaque WebGL canvas removed them while leaving the ball’s translucent surface intact. The fix belonged in the browser loader, another part of making the replacement usable.

Feather’s Rust Minecraft server repository says development is inactive and the project is not production-ready. Ruffle’s Rust runtime keeps supported compiled Flash content usable on desktop and in browsers, while its project documents compatibility limitations. A developer’s firsthand Reddit reaction explains why preservation mattered. Neither changing language nor attracting enthusiasm supplies all the maintenance a replacement needs.

RustDesk’s self-hosted setup needs an ID/signaling server and a relay for connections that cannot be made directly. The operator must deploy and maintain them. Supabase’s independently designed backend offers another workflow alternative; its self-hosting guide assigns configuration, security and updates to the operator. Neither case recovers TeamViewer’s or Firebase’s private implementation. Both show how much delivery remains after obtaining code. RustDesk architecture, Supabase self-hosting.

Suppose you inherit a reporting utility whose vendor has vanished. A replacement needs to handle the empty files, duplicate records, missing fields and interrupted exports that your organization encounters. Some quirks may be bugs that downstream scripts depend on. Keep the original outputs, decide which behavior matters, and provide an exit path if the replacement fails. That is a job a buyer can evaluate and a provider can deliver.

Cheaper reconstruction could move more spending toward migration, deployment and continued operation. Buyers still need someone to import their data, preserve important workflows, patch dependencies, track changing interfaces and explain failures. Specialized hardware, scarce data and private production conditions can keep reconstruction difficult. The opportunity is to deliver dependable outcomes as more of the underlying behavior becomes reproducible.

Go back to the game and change the camera. That small setting leads to calculations in an executable, a question in ReAgent and another behavior the replacement must get right. Once you can follow that chain, “closed source” stops meaning “nothing to learn.” The next question is who can turn what you’ve learned into software people can keep using.

Rust logo © Rust Foundation, CC BY 4.0, reproduced unmodified from Rust artwork.

With your permission, Google Analytics cookies measure page visits and referring websites to help us decide which content to improve. Google receives this usage data. Analytics stays off until you allow it. Change your choice anytime in Cookie settings. Privacy and cookies