An in-progress open source implementation of "The Incredible Machine" and "The Even More! Incredible Machine".
This is a fork of mrfixit2001/OpenTIM, originally written by Danny Spencer. The upstream project reverse-engineered the simulation from the 16-bit Windows build of TEMIM; all of that work is theirs.
This fork exists to get the project building and running again on a modern toolchain (Apple Silicon in particular), and to carry it forward. See Changes in this fork.
This repository contains no game data, and never will. OpenTIM is an implementation of the game engine only. To run it you must supply the original data files from a copy of the game that you legally own — an original disc, or a purchase from a digital storefront that sells it.
Do not commit game assets to this repository, and do not ask for copies of them
in issues or pull requests. The game-data/ directory is gitignored for exactly
this reason.
Struct layouts are generated from the CSVs in structures/ and are not checked
in, so generate them before the first build:
mkdir -p c_src/generated src/generated
uv run --no-project python scripts/generate-structs.py > c_src/generated/structs.h
uv run --no-project python scripts/generate-structs.py --rs > src/generated/structs.rs(The generator is stdlib-only; plain python3 works just as well.)
Then:
cargo build
cargo testPut your game files somewhere under game-data/, e.g. game-data/tim1
containing RESOURCE.MAP and RESOURCE.00*:
cargo run -- game-data/tim1 L6.LEV --playWindow controls: Space run/pause, B collision-border overlay, S screenshot, G graphviz dump, Esc quit.
Other modes:
opentim <game-dir> --list-resources # list the archive index
opentim <game-dir> --extract <NAME> <out-file> # extract a raw archive payload
opentim <game-dir> --dump-images <dir> [filter] # decode sprites to PPM
opentim <game-dir> <level> [ticks] # headless: load, step, dump parts
opentim <game-dir> <level> --screenshot <out.ppm> [ticks] [--borders]A level is either a saved machine on disk (CATOMATC.TIM) or the name of a
puzzle inside the archive (L6.LEV).
OpenTIM also runs in the browser. The simulation is the same C core cross-compiled to wasm32, and it produces bit-identical results to the native build.
Prerequisites:
rustup target add wasm32-unknown-unknown
cargo install wasm-bindgen-cli --version 0.2.126
brew install zig # Apple clang has no WebAssembly backendThen:
./scripts/build-web.sh # writes web/pkg
python3 -m http.server -d web 8080Open http://localhost:8080/ and drop in your own game folder — the one holding
RESOURCE.MAP and RESOURCE.00*. Nothing is uploaded; the files are read in the browser
and the whole game runs locally.
web/test-auto.html is a development harness that fetches the game data over HTTP instead
of asking for a drop, so the browser path can be checked without clicking. Serve the repo
root rather than web/ to use it, and put the data in game-data/tim1.
The asset pipeline and level parsing are complete and verified against the original DOS data. The simulation core runs. What is missing is part behaviour and the entire editing UI.
| Area | State |
|---|---|
| Asset pipeline (LZW/LZHUF, BMP/SCN, VGA palette) | Complete — all 484 TIM 1 sprites decode |
| Level parsing | Works — titles, objectives, gravity, parts |
| Simulation core | Works — gravity, collision, bounce |
| Part behaviour | 28 of 66 parts implemented |
| Levels | 7 of the 87 shipped puzzles load and simulate |
| Browser build | Works, bit-identical to native |
| Editing / design mode | Not started — see docs/specs/ |
| C-to-Rust port | Wave 2 complete — 54 of 92 legacy C functions (1,180 of 3,392 lines) moved to Rust; 38 functions / 2,212 lines remain in c_src/main.c, part_defs.c and draw_rope.c. See docs/specs/2026-07-22-c-to-rust-port-design.md. |
Levels that fail do so only because they contain parts that are still
unimplemented(). Missing parts cluster by theme: everything electrical, all
weapons and pyrotechnics, and most characters. The playable seven are
L6, L20, L21, L24, L25, L31 and L79; the browser build offers only
those, and parts::is_implemented is what decides. That list is checked against
reality by a test which creates every part type under catch_unwind, so it cannot
go stale as parts get ported.
"The Incredible Machine 2" (TIM2.EXE, 1994) is not supported. Its levels
use magic 0xACEF against TIM 1's 0xACED and carry extra fields.
- Builds on modern Rust and on Apple Silicon. The pinned 2020 nannou master
pulled
winit 0.22, which cannot compile foraarch64-apple-darwin. - The renderer is now a software rasterizer (
src/render.rs) drawing into a 640x480 framebuffer viaminifb, which suits a palette-indexed 2D sprite game and builds in seconds. The old nannou renderer is kept behind theguifeature for reference and does not build. - Levels can be loaded directly out of
RESOURCE.MAPrather than only from loose files on disk. - Added
--list-resources,--extract,--dump-imagesand--screenshot. - Fixed a null-pointer dereference on unlinked pulleys, and an integer underflow on zero-height walls (the original truncated to a byte and wrapped).
- Added a WebAssembly build (upstream listed this as a stretch goal). The C core is
cross-compiled with
zig cc, freestanding wasm has no libc sosrc/wasm_libc.rssupplies the handful of symbols the core needs, andsrc/web.rsdrives a canvas from a requestAnimationFrame loop. Verified bit-identical to native across every loadable level. - Added
CLAUDE.mdwith architecture notes, and a design spec for the editor indocs/specs/.
Debug and release builds once simulated differently — balloons diverged on the same
machine purely from -O0 versus -O2. The cause was not undefined behaviour in the C, as
first assumed, but an ABI mismatch at the FFI boundary: 22 functions were exported with
#[no_mangle] pub fn, giving them the Rust ABI, while C called them as C functions. The
Rust ABI is explicitly unspecified, so how a small integer return lands in the register is
a codegen decision that changes with optimisation level.
The visible symptom: part_acceleration() returns −198 for a balloon, but the C caller
testing part_acceleration(part->type) < 0 read it as non-negative at -O0 and took the
wrong branch when snapping a resting part's sub-pixel position.
Every #[no_mangle] export now declares extern "C". Debug and release agree across all
loadable levels, and the wasm build agrees with both. When adding an export, always give it
extern "C" — without it the code will appear to work and then diverge under optimisation.
Inherited from upstream:
- Run on desktop platforms (Windows, Mac, Linux). Requires the user to provide the original game assets.
- WebAssembly is a stretch goal. Not actively targeted, but kept in mind.
- Simulation should be as accurate to the original game as possible.
- Keep simulation quirks. Bugs carried over from the original may be fixed if there is reasonable consensus that they are disruptive or game-breaking.
All deliberate deviations from the original game's behaviour are labelled
VANILLAFIX. Comments of the form /* TIMWIN: 10a8:1e46 */ give the
segment:offset of the corresponding routine in the original executable — keep
them when moving code, they are the cross-reference to the disassembly.
See CLAUDE.md for a fuller architecture guide.
GPL-3.0, as upstream.