Byrgenwerthnotes of Paleblood, rebuilding Bloodborne one verified function at a time

7 October 2026: going public, and two frames' worth of work

The code repository went public today, with a clean history, open contributions and branch protection, and two more functions that run every frame are rebuilt and verified.

Later the same day

The project's direction changed on 7 October: Paleblood is now a decompilation with its own runtime, and the borrowed runtime this post describes is being removed. See a new direction.

Going public

The port's code now lives in a fresh public repository with a clean starting history, its runtime fork beside it, and the old repositories kept private as archives. Anyone can fork, claim a function and open a pull request; every pull request carries a short session report that this wiki is written from. Backups run on their own: every ten commits a verified bundle and a copy in the archive.

The pad manager's frame step

Once a frame the pad manager marks two of its map entries and, when its owner is ready, advances it by a frame step. The replacement walks the same red-black tree the same way, signed comparisons and all. The frame-rate patches change that step in a surprising way (1/15 s for both 30 and 60 frames a second), and the option does exactly what they do.

A render-side update

A larger one: a list of reference-counted objects aged every frame, finished ones released and unlinked, and a child stepped by the frame time. The uncapped patch reroutes it through a small code cave to read the measured frame time instead.

The present, and a piece of middleware

Two more functions that run every frame: the swap chain's present, which waits for its vblank and picks a flip mode (the uncapped patch simply asks for no vblank sync), and a sampling helper from YEBIS, the post-processing middleware the game ships with, whose only patch is one assertion removed. Checking them took two new abilities in the test harness, passing and returning floating-point values, and three strengthened test cases each time a deliberately wrong version slipped through: a NaN, an odd sample count, and a microsecond-exact sleep threshold.

Playing it, and the task manager

The first real play session with every replacement in place ran at a steady 60 FPS on a 2560x1440 screen. It also turned up a renderer setting in the runtime that mirrored the bottom of the screen into the sky; the runtime now starts at native resolution without its upscaler, and the artefact is gone. Then the frame's work itself: the task manager's six small functions that bracket every frame, replaced and checked against real frames.

Physics, and a constructor that wires itself in

The next frame-rate patched function is a constructor: it builds a physics instance over a set of bodies and threads it into the world's shared tables. It runs only while an area loads, so the recordings come from the clinic's loading. The harness learned to pass arguments on the stack, and the recordings now come from a shared recorder. The first recordings crashed both sides in the harness, which led to finding that some objects live in the executable's own memory and have to be recorded by address. Four wrong versions slipped through at first; each one led to a sharper case.

The hunters' prey: the AI manager

The biggest function so far: the Havok AI manager's per-frame update. It steps the AI world by a fixed 1/30 s, which the 60 FPS patches halve, and it carries a whole debug display (marks, axis frames, boxes) that never runs in a normal game. Every branch is covered by hand-built cases with real trees and render state, and ten deliberately wrong versions all fail.

A new system: characters

The first function from the character code: a per-frame update that, among other things, regenerates what looks like stamina. The frame-rate patches pin its frame time to a constant (1/27 s at 30 FPS, 1/54 s at 60). It reads thread-local storage, so the test harness now does what the runtime's loader does (game code reads its thread pointer through another segment register), and stubbed game functions can return floats. Four deliberately wrong versions slipped through at first. One was a genuine twin of the original; the other three needed new cases: two NaNs with different payloads, a maximum below the stamina floor, and a string of exactly eight characters' capacity.

A lesson about the patches

Checking that update against the 60 FPS patch failed at first although the patch never touches its code: the patches also rewrite shared constants in the data segment, which this function reads. In the game our options run together with the patch, so the checks now run both sides on the patched image, and every earlier check still holds.

Next

The remaining frame-rate-patched functions that run in the clinic, smallest first.

← Back to devlog