***Disclaimer: This is what those in the decomp/recomp community would call an "AI Slop" recomp. It is almost entirely LLM-generated by a mix of Claude Fable, Opus 5, and Sonnet.*** As a career Software Engineer, I think it's remarkable that an LLM can leverage the amazing tools built by the retro reverse-engineering community to derive a static recompilation of an N64 game. However, this project has not been built, reviewed, or endorsed by anyone with relevant expertise in the area. I can make no guarantees of the quality, accuracy, stability, or security of the code in this repository, since I did not write it, and I lack the knowledge to properly review it. This was merely an exercise in curiosity to see if I could get an LLM to pop out a PC port of an obscure N64 title I remember from my childhood. I hope to some day develop the skills necessary to create a proper decomp project. For examples of decomps/recomps that *were* built by communities of experts, check out: - [Starship](https://github.com/HarbourMasters/Starship) - [Ghostship](https://github.com/HarbourMasters/Ghostship) - [Snowboard Kids 2: Recompiled](https://github.com/cdlewis/snowboardkids2-recomp/) - [Zelda64Recomp](https://github.com/Zelda64Recomp/Zelda64Recomp) - [Starfox64Recomp](https://github.com/sonicdcer/sf64) # Knife Edge Recompiled A static-recompilation PC port of *Knife Edge – Nose Gunner* (N64, Kemco, 1998), built on [N64Recomp](https://github.com/N64Recomp/N64Recomp) / [N64ModernRuntime](https://github.com/N64Recomp/N64ModernRuntime) and [RT64](https://github.com/rt64/rt64) — the same toolchain used by [Zelda64Recomp](https://github.com/Zelda64Recomp/Zelda64Recomp). Knife Edge's N64 machine code is translated to C ahead of time (no emulation), linked against a runtime that HLEs the OS/libultra layer, and rendered through RT64's modern graphics backend. ## Status Playable and stable. Boots through the KEMCO logo, title screen and attract loop into the menus and gameplay — verified on real hardware through stage 4 including an alternate stage path, with correct music and sound effects, correct game speed, and working results/stage-overview screens. No known defects. Not yet verified: full campaign completion, and high-score persistence across runs (analyzed and almost certainly absent — which is *faithful*, since the game only probes for a Controller Pak; see `analysis/docs/enhancements.md`). All planned enhancements are built: high resolution, widescreen, full height, HUD relocation, high framerate (experimental), plus gamepad and mouse-aim input support and Flatpak packaging. `enhanced` is the default profile; `vanilla` is the regression baseline (see "Configuration" below and `analysis/docs/enhancements.md`). Development notes, working practices and a documentation index live in `CLAUDE.md`. ## Repository policy What this repo contains is the **analysis pipeline and its metadata**, not build artifacts: - `analysis/` — the scripts that derive symbols from a ROM (function boundary detection, libultra identification, spin-loop detection). - `config/*.toml` — the symbol/section tables those scripts produce. This is the reviewable source of truth: a change here is human-meaningful. - `src/` — the runtime shim (window, renderer, audio, input, ROM provisioning). Deliberately **not** committed: - **The ROM, and any asset or data dump taken from it.** Knife Edge – Nose Gunner is © 1998 Kemco; you need your own legally-obtained dump to build a playable binary or to regenerate anything. - **Recompiler output** (`generated/`). It is a build artifact derived mechanically from the ROM, so it is regenerated rather than stored — see "Regenerating from a ROM". Two consequences worth knowing: **Normal CI needs no ROM and no secrets.** With no ROM available, the build compiles `src/stub_game/` instead of recompiled game code, so `.github/workflows/build.yml` still validates the runtime shim, RT64, librecomp, ultramodern, audio and input on every push — and works for pull requests from forks, which cannot receive secrets. **Release binaries contain recompiled code but no game data.** The player supplies their own ROM at runtime and the game reads assets, audio and level data directly out of it, exactly as it did on hardware — there is no asset extraction step. The `linux-build` workflow job asserts this via `scripts/assert_no_rom_assets.py`, which samples the ROM's data regions and fails if any of those bytes appear in the binary. ## Building ### Option A: devcontainer (recommended) Open the repo in the provided devcontainer (`.devcontainer/devcontainer.json`). Its `postCreateCommand` installs the full toolchain, sets up a Python venv for the analysis pipeline, and initializes submodules. Then: ```sh git submodule update --init --recursive # if not already done cmake -B build -G Ninja \ -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_BUILD_TYPE=Release \ -DKE_ROM="/path/to/Knife Edge - Nose Gunner (USA).n64" cmake --build build ``` The resulting binary is `build/KnifeEdgeRecompiled`. (Any build directory name works — local development in this repo has also used `build-shim`; CI uses `build`.) `KE_ROM` makes CMake normalize the ROM, run the analysis pipeline, and invoke N64Recomp/RSPRecomp at configure time; the results land in `build/generated/`. Omit it to build against `src/stub_game/` instead — a placeholder that links and runs but has no real game code, useful for build-system iteration and for CI without a ROM. (`-DKE_GENERATED_DIR=` still works if you have recompiler output from elsewhere.) ### Option B: native deps Install (Debian/Ubuntu package names — matches `.devcontainer/devcontainer.json`'s `postCreateCommand`): ```sh sudo apt-get install -y cmake ninja-build clang lld pkg-config \ libsdl2-dev libx11-dev libvulkan-dev vulkan-tools \ glslang-tools spirv-tools mesa-vulkan-drivers xvfb ``` (`xvfb` and a Vulkan software rasterizer like `mesa-vulkan-drivers`' lavapipe are only needed for headless/CI runs without a real GPU/display.) Then clone with submodules and build as in Option A: ```sh git clone --recurse-submodules cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ \ -DCMAKE_BUILD_TYPE=Release -DKE_ROM="/path/to/knife_edge.n64" cmake --build build ``` Windows has a native CMake branch and CI jobs (see Option E below); first CI runs are pending. macOS should work per the top-level `CMakeLists.txt`'s platform branches (this mirrors Zelda64Recomp's supported set) but remains unexercised — Linux is the actively-tested platform (see `analysis/docs/build-notes.md`). ### Option C: containerized (any machine with docker or podman) No local toolchain install at all — everything (clang, cmake, ninja, SDL2, Vulkan, the analysis pipeline's Python venv, ...) lives in `containers/Containerfile`, built and run for you by one script: ```sh git submodule update --init --recursive # if not already done ./scripts/container_build.sh --rom "/path/to/Knife Edge - Nose Gunner (USA).n64" ``` Output lands in `build-container/KnifeEdgeRecompiled` — run it on the host exactly as in "Running" below, no container involved at runtime. Omit `--rom` to build against `src/stub_game/` instead, same as Options A/B. This is exactly what CI runs (`.github/workflows/build.yml` calls the same script), so a container build is the closest thing to a guaranteed-green build available locally. See `./scripts/container_build.sh --help` for the rest of its flags (`--check`, `--smoke`, `--shell`, `--exec`, `--jobs`, `--rebuild-image`), and `analysis/docs/build-notes.md`'s "Containerized build" section for the design. ### Option D: Flatpak Needs recompiled game code first (Option A/B/C above with `-DKE_ROM=...`, or `./scripts/container_build.sh --rom ...`) so `generated/us/` exists, plus `flatpak-builder` on the host: ```sh ./scripts/build_flatpak.sh --install ``` This builds `KnifeEdgeRecompiled.flatpak` in the repo root and installs it for the current user (drop `--install` to just produce the bundle; see `./scripts/build_flatpak.sh --help` for `--bundle ` and `--runtime-install`, which fetches the `org.freedesktop.Platform`/`Sdk`//25.08 runtime this manifest needs if you don't have it yet). Then: ```sh flatpak run io.github.haydenmc.KnifeEdgeRecompiled ``` First run opens a portal file-open dialog to pick your ROM (no ROM ships in the bundle, and none is downloaded) — the sandbox has **no filesystem permissions at all**; the portal is what lets you point it at a file from outside the sandbox for that one pick. App data (config, the normalized ROM cache) lives at `~/.var/app/io.github.haydenmc.KnifeEdgeRecompiled/data/knife-edge-recompiled/`, so it persists across app updates. See `analysis/docs/build-notes.md`, "Flatpak packaging", for the manifest's design and `packaging/flatpak/io.github.haydenmc.KnifeEdgeRecompiled.yml`'s `finish-args` for the exact sandbox permissions (wayland/X11/pulseaudio sockets, DRI, IPC — no filesystem, no network). ### Option E: Windows (clang-cl, native) Install Visual Studio 2022 Build Tools (or VS with the C++ workload), which provides CMake ≥3.20, Ninja, and LLVM/clang-cl — all available via the Visual Studio installer. Python 3 with rabbitizer, capstone, and tomlkit is needed for ROM-based builds: ```sh pip install rabbitizer capstone tomlkit ``` **CRITICAL:** Before cloning, run these **once** (the pinned-submodule patch is LF-only and configure fails if autocrlf mangles it; long paths are needed by the deep build tree): ```sh git config --global core.autocrlf false git config --global core.longpaths true ``` Then clone with submodules and build the host tools: ```sh git clone --recurse-submodules cmake -S deps/N64Recomp -B deps/N64Recomp/build -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build deps/N64Recomp/build --target N64RecompCLI RSPRecomp ``` From a **Visual Studio x64 Developer Prompt** (not a plain cmd.exe; open it from the Start Menu or via `vcvarsall.bat x64`), configure and build: ```sh cmake -B build -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl "-DCMAKE_CXX_FLAGS=-Xclang -fexceptions -Xclang -fcxx-exceptions" -DKE_ROM="" cmake --build build ``` SDL2 and dxc DLLs are staged next to the exe automatically. App data (config, normalized ROM cache) lives in `%LOCALAPPDATA%\KnifeEdgeRecompiled` (override with `KE_DATA_DIR` if needed). ## Running You need your own dump of *Knife Edge – Nose Gunner (USA)* (`.z64`, `.v64`, or `.n64` byte order — all three are accepted). ```sh ./build/KnifeEdgeRecompiled --rom /path/to/knife_edge.z64 ``` The first run with `--rom` validates the dump (hash-checked against the known-good ROM) and stores a normalized copy under a `ke_recomp_data/` directory next to wherever you run the binary from. Subsequent runs can omit `--rom` and will reuse the stored copy. Running without `--rom` and without a previously stored ROM prints a clear error and exits (this is what CI's smoke test checks — see below). ### Controls Keyboard, and any SDL-recognized game controller — both work at once, and a gamepad can be hot-plugged in or out at any time. **Keyboard and mouse are always N64 controller 1** (port 0). Pads are handed ports in the order they connect, starting at `pad_start_port` (default `0`), each new pad taking the lowest free port from there; a pad keeps its port until it is unplugged, and unplugging frees that port for the next connection. With the default, a single pad also lands on port 0 and merges with the keyboard and mouse — the same "whichever controller is in hand works" behavior as before — while a second and third pad become controllers 2 and 3. See "Multiplayer" below for the two-player recipe. | N64 input | Key | |---|---| | A | X | | B | Z | | Z | Left Shift | | Start | Enter | | D-pad Up/Down/Left/Right | Arrow keys | | C-Up / C-Down / C-Left / C-Right | I / K / J / L | | L / R shoulder | Q / E | | Control stick | W A S D | | N64 input | Gamepad | |---|---| | A | A | | B | B | | Z | Either trigger | | Start | Start | | D-pad Up/Down/Left/Right | D-pad | | C-Up / C-Down / C-Left / C-Right | Right stick | | L / R shoulder | Left / right bumper | | Control stick | Left stick | Custom gamepad mappings can be supplied the standard SDL way, via the `SDL_GAMECONTROLLERCONFIG` environment variable. Mouse aim is also available, on by default. Click anywhere in the window to capture the pointer; Esc releases it. While captured, mouse motion steers the same control stick as the keyboard/gamepad (summed with them, then clamped), and mouse buttons map to N64 buttons: | N64 input | Mouse | |---|---| | Z | Left click | | A | Right click | | B | Middle click | There are two ways mouse motion can be turned into aim, chosen with the `[input]` table's `mouse_mode` key: - `positional` (default) — the reticle follows the mouse **1:1 and absolutely**: move the mouse an inch and the reticle moves the corresponding distance, then stops there. This is a closed loop on the game's own reticle position, so it needs no drift correction and never overshoots. `mouse_sensitivity` scales how far the reticle travels per unit of mouse movement. - `velocity` — mouse *speed* maps to stick deflection, so the reticle keeps drifting for as long as the mouse keeps moving. This was the original mapping; it is kept because it needs no knowledge of the game's state at all. `mouse_invert_y` (off by default) inverts the vertical axis in both modes. Off means mouse-up aims up; on restores the game's own flight-style inverted aim. Turn mouse aim off entirely with `mouse_aim` — see "Configuration" below. The full reverse-engineering behind positional mode is in `analysis/docs/mouse-aim.md`. One honest caveat that applies to both modes: the game samples input once per game frame (~15 Hz, same as the pad — see below) and moves the reticle by a fixed amount per frame, so the reticle updates at that same game rate no matter how smoothly the mouse itself moves. That's inherent to the current build, not a bug in the mouse handling; fully smooth aim is coupled to the (not yet started) high-framerate enhancement. The game samples the controller once per game frame (~15 Hz — see `analysis/docs/timing-and-mission-debug.md`), the same as on console, so a key has to be held for a frame to register. The window disables SDL's text input so a desktop input method cannot swallow those holds; if your desktop still intercepts held keys, try running with `XMODIFIERS=@im=none QT_IM_MODULE= GTK_IM_MODULE=` to take the input method out of the path entirely. Analog-stick deadzone, response curve, and sensitivity can be tuned via the `[input]` table — see "Configuration" below. ### Multiplayer Knife Edge has four-player TEAM (co-op) and BATTLE (team versus) modes on the GAME MODE SELECT carousel. The game only lets you pick them when it sees at least **two controllers**, and it checks once at startup — so plug pads in before launching, or tell it how many ports to report with `ports`. Two `[input]` keys cover this (full table under "Configuration" below): - `ports` — how many controller ports to report. `0` (default) is automatic: port 0 always, ports 1–3 while a pad is assigned to them. Setting it to `2`–`4` reports that many regardless of what is plugged in; the ports with nothing on them simply never press anything, which is enough to enter a multiplayer game and play it with fewer humans than players. - `pad_start_port` — which port pads start claiming from. `0` (default) merges the first pad with the keyboard and mouse on controller 1. **Two humans, one keyboard and one pad:** ```toml [input] pad_start_port = 1 # keyboard + mouse stay P1; the pad becomes P2 ports = 2 # report 2 controllers so TEAM/BATTLE are selectable ``` `ports` can be left at `0` there if the pad is genuinely plugged in before launch — it is only needed when you want the game to believe in a controller that is not there. In TEAM mode each player gets their own coloured reticle, and the colour is fixed by port: **P1 green, P2 red, P3 yellow, P4 magenta**. All four gun for the same aircraft, so there is one shared health dial. Multiplayer is newly reachable in this port and is still being worked through (`analysis/docs/multiplayer.md` has the details, including two known cosmetic HUD faults in BATTLE mode on the `enhanced` profile — `vanilla` is correct in every mode measured). ## Configuration Settings live in `/config.toml` (the `ke_recomp_data/` directory mentioned above, next to wherever you run the binary from), created with commented defaults on first run. It selects a **profile**: - `enhanced` (default) — a curated set of quality-of-life changes: currently `input_latching` (see Controls above), `high_resolution` (renders the 3D scene at window resolution through RT64 instead of native 320x240), `widescreen` (expands the 3D field of view to the window's aspect ratio instead of 4:3-pillarboxing it), `full_height` (removes the in-mission 20-line letterbox, revealing the full 320x240 frame), `hud_relocation` (re-anchors the in-mission HUD to the true window corners that the previous two open up — health cluster bottom-left, S-BOMB gauge bottom-right, each keeping its original margin — instead of leaving it inside the centered 4:3 column), and `extended_aim` (lets the aiming reticle reach the whole visible viewport instead of stopping at the rails it had for a 4:3 / 320x200 view — ±170 instead of ±128 horizontally at 16:9, ±100 instead of ±84 vertically; the shot goes where the crosshair is out there too, the game's aim math needs no correction for it). - `vanilla` — every enhancement off, faithful to original hardware behavior. This is the regression baseline every measurement and bug investigation in `analysis/docs/` assumes unless stated otherwise. - `custom` — reads individual `[enhancements]` flags from the file. One additional flag exists outside every curated profile: `high_framerate` (**experimental**) renders at the display's refresh rate via RT64 frame interpolation while game logic keeps its original paced rate. It works — motion is visibly smoother — but has known visual artifacts on some effects (the lock-on effect and bullet trails warp/smear, a limitation of RT64's heuristic frame matching; see `analysis/docs/high-framerate.md`). To try it, set `active = "custom"` and enable it (plus whichever enhanced-set flags you want) under `[enhancements]`. Two CLI flags, and nothing else config-related: ```sh ./build/KnifeEdgeRecompiled --rom /path/to/knife_edge.z64 --profile vanilla ./build/KnifeEdgeRecompiled --config /path/to/other-config.toml ``` `--profile` overrides `[profile] active` from the file; `--config` points at a config file somewhere other than the default location. A missing file is created with defaults; a malformed one falls back to vanilla defaults with a warning on stderr rather than failing to start. The active profile and any non-default flags are logged on startup right after the build stamp, so a bug report identifies its config along with its build. See `analysis/docs/enhancements.md` for the full policy, the current flag table, and how future enhancements get added. The `[input]` table tunes how the host analog stick maps to the N64 stick range. It applies in every profile, vanilla included — it's host input-device shaping, not a change to game behavior, so it isn't gated by `--profile`. Out-of-range values are clamped to the ranges below, with a warning on stderr. | Key | Default | Range | Meaning | |---|---|---|---| | `stick_deadzone` | `0.15` | `0.0`–`0.9` | Radial stick travel around center that's ignored. | | `stick_curve` | `1.0` | `0.25`–`4.0` | Response exponent applied after the deadzone rescale; `>1.0` gives finer control near center without changing full deflection. | | `stick_sensitivity` | `1.0` | `0.1`–`3.0` | Multiplier applied after the curve; `>1.0` reaches full deflection before the stick is maxed out, `<1.0` caps below full deflection. | | `mouse_aim` | `true` | boolean | Master switch for mouse aim (see Controls above). `false` disables capture-on-click entirely. | | `mouse_sensitivity` | `1.0` | `0.05`–`20.0` | Multiplier on mouse-derived aiming: reticle travel per unit of mouse movement in `positional` mode, stick deflection per unit of mouse *speed* in `velocity` mode. | | `mouse_mode` | `"positional"` | `"positional"` \| `"velocity"` | How mouse motion becomes aim — 1:1 absolute reticle following, or mouse-speed-to-stick drift. See Controls above. | | `mouse_invert_y` | `false` | boolean | Inverts the vertical mouse axis in both modes. Off is mouse-up = reticle-up; on restores the game's own flight-style inverted aim. | | `pad_start_port` | `0` | `0`–`3` | Which N64 port pads start claiming, lowest free port first, in connection order. `0` merges the first pad with keyboard and mouse on controller 1; `1` keeps controller 1 keyboard-only and makes pads P2–P4. See "Multiplayer" above. | | `ports` | `0` | `0`–`4` | How many controller ports to report to the game. `0` = automatic (port 0 always, ports 1–3 while a pad is assigned there). `2`–`4` reports that many whatever is plugged in, which is what makes the TEAM/BATTLE modes selectable with fewer pads than players. The `KE_FORCE_PORTS` environment variable overrides this. | ## Regenerating from a ROM Recompiler output is not committed, so a playable build always regenerates it. Passing `-DKE_ROM=` to CMake does all of this for you; the manual recipe below is what that automates, and is what you want when iterating on the analysis pipeline itself: 1. **Build the one-time tools** (if you haven't already): ```sh cmake -S deps/N64Recomp -B deps/N64Recomp/build -G Ninja \ -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ ninja -C deps/N64Recomp/build N64Recomp make -C deps/n64sym # symbol-identification oracle used by stage 2 below ``` 2. **Ingest your ROM** into the normalized form the pipeline expects: ```sh .venv/bin/python analysis/byteswap.py /path/to/your/rom build/knife_edge.z64 ``` This accepts z64/v64/n64 byte order, verifies the dump's md5 against the known-good Knife Edge (USA) hash, and writes a normalized big-endian z64 to `build/knife_edge.z64` (outside the repo tree — never committed). 3. **Run the analysis pipeline**: ```sh make -C analysis ``` This runs `find_functions.py` → `ident_libultra.py` → `gen_syms.py` (see `analysis/docs/pipeline-spec.md` and `pipeline-report.md`) and (re)writes `config/knife_edge.us.syms.toml` and `config/knife_edge.us.toml`. 4. **Run N64Recomp** (and `RSPRecomp` for the audio microcode). Output goes wherever `output_func_path` in `config/knife_edge.us.toml` points — a build directory, never the repo: ```sh deps/N64Recomp/build/N64Recomp config/knife_edge.us.toml deps/N64Recomp/build/RSPRecomp config/aspMain.us.toml ``` 5. **Check the metadata is unchanged**: ```sh git diff --stat -- config ``` No diff means the pipeline is reproducible. A diff means the pipeline changed behavior — review it, since `config/` is the reviewable record of what the analysis concluded. 6. Rebuild `KnifeEdgeRecompiled` against the regenerated output as in "Building" above. `.github/workflows/build.yml` has a `regen-verify` job that automates this recipe and fails if step 5 finds a diff. Unlike the rest of this section (which assumes you're supplying your own local ROM), that job runs on a plain hosted `ubuntu-latest` runner: it fetches an age-encrypted ROM from owner-configured private storage and decrypts it in-run using repo secrets (see analysis/docs/build-notes.md, "Encrypted ROM in CI (Backblaze B2)" for the full design and setup). It runs on every push to `main`, every same-repo pull request (so config/ drift is caught before merge, not after), and manual dispatch. `linux-build` and `flatpak-build` fetch the ROM the same way and run on the same events plus version-tag pushes, producing a downloadable Linux tarball and Flatpak bundle artifact respectively on every one of those runs (PR, push to `main`, tag, or dispatch) — not just tagged releases. A `v*` tag push additionally triggers `publish`, which attaches those two artifacts from the same run to the GitHub release for that tag (creating it if it doesn't exist yet); `publish` is the only job in the workflow with write permissions and does no building of its own. All ROM-needing jobs skip automatically wherever the ROM secrets aren't configured — forks and their pull requests still need no ROM and no secrets, unchanged from the policy above. ## Architecture - `CMakeLists.txt` — top-level build; wires up RT64 + N64ModernRuntime (`ultramodern`/`librecomp`) + the `RecompiledFuncs` static lib (either the regenerated output or `src/stub_game/`) into the `KnifeEdgeRecompiled` executable. See `analysis/docs/build-notes.md` for how it was put together, including dependency quirks and what was and wasn't kept from Zelda64Recomp's reference implementation. - `src/main/` — runtime entry point (`main.cpp`), RT64 renderer wiring (`rt64_render_context.{h,cpp}`), overlay registration (`register_overlays.cpp`), the RCP frame-time model that sets the game's speed (`rcp_timing.cpp`), small platform helpers (`support.{h,cpp}`), the enhancement-flag/profile config loader (`config.{h,cpp}` — see "Configuration" above and `analysis/docs/enhancements.md`). - `src/stub_game/` — placeholder game code used when no ROM/generated code is unset, so the executable always links and runs even without a ROM-derived build. - `analysis/` — the ROM → symbols pipeline (Python): `find_functions.py` (function boundary detection), `ident_libultra.py` (libultra/OS function naming via the vendored `n64sym` tool plus a curated override table), `gen_syms.py` (emits the `config/*.toml` N64Recomp consumes). Full pipeline design in `analysis/docs/pipeline-spec.md`, results in `analysis/docs/pipeline-report.md`. - `analysis/docs/n64recomp-formats.md` — reference for the N64Recomp input TOML formats and output contract this project relies on. - `analysis/docs/overlay-tracking.md` — how Knife Edge's own code-overlay loader is hooked so librecomp's function-lookup tables stay correct across overlay swaps. - `analysis/docs/boot-debug.md` — the crash-by-crash debugging log from first boot to a stable title/attract loop; the best single source for "what does and doesn't work yet." - `analysis/docs/timing-and-mission-debug.md` — why the game ran 4x too fast and why starting a mission hung, with the measurements and backtraces; also documents `KE_PERF` (frame-rate instrumentation) and `KE_RCP_FRAME_MS` (the RCP frame-time budget), and the headless recipe for driving the menus. ## Credits - [N64Recomp](https://github.com/N64Recomp/N64Recomp) and [N64ModernRuntime](https://github.com/N64Recomp/N64ModernRuntime) (MIT, N64Recomp org / Wiseguy) — the static recompiler and the `ultramodern`/`librecomp` runtime this project's game code and `main.cpp` are built on. - [RT64](https://github.com/rt64/rt64) (MIT, RT64 Contributors) — the rendering backend used for `ultramodern::renderer::callbacks_t`. - [n64sym](https://github.com/shygoo/n64sym) (MIT, shygoo) — the signature- based symbol identification tool used as the seed oracle in `analysis/ident_libultra.py`. - [Zelda64Recomp](https://github.com/Zelda64Recomp/Zelda64Recomp) — reference implementation this project's `main.cpp`, `rt64_render_context.cpp`, and `register_overlays.cpp` patterns were adapted from (see `analysis/docs/build-notes.md` for exactly what was kept vs. dropped). Knife Edge – Nose Gunner is © 1998 Kemco. This project contains no Kemco assets, game data, or ROM content — only independently reverse-engineered control-flow structure (function boundaries and libultra/OS symbol names) needed to recompile the game's own machine code.