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.
6.7 KiB
Translation batch: branching map-header constructor
2026-10-20. The next engine primitive is implemented and passes desktop native equivalence tests. This document distinguishes constructor tests from loading a real world.
Implemented
tools/record_target.pyrejects a different executable/slice and records hashes of all 8,673 installed data files (1,498,094,877 bytes). It inventories base/core metadata and tool/source identities without copying assets. Outputs:build/target/manifest.jsonandassets.json. Save compatibility and the gameplay configuration remain unselected.tools/a64_lift.pynow handles bounded immediate branches, loops, CBZ/CBNZ, TBZ/TBNZ, B.cond, integer ADD/SUB with NZCV, conditional selects, and selected unscaled/indexed memory operations. It also recognizes an adjacent 128-bit load/store copy idiom; this is not general SIMD support.- CFG exports use
run(context_offset, instruction_budget). Straight-line exports retainrun(context_offset). Status 3 aborts on budget exhaustion with partial state; it is not resumable. Unknown targets, unsupported instructions, undefined incoming flags, and entry into the middle of a copy are rejected. tools/build_map_header.pyautomatically translates the original 332-byte / 83-instructionMapHeader::MapHeader(MapDeserialiser&)at0x1014c6aac. It has six direct read call sites but performs three reads per successful invocation. The native memory-stream boundary replacesreadOrThrow; arbitrary C++ unwinding remains unimplemented.runtime/map_header_bridge.cexposes this constructor with an explicitly supplied version and a payload already positioned at the constructor's input. It does not assume the payload follows byte nine in the save. RecoveringMapDeserialiserinitialization/stream positioning is still required before probing these fields in a real archive.- The constructor copies the version object and reads three MapTick-sized fields. For packed versions at or below
0x0001000200000175, it reads three 32-bit values and widens0xfffffffftoUINT64_MAX; above that cutoff, it reads three 64-bit values. The individual field meanings are not established. - Device reports now include run ID, timestamp, app version/build and compilation identity. Save probes stream a SHA-256 of the entire input. A fresh device report's hash matched the actual approved Mac autosave, and its run ID matched the version self-test.
Checks
- 1,860,000 comparisons of hand-authored ARM64 branch/loop/flag/select fixtures against native execution, including edge values, preserved integer registers, memory guards, execution-budget exhaustion, and six invalid CFG cases. These fixtures are compiler validation, not recovered game code.
- 20,000 comparisons of the original MapHeader instruction words against the generated WASM→C implementation, covering old/new version paths, boundary versions, legacy sentinels, guarded public output, unchanged input, and short-stream failure without public output mutation.
- Existing 20,000 MapVersion comparisons and fifteen archive-prefix fixtures pass.
- Nine unsupported-instruction/CFG/copy guards reject invalid inputs.
- Exact REA byte provenance passes for the two RNG functions, their constants, MapVersion, and MapHeader.
- Unsigned iOS build and Steam dependency/import audit pass. Signed build 2 was installed and launched on the iPhone 15 Pro; both modern 64-bit and legacy 32-bit/sentinel MapHeader self-tests passed. The version constructor self-test and actual-save version probe passed in the same launch, with a matching input SHA-256.
build/map-header/device-verification.jsonretains these reports and the signed binary hash. These MapHeader tests use controlled payloads; the actual-save check still reads only its version.
Reproduce the new checks after the existing RNG/version builds:
python3 tools/record_target.py '/absolute/path/to/factorio.app'
python3 tests/check_a64_cfg.py
python3 tests/check_a64_guards.py
python3 tools/build_map_header.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
python3 tools/check_rea_rng.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio' evidence/loader-cfg-final.json --include-version --include-map-header
python3 tools/loader_inventory.py evidence/loader-cfg-final.json
python3 tools/package_ios.py
New loader evidence
evidence/loader-cfg-final.json retains the complete exported records. REA was closed successfully with build/loader-cfg-rea.snapshot.json; the live Hopper session took roughly ten minutes to prepare this time. Snapshot replay did not eliminate native analysis for these new queries.
| Root | Observed direct callees | Observed basic blocks |
|---|---|---|
Map::load, 0x1014c93f8 |
3 | Not queried |
Map::loadData, 0x1014c9618 |
82 | 227 |
Scenario::loadFactory, 0x1000fa1d0 |
87 | 702 |
MapHeader constructor, 0x1014c6aac |
1 | 8 |
The Map::load assembly allocates an object, calls the Map constructor, copies initialization state, then calls loadData. Its vector copies, allocator and constructor dependencies remain unsupported by the runnable engine prototype. Map::loadData includes indirect calls and many subsystems; its observed direct call set does not establish complete dependency closure. build/startup/loader-frontier.json records the immediate frontier and observed indirect call sites available in retained assembly.
REA evidence IDs:
- Map::load direct calls:
ev_9de8048a446bfa9212d8be5bd14ca915af239bde3fefe0005c4d635624fcdd35. - Map::loadData direct calls:
ev_d8182d6051b428439fae8688802fd80ded2f7a4b88e4b121da8c85c2f015a3ea. - Scenario::loadFactory direct calls:
ev_3272507fc8a450275baa7dcdba5330225b12cb7df300791ace8a663dd9da9833. - MapHeader assembly:
ev_23fbedc8f2cdb2aa700d457c1395d090c9af691160a01e3398330fcc740031c8. - Complete MapHeader bytes:
ev_edc9090e494bf4c92f5c0e5d9f4366272062c6d2d3d758442448ab720bf4e422.
Next batch
- Recover MapDeserialiser construction, its version object/layout, and stream position at MapHeader entry. Establish a real-save fixture for this constructor before interpreting actual tick fields.
- Add general guest-to-guest calls so translated constructors compose without implementing each callee as a native bridge.
- Recover the Map constructor's required allocations/global references and translate its smallest reachable dependencies.
- Continue the runtime memory/import model and optional Steam-free startup. Full archive loading, world reconstruction, simulation, rendering and gameplay are still absent.
The subsequent stream-positioning batch locates MapHeader in the actual save, adds bounded direct guest calls and enters MapHeader through its original C1 tail branch. See save-layout-progress.md. The original batch status above is historical.