Run the original macOS ARM64 game code on iOS: - tools/retarget.py: header-only Mach-O retargeting to an iOS dylib - platform/native: UIKit host and macOS API shims (AppKit for SDL's Cocoa backend, CoreGraphics displays, CoreAudio device, Metal device properties, Wuffs PNG decoding, Steam and peripheral stubs) - touch, overlay, iOS keyboard and hardware keyboard input - tools/build_native.py: build, sign, install, data copy, launch - docs: build guide, controls, architecture Also includes the earlier static-translation research (ARM64 -> WASM -> native) under docs/research, runtime/, tests/ and tools/. No game files are included; the build reads them from a local install.
7.3 KiB
Original MapDeserialiser initialization (2026-10-20)
Build 5 initializes the original MapDeserialiser on the iPhone 15 Pro using both a controlled version header and the approved actual save's nine-byte version prefix. The original guest code performs five reads, creates its helper structures and owned TargetDeserialiser, and returns successfully. Reports record 12 allocations, 10,712 guest heap bytes, a target-pool capacity of 64 and a stream cursor of 9. A controlled newer-version prefix takes the original error branch and returns the adapter's version-error status 7.
This is an isolated constructor probe. The module is freed after validation; its object graph is not yet connected to a persistent world loader. The rest of the save stream, object references, entities and gameplay are not loaded. The scenario-envelope probe remains a separate host-side reconstruction.
Original input and evidence
| Code/data | Address | Bytes | Complete REA byte record |
|---|---|---|---|
| MapDeserialiser C2 normal CFG including version-error branches | 0x1014def40 |
1272 | ev_904c99c8305aec9932b9bfe4c722d12a961c34e254d80c97a09db109dc390bba |
| Actual TargetDeserialiser C1 normal body called by that CFG | 0x1012c1044 |
116 | ev_d0a2867c6c04389e519058440e52ecc612c2212435f9920a725fb219e27fc663 |
| Shared TypeAwareMemoryPool C2 normal CFG | 0x10244a4bc |
1260 | ev_bdbccc8d7c9a58178fcb6476fcf32651ee9daa7abfd66ac2b4e39b2b8d489b3e |
| Pool vtable identity range | 0x102fc2d98 |
32 | ev_ee2b757ce4a5c73c150c5cd29112e7dd6db99529211abfa59085d500e4c2cbb1 |
The prior C2 TargetDeserialiser input at 0x101ab7c74 also has a complete matching byte record, ev_5c30d5ca0d76c6b2727297e08f671d59e0944c9bffadd7b58cb62f8650e32b37. This closes the previous batch's pending byte check. The C1 constructor is a distinct body with different stack saves; it is translated at its actual call target, without substituting the earlier C2 implementation.
The unchanged ARM64 SHA-256 is pinned to 2cb4de127b50d54c6c5666acfef2263fae16d11a3361ad39353ecf8a66a4d9c7. Function range hashes and all 662 translated instruction words are in build/deserialiser/manifest.json. Complete REA records are in evidence/deserialiser-final.json; the native session was snapshotted to build/deserialiser-rea.snapshot.json and closed. Earlier constructor assembly is retained as ev_3143ceb6dee72f0a56235e02cd4a5ed942c3826860ea21c315847bb186053650.
Guest execution and boundaries
The original constructor stores the reader pointer at object offset +8, the packed version at +0x18, its flag at +0x20, the target pointer at +0x28 and the target ownership byte at +0x4c6. The initialized-state byte at +0x10 becomes 1. Helper allocation order and self/heap pointer relationships are compared against the original ARM64 execution.
The stream service is a bounded memory adapter selected for the observed readOrThrow contract; it does not implement the game's ReadStream virtual dispatch. Operator new uses the existing 16-byte-aligned per-run guest arena. The new bzero adapter validates its destination in the allocated arena prefix. All guest pointers remain offsets into the fixed 1 MiB module memory.
Zero-vector stores now require a MOVI-zero definition on every incoming path. Integer instructions preserve these facts; calls clear them, and copy loads overwrite the relevant vector fact. The supported stores include unscaled/scaled Q stores, pre/post writeback and Q-store pairs. This does not add general SIMD arithmetic, floating point or incoming vector-register state.
Version rejection aborts with status 7 at the original call to __cxa_allocate_exception. The original exception object, message and unwinding behavior are not implemented. Destruction helpers and delete return unsupported-service status 6 if reached. Ordinary constructor inputs skip those cleanup blocks; allocation failures abort with status 5 and discard the module. Static ADRP pages used for vtable/type/callback identities are declared explicitly; those addresses do not provide callable native pointers or a complete relocated image. No exception landing pads are executed.
The constructor's original checks accept 2.0.77.0 and reject 2.0.77.1; they also reject major zero, the tested newer versions/nonzero flag and the original 1.2.0 build-0..27 interval. This constructor-level acceptance does not establish that the rest of a save version or mod set is supported. The separate save-envelope adapter remains restricted to 2.0.77.0/flag 0/empty startup settings.
Verification
- 512 original ARM64 object-graph comparisons vary guest object and heap offsets, accepted headers, supplied/owned targets and stream-ownership values. They compare the complete 1,224-byte constructor output and allocated graph after normalizing reader, self, heap and vtable pointers. Register and unallocated-memory guards pass.
- 9 short-header cases, 8 original version-error branches, all 12 allocation-failure boundaries, and exact heap capacity pass. Public wrapper outputs remain unchanged on failure.
- 4,096 native zero-vector comparisons cover pair/single stores, v31, positive/negative offsets, pre/post writeback, branches, merges and loops; four invalid vector cases fail closed.
- Prior 66,048 instruction comparisons, 80,000 guest-program comparisons, 512 TargetDeserialiser graphs and 20,000 MapHeader comparisons still pass.
- All new constructor/code/data ranges match complete REA observations. The checker reports this independently of behavioral comparisons.
- Build 5's controlled and actual-save initialization reports pass on the iPhone, alongside same-launch TargetDeserialiser, version and modern/legacy MapHeader checks. The actual save's header offset and raw tick values still match the existing desktop result, and all save reports share the same input hash and probe ID.
- The signed binary has no Steam dependency/import and contains none of the desktop original-code oracle entry symbols.
The desktop oracle redirects declared BL services and relocates the pool's vtable ADRP/ADD reference. Its version-error service aborts before exception construction, matching the guest adapter's selected stopping point. Tests therefore compare original initialization and branch decisions, not native C++ exception semantics.
Reproduce
python3 tools/build_target_deserialiser.py build/factorio.arm64
python3 tools/build_deserialiser.py build/factorio.arm64
python3 tests/check_a64_zero_vectors.py
python3 tests/check_a64_initialization.py
python3 tests/check_a64_program.py
python3 tools/build_map_header.py build/factorio.arm64
python3 tools/check_initialization_rea.py build/factorio.arm64 evidence/deserialiser-final.json
python3 tools/package_ios.py
Generated artifacts and verification reports live under build/deserialiser/. After signing, installing, launching and downloading the seven iphone-*-result.json reports, python3 tools/check_deserialiser_device.py verifies build identity, common launch/probe/input identities and the signed binary, writing device-verification.json.
Next: translate MapGenSettings and the following Map::loadData dependencies, then connect these isolated verified components into a persistent loader. Full static-image mapping, indirect calls, object references, destruction/unwinding, simulation, graphics, sound and touch controls remain ahead.