The compatibility implementation uses the Box2D 1.4.3 engine from
Box2D's official SourceForge SVN, revision 58.
The immutable repository path is
!svn/bc/58.
Revision 58 was committed on 2007-11-29; its Readme.txt identifies the tree as
"Box2D Version 1.4.3."
This is the strongest supported source basis for the shipped box2d.dll. It is
not merely a nearby version: its object layout and stepping algorithm agree with
the binary evidence, while later revisions that still call themselves 1.4.3 do
not. Source identity cannot be proved from a stripped binary, so the DLL and the
probe traces remain the behavioral oracle.
The upstream project is Erin Catto's
Box2D SourceForge project. Revision 58
is distributed under the zlib license, copyright 2006-2007 Erin Catto. The
license is preserved at
third_party/box2d_legacy/License.txt,
and every imported source file retains its upstream notice.
Only the engine's Include/ and Source/ trees, License.txt, and Readme.txt
were imported. Examples, generated libraries, IDE projects, documentation, and
contributed code were omitted. The joint implementations remain because the
unmodified engine core's joint factory and world code link against them, even
though the game does not create joints.
The imported baseline was normalized from CRLF to LF, then nine source/header files received small, plainly marked portability/safety adaptations described below. No solver, collision, integration, material-mixing, sleeping, or fixture algorithm was changed. For auditability:
| Material | SHA-256 |
|---|---|
Upstream License.txt bytes |
6d77641123753e054c382a0bd224f79af81a87bf69a8157ee66dff653a43b58e |
Upstream Readme.txt bytes |
71281efaf7825b823cbcd07c4a45ef9a98237a26b2c4998a320378b463855426 |
Upstream 55-line Include/ + Source/ checksum manifest |
e29c662d65c4eea125f23befac8aa5e621013326fab7d25c308bf98207e0cc43 |
| Baseline LF-normalized 55-line checksum manifest | fa06cceba7e66b7033257c6ac36e086660a62d5a0c914cc1c6933184566d3d56 |
| Current adapted 55-line checksum manifest | 5c04cb04c9c89fc696682751e180303dea7774a8a40e05443ede762a276fd393 |
Each manifest hash is the SHA-256 of the sorted output of
sha256sum over files named relative to the revision root (Include/... and
Source/...). The SourceForge-generated ZIP itself is not used as an integrity
identifier because regenerated directory metadata can change its archive hash.
Every source change carries an IRISU SAFETY PATCH comment. They address
undefined behavior or process-global initialization on the supported 64-bit,
multi-environment host without changing the single-world numerical model:
b2PairManagernow initializes its broad-phase pointer, callback pointer, and pair-buffer count. Revision 58 otherwise consumes an indeterminatem_pairBufferCountwhile the first proxy is committed; a dirty placement-new regression test covers that construction context.b2Allocuses amax_align_t-aligned size header. The original four-byte prefix can return misaligned storage on 64-bit systems. The allocation byte counter is a relaxed atomic because it is diagnostics only and independent worlds allocate concurrently.- The immutable block-size lookup and contact-factory registry use
std::call_once. Revision 58 lazily initialized both through unsynchronized process globals, which races when separate environments are created on different threads. - The GJK iteration diagnostic is thread-local, and island position-solver progress is stored on each island instead of in shared function statics. Neither value participates in the physical result; both otherwise race when independent worlds step concurrently.
- World destruction runs the engine's normal deferred body teardown before releasing the broad phase. This ensures that live shapes, proxies, contacts, and joints do not outlive the storage they reference.
- Shape destruction saves the shape type before ending the polymorphic object's lifetime. Revision 58 read that member after the explicit destructor call, which is undefined behavior in modern C++ even though the old compiler happened to preserve the bytes.
tests/native/test_physics_lifecycle.cpp covers dirty construction, 2,000
create/configure/reset/destroy cycles, and concurrent independent worlds. These
changes are compatibility hardening, not evidence that the shipped 32-bit DLL
contained the same fixes.
The shipped DLL's 32-bit layouts match revision 58 exactly:
| Evidence | Shipped DLL | Revision 58 |
|---|---|---|
b2Body allocation |
0x88 bytes |
sizeof(b2Body) == 0x88 |
| body user-data offset | 0x84 |
offsetof(b2Body, m_userData) == 0x84 |
b2World allocation |
0x19260 bytes |
sizeof(b2World) == 0x19260 |
The exported wrapper also exposes the revision-58-era API: b2BodyDef::AddShape,
origin-position accessors, an AABB world constructor with a sleep flag, and the
two-argument World::Step.
More decisively, disassembly of the shipped World::Step follows revision 58's
single-phase order:
CleanContactList -> CleanBodyList -> Collide -> Island::Solve -> Commit
Revision 62 and later use the materially different split pipeline
Integrate -> Commit -> Collide -> SolvePositionConstraints. Revision 69 also
adds a body field, moving user data to 0x88 and growing the body to 0x8c.
Those revisions are therefore incompatible despite retaining a 1.4.3 label.
Independent probe observations agree with revision 58 semantics: semi-implicit integration, the legacy polygon skin, retained sleeping contacts, deterministic contact ordering, geometric-mean friction in disassembly, and maximum (rather than multiplied) restitution.
third_party/box2d_legacy/CMakeLists.txt
provides the static target irisu::box2d_legacy. A forced include of
compat/cstring_compat.h supplies declarations for memcpy, memmove, and
memset that the Visual C++ 2005-era code obtained transitively. The marked
source safety adaptations above are the only other changes to the imported
engine.
The shipped 32-bit MSVC DLL evaluates the solver through x87. On GNU x86 and
x86-64 builds, the compatibility target therefore uses -mfpmath=387 instead
of GCC's x86-64 SSE default. A seed-41 crowded-board differential probe found
one-ULP SSE disagreements on the first update. The optimized GNU x87 build is
closer, but it is not bit-exact: against the full original getter oracle its
first mismatch is initial actor 10's rotation after step 1 (403df825 shipped
versus 403df823 GNU), followed by a one-ULP X mismatch at step 2. The native
build metadata reports the arithmetic choice as legacy_fp_mode: "x87"; it is
a deterministic supported profile, not an MSVC-equivalence claim. Unsupported
compiler or architecture combinations report compiler-default and retain
their own determinism boundary.
A historical clean-room diagnostic built pristine revision 58 with 32-bit MSVC
9 RTM (/Od /fp:precise /MT /D NDEBUG) and an original-compatible wrapper.
After restoring the wrapper's required b2d_set_v division by magnification,
it matched 32,515 observed X/Y/rotation/velocity float words through step 1,340
and all 191,339 contact-cursor results through the original run's terminal
physics step 14,706 under the captured 0x137f environment. The bounded
evidence and hashes are archived in
reference/runs/replay-41449-msvc-r58-phase-20260720-006/.
The current exact-forward diagnostic compiles the same pristine source with
MSVC 9 RTM (/O2 /fp:precise /MT /D NDEBUG), converts the COFF objects to a
native ELF32 library, and runs them under control word 0x027f. Through all
47,019 steps of the replay it matches every active wrapper-operation stream:
2,368 creates, 2,368 velocity writes, 2,368 user-data writes, 183,387 transform
writes, 2,344 gameplay destroys, 47,019 steps, and 573,557 contact-cursor
results. This resolves compiler/code generation as the cause of the portable
GNU backend's numerical divergence. The generated host is now an opt-in Python
backend: each active episode owns an isolated 32-bit worker, production resets
replace that worker, and vector lanes run separate worker processes
concurrently. Calls through one public multiworld bridge remain serialized
because pristine r58 retains shared lazy tables and runtime state. Because the
exact legacy world cannot yet be serialized directly, durable exact snapshots
store seed plus accepted action history and restore into a fresh configured
worker by deterministic replay. This is exact but linear in episode age; Linux
fork/COW checkpoints provide the faster local, non-serializable branch path.
Its source, construction recipe, comparison method, exact bounds, and hashes
are recorded in reference/native-box2d/README.md and
reference/native-box2d/validation.json.
The active comparison covers 813,412 mutation, step, and contact records. The
original trace's other 9,810,360 records are getter-only observations. They are
now directly compared by a second generic harness that replays all 10,777,297
recorded commands after the proxy header in original global order against the
exact host. Its 7,357,770 scalar getters and 2,452,590 velocity getters contain
12,262,950 returned binary32 words; every word and all 573,557 contact results
match through step 47,019, with no first mismatch. The harness derives its
operation stream and expectations from the trace rather than replay-specific
code. The tracked
getter-parity-2026-07-21.json
report has SHA-256
815e8805ea777fdcecc265ee1a669c4664e993a74f92a05cfe2f134195703ef8.
The forward wrapper resolves a typed table of 15 b2d_* entrypoints before
constructing a simulator and performs every physics operation through those
stored pointers. Every target must be a unique executable address owned by the
genuine exact-library link-map object, all targets must share one device/inode
mapping, and each process-global binding must equal the attested call target.
The dladdr1 name and exact symbol-start address must also equal the requested
entrypoint. Thus an ordinary preload exporting (for example)
b2d_world_step, and a tested interposed-dlsym permutation of the genuine
host's X/Y getters, are rejected before simulation. Worker opcode 13 publishes
the count and target mapping identity; Python requires it to equal an
independently captured live library. Production launch uses working directory
/, removes every inherited LD_* variable and GLIBC_TUNABLES, and forces
x87 control word 0x027f. The generated host also links its private
msvc_b2d_* bridge calls with -Bsymbolic-functions. The hosting tool and
exact CMake configuration reject every R_386_* relocation naming those
helpers. These are fail-closed provenance checks against the tested loader
attacks, not a sandbox for arbitrary code already executing in the worker.
The generated host carries a stable DT_SONAME, is retained by exact consumers
as a DT_NEEDED dependency, and is reopened only with RTLD_NOLOAD. Exact
post-link validation requires a nonempty RPATH/RUNPATH whose components are
absolute or $ORIGIN-relative; empty/current-directory and bare relative
components are rejected. Install relinks exact executables with origin-relative
lookup and places the host in the configured library directory.
Exact PaddedVectorEnv(workers=None) now uses
min(num_envs, 4 * process-visible logical CPUs) independent worker processes;
an explicit workers= request remains authoritative. The portable backend
remains capped at eight. A supplemental 8-to-64-lane scaling probe motivated
this heuristic default, but one sample per width does not establish a universal
optimum and the probe is not the formal throughput gate. Separately, Simulator
mechanics configuration is immutable after validation, so its configuration
hash is computed once at construction and reused by transitions, diagnostics,
and snapshot compatibility checks without changing its value.
Ten sampling runs of the dense exact baseline collected 24,020 samples at
1,267.12 ± 4.43 decisions/s. The converted MSVC host accounted for 92.678%
of samples. The largest symbols were SolveVelocityConstraints (28.426%),
SolvePositionConstraints (23.393%), the MSVC FCOS intrinsic (14.205%), and
the matching FSIN intrinsic (11.678%). Instrumentation over 3,000 decisions
then found 13,096,069 cosine/sine calls with the same raw float argument.
The checked-in msvc-runtime.S
therefore implements a narrow pair optimization. FCOS stores the raw float
key, executes one x87 FSINCOS, returns cosine on the x87 stack, and stores the
float-rounded sine. The next FSIN reuses that sine only when its raw input bits
match; otherwise it executes standalone x87 FSIN. The public multiworld bridge
continues to serialize calls, so different worlds cannot race or interleave the
pair state. Concurrent direct calls to the legacy single-world host remain
unsupported.
Instrumentation also found that 833,228 of the 13,096,069 matching inputs
(6.362%) are raw positive zero, largely from position-solver rotation updates
of static bodies. For that exact input, the retained runtime stores sine +0
and returns cosine 1 without executing FSINCOS. Raw negative zero and every
ordinary nonzero input continue through the paired instruction path. This
preserves the raw signed-zero behavior while avoiding an instruction whose
exact result is already known.
x87 FSINCOS sets condition bit C2 and leaves its operand unreduced when the
absolute argument is at least 2**63. Snapshot validation permits every finite
float angle, so the runtime checks raw absolute-angle bits against 0x5f000000
and executes direct FCOS followed by FSIN at and above that boundary. The
dedicated runtime regression covers both sides of the positive and negative
boundary, maximal finite values, non-finite values, and 100,000 full-raw-bit
randomized inputs. An unarchived controlled local dense-core A/B improved
1.287% over the paired-only host; the wide pipeline does not isolate that
change.
This is a runtime-intrinsic substitution, not a Box2D algorithm change. It does
not alter the ten solver iterations, body/contact ordering, rule calls, or
floating-point result stream. A pinned seven-run A/B improves the dense core
from 1,246.873 to 1,448.173 decisions/s (+16.14%). The paired source-manifested
pipeline records +16.82% for the dense native simulator, +14.37% for the 48-body
physics workload, and +13.50% for eight exact padded lanes. It remains exact on
all four replay oracles and the active mutation/step/contact stream through all
47,019 steps. The retained host passes 14/14 exact Release and 14/14 exact
ASAN/UBSAN CTest targets; portable Release and ASAN/UBSAN builds each pass 8/8.
Hostile preload fixtures run with the worker-linked ELF32 libasan first. The
GNU worker/wrapper/API and fixtures are instrumented, while a regression
explicitly verifies that immutable MSVC9 host instructions are not. The Python
suite passes 204 tests with three expected skips in a normal build.
An additional old-host/new-host differential replay produced byte-identical
813,508-record wrapper traces (13,757,907 bytes) through tick 47,019, both with
SHA-256 cde35ca60b5511678edf128ed8f3ae09c8cf00e240696325c4acc8681f829eb0.
This covers every traced create, velocity/user-data/transform write, destroy,
step, and contact-cursor result, not only the terminal score.
The current
exact-pipeline-adaptive-wide-perf-2026-07-21.json
records 38/38 current runtime-source hashes and 88/88 true cross-path equivalence
leaves, and SHA-256
4067fdff9360989adb696bdc5ad7d98983729f9fa424271fbd7e3e1fb9164eef.
It records worker SHA-256
4faa4508a89df3e1e62b80e2871b6a35b5913f220d53fe5de43408ad6512c261
and host SHA-256
ce14d1cab9ce4331bf494fe92bf657029487aec9f7435e7479b3c7cb579fafb5.
It measures 9,685.170 decisions/s at 64 exact lanes, 48.426% of the
20,000-decision/s target (2.065x short), plus 1,447.881 dense native wall
decisions/s (1,485.277/s for step plus observation) and 74,853.849 ticks/s on
the directly comparable 30,000-tick 48-body physics workload. The July 20
exact-pipeline-paired-trig-2026-07-20.json
remains the historical comparable post-pair artifact, and
exact-pipeline-final-2026-07-20.json
is its pre-trig baseline.
The follow-up
exact-core-solver-source-optimizations-2026-07-21.json
evaluates two MSVC9-only exact-preserving source changes against a predeclared
3% integration threshold. Guarding static-body position/rotation stores improves
the dense median by 0.648%; caching rotated velocity anchors improves it by
0.472%. Both variants match the exact 47,019-step replay, the 813,508-record
wrapper trace, and the runtime trig gate, but neither was retained. Their full
10,777,297-command getter replay was intentionally not run after they failed the
performance threshold. The artifact SHA-256 is
6fe2b8c482e8764ff64577261d839d552ae7c4a5a996c538bbead2a135ffcb71.
Its candidate source/object/host inputs were local and unarchived. The report
retains their hashes, source-level descriptions, machine-code audit, and parity
results, but it is not a clean-checkout rebuild recipe.
Scheduling and rollout changes do not alter this physics source. The adaptive exact-worker default only changes how many isolated processes are in flight. The experimental actor pool keeps the same worker frames and one-world-per- process lifecycle, but executes an independent policy for multiple decisions inside each lane task before joining results in lane order. Such policies must use lane-private or thread-safe mutable state.
Every public simulator and C API operation runs inside a thread-local floating-
point boundary and restores the caller's complete environment on return or
exception. The boundary masks exceptions, selects round-to-nearest, and uses
gradual underflow for SSE operations. In the GNU x87 fidelity build it also
loads control word 0x027f (53-bit precision and round-to-nearest), matching
the controlled full getter/event oracle. An unmodified Wine forwarding run
recorded 0x137f, so startup precision remains an explicit runtime-provenance
boundary rather than a universal original-game claim. The supported setting
preserves deterministic GNU-profile regression behavior and narrows the
shipped-DLL error; it does not erase the documented MSVC mismatch. Padded
workers enter the same boundary independently; no process-global floating-point
state is changed. Build metadata records this as nearest,x87-pc53 in
fp_environment.
The original game wrapper is a separate compatibility layer. In particular,
pixel/world magnification, the asymmetric velocity conversion, and the wrapper's
velocity reset in set_position are not Box2D engine behavior. They must remain
in the clone-facing adapter and should not be "corrected" inside this source.
Schema-7 snapshots retain both GetOriginPosition() and the exact current
m_position center of mass. For r58 triangles, deriving one from the other is
not bit-reversible after float rotation and subtraction, and a one-bit center
change can alter the next solved frame. Restore validates their relationship by
running r58's own transform operations.
Schema 7 also preserves the stale color value in each of the original game's 200 actor-pool slots. Special clears scan those slots by color even after an actor is inactive, so this otherwise invisible state can change gauge survival and therefore the entire future reward trajectory.
The previous-step fields m_position0 and m_rotation0 are intentionally not
serialized. Simulator checkpoints occur only between completed world steps;
r58's conservative-advancement pass is compiled out in b2World::Step, and
b2Island::Solve overwrites both fields from the current transform before the
next shape synchronization. They therefore cannot affect a future step from a
supported checkpoint.