Native iPhone port of Factorio 2.0.77
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.
This commit is contained in:
commit
51d085e865
89 files changed
+7999
No files matched your search
@@ -0,0 +1,59 @@
|
||||
# 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
|
||||
|
||||
```sh
|
||||
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.
|
||||
@@ -0,0 +1,250 @@
|
||||
# Factorio 2.0.77 → native iPhone: implementation plan
|
||||
|
||||
Written 2026-10-20. This is a proposed implementation sequence, not a claim that the remaining translation is feasible or complete. Function discovery will refine the scope and dependencies. No reliable completion date is available before the translation and loader feasibility gates pass.
|
||||
|
||||
## 1. Target and definition of done
|
||||
|
||||
Build a native application for the connected iPhone 15 Pro that runs a small Factorio world locally, using the installed macOS ARM64 executable as the reconstruction input. Use REA MCP for focused binary evidence. Translate selected game code ahead of time through ARM64 → WebAssembly → wasm2c C → Clang iOS machine code. All translation happens on the Mac during the build; the phone executes signed native code.
|
||||
|
||||
Initial gameplay scope:
|
||||
|
||||
- Factorio 2.0.77 and a pinned, recorded set of its required base-game data. Identify expansion/mod dependencies explicitly before choosing a fixture.
|
||||
- Local single player, a small controlled world, and touch controls.
|
||||
- Load a world, move the character, inspect inventory, mine, craft, place a miner and belt, and observe item transport.
|
||||
- Save locally, exit, relaunch, and resume the same world.
|
||||
- No Steam integration. Multiplayer, Steam cloud/achievements/overlay, arbitrary mods, expansion compatibility, large factories, and App Store distribution are deferred.
|
||||
|
||||
Completion requires an actual device demonstration of this sequence, reproducible builds, save round-trip checks, and measured memory/performance results. A menu, header parser, or rendered screenshot alone does not meet this definition.
|
||||
|
||||
## 2. What exists now
|
||||
|
||||
Verified foundations:
|
||||
|
||||
- Pinned installed executable and unchanged ARM64 slice, with matching debug UUID.
|
||||
- REA evidence for RNG, the map-version constructor, parts of startup, and Steam integration.
|
||||
- Two RNG functions translated with specialized byte-guarded lifts; 876,544 output comparisons and 1,024 seed-constructor comparisons pass.
|
||||
- A bounded automatic integer lifter translating the original map-version constructor; 20,000 native comparisons pass.
|
||||
- A native ZIP/zlib adapter that reads only the first nine decoded map bytes; fifteen archive fixtures pass.
|
||||
- A signed prototype installed on the iPhone. Its AOT self-test passes. With user approval, the real autosave was copied to the app and its header reports version 2.0.77 successfully.
|
||||
- Steam import/dependency audits pass for the current prototype.
|
||||
|
||||
Missing: whole-world loading, general control flow and calls, a complete memory/runtime model, simulation, game rendering, audio, gameplay input, and save writing. The current reader does not validate complete archive contents or load entities.
|
||||
|
||||
## 3. Architecture to build
|
||||
|
||||
```text
|
||||
Pinned local Mach-O + matching symbols + required data files
|
||||
|
|
||||
REA MCP evidence
|
||||
|
|
||||
function/CFG/import/relocation manifests and semantic IR
|
||||
|
|
||||
ARM64 → validated WASM modules
|
||||
|
|
||||
wasm2c → generated C
|
||||
|
|
||||
Clang → static iOS libraries
|
||||
|
|
||||
native host: storage / threads / Metal / audio / input
|
||||
|
|
||||
signed iPhone app
|
||||
```
|
||||
|
||||
Keep engine object layouts and guest addresses inside the guest model. Native services receive validated values through explicit bridges. Every lifted function is identified by source address, exact bytes/hash, supported instruction set, calls, and evidence provenance. Unknown instructions or unresolved executable targets stop the build or trap with diagnostics; they never silently become no-ops.
|
||||
|
||||
Glue5 is a reference for the WASM-to-native build and compatibility layer. Its documented input is an existing supported `game.wasm`; it does not supply Factorio's missing native-binary-to-WASM translation. Its graphics and import interfaces must not be assumed to match Factorio. Reference: https://github.com/mempage4k/glue5.
|
||||
|
||||
## 4. Phase A — freeze inputs and make evidence repeatable
|
||||
|
||||
1. Record executable/slice hashes, UUID, game version, selected data/mod versions, tool revisions, Xcode/SDK, device OS, and build configuration in one machine-readable manifest.
|
||||
2. Inventory installed data files without copying them into source control. Define a local asset-import manifest with hashes and relative paths.
|
||||
3. Record the current device baseline: self-test, real-save header result, app build identity, and source-save hash. Future reports include unique run IDs, timestamps, and input hashes to distinguish fresh results from stale files.
|
||||
4. Reuse the isolated REA MCP workflow and its longer startup timeout. Export evidence after each bounded investigation and close the binary session cleanly.
|
||||
5. Store each finding with function address/range, byte hash, evidence IDs, observed callers/callees, unresolved references, and confidence. Keep decompiler hypotheses distinct from instruction-level observations.
|
||||
6. Add resumable investigation batches so a lost Hopper session does not erase completed work. Determine whether persistent analysis is available; do not depend on saving databases in the demo.
|
||||
|
||||
Deliverables: target manifest, asset manifest, evidence index, repeatable baseline report.
|
||||
|
||||
Exit gate: another clean build reproduces the existing RNG and header results from the pinned input. Changed input is rejected clearly.
|
||||
|
||||
## 5. Phase B — recover the smallest engine dependency graph
|
||||
|
||||
1. Continue REA analysis at `Scenario::loadFactory` (`0x1000fa1d0`), `Map::load` (`0x1014c93f8`), and `Map::loadData` (`0x1014c9618`). These are candidate roots from symbol inventory; their complete behavior remains to be recovered.
|
||||
2. Follow direct calls and identify virtual calls, callbacks, jump tables, global reads, static constructors, TLS accesses, allocations, and exceptions.
|
||||
3. Inspect reachable startup dependencies from `GlobalContext::init` (`0x100161584`). Its current 151 direct callees and 643 basic blocks are an observed frontier, not the complete graph.
|
||||
4. Recover the required object layouts, vtables, serialized field order, ownership rules, and initialization order using matching debug metadata where present and instructions where necessary.
|
||||
5. Classify each dependency as: lift original code, implement a documented host adapter, exclude an optional service, or unresolved. Give every host adapter a concrete contract.
|
||||
6. Build a coverage report: discovered reachable functions, translated functions, unsupported instruction classes, unresolved indirect targets, and unavailable imports. Do not equate function count with working gameplay.
|
||||
7. Choose the smallest legal local test world and required base-data configuration. Establish desktop observations: load result, entity counts, tick number, inventory, and stable state summaries where accessible.
|
||||
|
||||
Deliverables: dependency graph, object-layout notes, import contracts, prioritized translation queue, small-world fixture description.
|
||||
|
||||
Exit gate: enough of the loader graph is understood to select the next executable slice and enumerate its blockers. No claim of graph completeness while indirect targets remain unresolved.
|
||||
|
||||
## 6. Phase C — extend the ARM64 lifter into a real compiler
|
||||
|
||||
Implement incrementally, guided by Phase B rather than attempting every ARM instruction first.
|
||||
|
||||
1. Separate decoding, instruction semantics, control-flow IR, validation, and WAT emission. Preserve original PC on every IR operation and in generated diagnostics.
|
||||
2. Implement architectural integer semantics: register width and zero extension, SP versus zero register, signed/unsigned operations, shifts/rotates, multiplication/division, comparisons, and NZCV flags.
|
||||
3. Add basic blocks, conditional/unconditional branches, loops, multiple returns, and switch/jump-table control flow. Use structured WASM regions where possible and an explicit block dispatcher where necessary.
|
||||
4. Add direct guest calls with a defined register/stack ABI. Correctly preserve link register, callee-saved state, stack alignment, and nested/reentrant invocation.
|
||||
5. Model indirect calls, virtual dispatch, function pointers, and tail calls through validated guest-code identities. Discover address-taken targets and their relocations; unresolved targets remain explicit failures.
|
||||
6. Add PC-relative addresses, literal pools, global data references, signed and indexed loads/stores, and additional stack addressing modes.
|
||||
7. Add floating point and the SIMD operations actually reached by the chosen slice. Preserve required rounding, conversion, comparison, and NaN behavior; test edge cases separately.
|
||||
8. Define atomic and memory-order semantics before translating threaded paths. Initially run only paths whose single-threaded execution has been shown to preserve behavior.
|
||||
9. Recover exception/unwind behavior. Choose and validate an explicit guest unwind mechanism or narrowly verified boundary replacements. The current status-returning header adapter is not a general C++ exception implementation.
|
||||
10. Add build caching by source hash, compiler version, runtime ABI, and options. Split generated C into manageable units to control compiler time and memory.
|
||||
|
||||
For each new instruction family, run finite native-oracle comparisons on macOS, architectural edge cases, randomized inputs, guard-memory checks, and unsupported-encoding checks. Then translate a real function using that feature. Original instruction oracles remain desktop-only and are never embedded as executable game code in the iOS app.
|
||||
|
||||
Deliverables: CFG-capable compiler, instruction coverage report, guest-call ABI, diagnostic PC mapping, differential test suite.
|
||||
|
||||
Exit gate: a multi-function loader utility with branches and nested calls matches the desktop oracle and runs on the phone through AOT output.
|
||||
|
||||
## 7. Phase D — memory image and native runtime
|
||||
|
||||
1. Inventory Mach-O sections, initial data, zero-fill regions, rebases/binds, vtables, function pointers, constructor tables, and TLS. Reconstruct only the reachable required image, with explicit relocation records.
|
||||
2. Decide guest address representation using measured requirements. Compare rebased offsets, segmented translation, and supported WASM memory configurations. Preserve 64-bit guest pointer semantics even if physical linear-memory offsets are narrower.
|
||||
3. Validate the chosen WASM/WABT/wasm2c memory model early. The prototype's fixed 64 KiB memory is insufficient for a world; do not assume memory64 or multi-memory support without testing the pinned toolchain.
|
||||
4. Implement guest heap, stack, globals, bounds checks, alignment behavior, allocation failure, ownership diagnostics, and configurable memory limits. Keep guest pointers separate from native pointers.
|
||||
5. Implement required allocator/libc/math, time, entropy, filesystem, compression, logging, threading, synchronization, and TLS adapters with explicit ABI conversion.
|
||||
6. Audit C/C++ runtime dependencies: constructors/destructors, RTTI, vtables, exceptions, and standard-library layouts. Reuse compatible native libraries only at demonstrated boundaries; matching names do not establish compatible object layouts.
|
||||
7. Map app read-only resources and writable saves/configuration to the iOS sandbox. Support interrupted writes and useful error messages.
|
||||
8. Implement deterministic host time/random inputs for replay tests, separate from UI and wall-clock services.
|
||||
9. Verify build outputs contain no runtime code generator, executable guest-memory mapping, or dynamic game-code loading. Audit imports and any memory-protection APIs in addition to checking linked dependencies.
|
||||
|
||||
Deliverables: image loader/rebaser used at build time, guest memory manager, runtime ABI, import implementations and diagnostics.
|
||||
|
||||
Exit gate: a substantial translated subsystem allocates, accesses globals, calls native services, and exits cleanly on Mac and iPhone without unsupported imports or memory faults.
|
||||
|
||||
## 8. Phase E — local startup without Steam
|
||||
|
||||
1. Recover all reached methods and layouts of `SteamContextInterface`, its base implementation, and `DummySteamContext` before selecting replacements.
|
||||
2. Route translated startup to local optional services at the interface boundary. Preserve the difference between unavailable Steam functionality and successful local functionality.
|
||||
3. Handle both the factory (`0x100267594`) and the inlined initialization in `GlobalContext::init` beginning at `0x100161608`. The graphical app must select local services independently of headless mode.
|
||||
4. Implement local storage, locale lookup, native text entry, and required callbacks. Exclude cloud, overlay, achievements, matchmaking, Steam identity, and Steam networking from the selected path.
|
||||
5. Remove Steam bindings and references from generated artifacts where reachable code no longer requires them. Audit imports, dependency lists, call targets, and runtime traces.
|
||||
6. Reach the minimal engine initialization checkpoint in a native harness before connecting the graphical shell.
|
||||
|
||||
Exit gate: the selected startup runs with no Steam library present, performs no Steam calls, and reaches the required local initialization state. Current prototype audits alone do not satisfy this engine gate.
|
||||
|
||||
## 9. Phase F — complete world loading on the Mac
|
||||
|
||||
1. Extend archive handling from a nine-byte probe to the loader's complete required members/streams. Validate full decompression, checksums where applicable, size limits, member naming, and failure cleanup.
|
||||
2. Recover deserializer contracts, version handling, references/object IDs, allocation order, and error propagation.
|
||||
3. Load required base prototypes and script state. Identify the embedded scripting runtime and its host interface; translate or replace it only after compatibility is established.
|
||||
4. Reconstruct surfaces, chunks, tiles, entities, inventories, recipes, forces, and other structures reached by the fixture. Reject unsupported saves with diagnostics rather than partially pretending to load them.
|
||||
5. Run the translated loader in a headless macOS harness first. Report map dimensions, entity counts/types, inventory totals, tick, and unresolved references.
|
||||
6. Compare those results against the original game's observable state and decoded format evidence. Add intermediate checkpoints to find the first divergence.
|
||||
7. Test truncated/corrupt streams, allocation failures, unsupported data/mod sets, and cleanup after failed loads.
|
||||
|
||||
Exit gate: the complete controlled world loads into translated engine state with validated reference integrity and matching observable summaries. This stage still does not imply simulation works.
|
||||
|
||||
## 10. Phase G — deterministic simulation
|
||||
|
||||
1. Recover the scheduler, tick entry points, entity update dispatch, command processing, scripting callbacks, and required worker behavior.
|
||||
2. Advance one tick with fixed inputs; compare state checkpoints with desktop observations or suitable native harnesses.
|
||||
3. Add minimal systems in dependency order: character movement, inventory transactions, mining, crafting, placement, resource production, belt movement, and required power/fluid systems if the fixture needs them.
|
||||
4. Verify RNG state, entity identifiers, event ordering, numeric behavior, and script state across repeated runs.
|
||||
5. Introduce replayable command logs and periodic canonical state summaries. Record exactly what each summary covers; a partial digest is not a whole-engine equivalence proof.
|
||||
6. Run 1, 60, 600, and then at least 3,600 ticks. Investigate the first differing checkpoint before increasing duration.
|
||||
7. Add a headless iPhone harness using the same world and command log. Compare its summaries with the translated Mac build.
|
||||
|
||||
Exit gate: the selected factory actions execute repeatably across Mac and iPhone, with matching validated state summaries and no unresolved required execution paths.
|
||||
|
||||
## 11. Phase H — bring the complete headless world to iPhone
|
||||
|
||||
1. Package the required translated modules and import only the recorded user-supplied assets needed by the fixture.
|
||||
2. Add asynchronous loading, progress, cancellation, diagnostics, and clean recovery from unsupported input.
|
||||
3. Run full-world loading and replay on the physical phone. Track peak/resident memory, load duration, tick duration, and failures.
|
||||
4. Handle background/foreground transitions without advancing uncontrolled simulation time. Define autosave behavior and safe cancellation points.
|
||||
5. Measure memory-pressure behavior and graceful failure before enabling larger worlds.
|
||||
|
||||
Exit gate: the phone loads the full fixture and runs the validated simulation without graphics. Investigate platform differences here before adding renderer complexity.
|
||||
|
||||
## 12. Phase I — graphics, audio, and touch
|
||||
|
||||
1. Recover the renderer boundary and active backend from evidence. Existing Metal linkage alone does not establish a reusable iOS renderer.
|
||||
2. Choose a backend after recovery: adapt an evidenced Metal command interface, or implement a native Metal renderer over a recovered render-command/state boundary. Avoid translating every desktop graphics API unless required.
|
||||
3. Implement texture/sprite loading, atlases, camera transforms, batching, layering, terrain, entities, animation, and required effects. Recover shaders and rendering state as needed.
|
||||
4. Present through a native iOS Metal surface with orientation, safe-area, scale, resize, and lifecycle handling.
|
||||
5. Implement input as engine commands: pan/zoom, selection, movement, inventory, crafting, build preview, place/remove, and pause. Resolve gesture conflicts and provide native text input where needed.
|
||||
6. Recover and implement the required audio mixer/device boundary, including pause/resume and interruptions. Keep audio work off the simulation/UI critical paths.
|
||||
7. Add loading/error UI and visible diagnostics suitable for device testing.
|
||||
|
||||
Exit gate: demonstrate the complete target gameplay sequence on the phone with correct world interaction and stable rendering. Compare captured scenes against the desktop reference, allowing deliberate mobile UI differences.
|
||||
|
||||
## 13. Phase J — save writing and resume
|
||||
|
||||
1. Recover the serializer and ownership dependencies for the selected world state.
|
||||
2. Write to a temporary sandbox file, finalize the archive, and atomically replace the destination only after success.
|
||||
3. Validate translated save → translated reload first, comparing defined state summaries and replay continuation.
|
||||
4. Where original-format compatibility is implemented, test translated save → original Factorio load, and original save → translated reload. Declare compatibility only for tested versions/data configurations.
|
||||
5. Test interruption, low storage, corrupt output, cancellation, repeated saves, and app termination. Preserve the last good save.
|
||||
6. Add Files import/export and clear local save selection. Continue to obtain explicit approval for any new private-file transfers outside previously authorized scope.
|
||||
|
||||
Exit gate: play, save, terminate, relaunch, and resume the same factory on the iPhone without lost state. Record interoperability separately from local round-trip success.
|
||||
|
||||
## 14. Phase K — performance and reliable builds
|
||||
|
||||
1. Establish a fixed benchmark world, camera, command replay, asset configuration, and device conditions.
|
||||
2. Profile simulation, indirect-call dispatch, bounds/address translation, allocation, renderer batching, texture memory, and generated C size/build time.
|
||||
3. Optimize measured hotspots. Re-run equivalence checks after changes to translated semantics, call dispatch, memory layout, or floating point behavior.
|
||||
4. For the initial small world, use provisional targets of sustained 60 simulation ticks/second and at least 30 rendered frames/second. Revise these explicitly if feasibility measurements require it; neither is currently achieved or promised.
|
||||
5. Run a 30-minute device session recording tick/frame percentiles, peak memory, thermal degradation, battery use, and crashes. Set memory budgets from observed safe device behavior.
|
||||
6. Pin dependencies, add one documented build command, preserve compiler manifests, and audit the final application for Steam dependencies and runtime code generation.
|
||||
7. Test clean installation, asset import, save migration policy, background/resume, offline use, and diagnostic export. Keep personal assets, saves, and signing material out of distributable tool sources.
|
||||
|
||||
Exit gate: reproducible signed development build plus a device acceptance report covering all target actions, saves, performance, known limits, and exact tested versions. Wider distribution is a separate decision.
|
||||
|
||||
## 15. Order, checkpoints, and feasibility decisions
|
||||
|
||||
Critical path: A → B → C/D → E → F → G → H → I → J → K. Discovery and runtime work may inform one another; revisit graph coverage after every new executable path. Do not finish the entire engine before validating a small end-to-end slice.
|
||||
|
||||
| Checkpoint | Concrete result | Decision if it fails |
|
||||
| --- | --- | --- |
|
||||
| Existing baseline | Reproducible RNG/header prototype | Fix provenance/build drift first |
|
||||
| General translation | Branching multi-function subsystem on iPhone | Reassess compiler design before translating more functions |
|
||||
| Runtime feasibility | Required memory/ABI model works within device limits | Revisit memory representation and supported scope |
|
||||
| Loader | Complete small-world state on Mac | Resolve data/runtime dependencies before graphics |
|
||||
| Simulation | Repeatable factory actions on both platforms | Find first state divergence |
|
||||
| Interactive slice | Touch-controlled rendered factory on phone | Profile and resolve platform boundary issues |
|
||||
| Persistence | Save/relaunch/resume succeeds | Correct serializer before broadening compatibility |
|
||||
| Acceptance | Sustained device session meets documented targets | Optimize measured bottlenecks or explicitly reduce scope |
|
||||
|
||||
The largest unknowns are dependency-graph size, indirect control flow, C++ unwinding/object ABI, scripting/prototype initialization, memory overhead, and renderer compatibility. If a gate shows an impractical amount of reconstruction, report the evidence and revise the plan with the user; do not substitute a mock game for the requested port.
|
||||
|
||||
## 16. Immediate next implementation batch
|
||||
|
||||
1. Add run IDs, timestamps, input hashes, and build identities to device reports.
|
||||
2. Freeze the current target and asset manifests; reproduce the current checks once.
|
||||
3. Reopen the unchanged ARM64 slice through REA MCP and export bounded dossiers for the three loader roots and their first required dependencies.
|
||||
4. Identify one small branching deserialization function with a tractable native oracle.
|
||||
5. Implement CFG/basic-block IR, integer flags/comparisons, and the branch forms needed by that function.
|
||||
6. Translate it, compare against original ARM64 behavior on macOS, and execute its AOT output on the iPhone.
|
||||
7. Publish a coverage/blocker report and select the next dependency from observed evidence.
|
||||
|
||||
This batch proves the essential next capability: progressing from isolated straight-line primitives to a real branching engine subsystem.
|
||||
|
||||
## 17. First implementation batch status (2026-10-20)
|
||||
|
||||
Completed: installed-data and target manifest tooling, identified device reports, initial bounded control flow/NZCV/select support, loader direct-callee recovery, and automatic translation of the branching MapHeader constructor with 20,000 native comparisons. Compiler fixtures pass 1,860,000 comparisons. See [translation-progress.md](translation-progress.md) for physical-device results and exact remaining scope. General guest-to-guest calls, real-save stream positioning for MapHeader, and complete world loading remain pending.
|
||||
|
||||
## 18. Stream positioning and guest-call batch (2026-10-20)
|
||||
|
||||
Implemented bounded direct guest calls/returns, a variable-length ZIP prefix API, and a restricted 2.0.77 scenario-envelope reader. The approved real save yields MapHeader at decoded offset 333; original ARM64 constructor and translated values agree. General engine initialization, scalable memory/import support, nonempty startup settings and complete world loading remain pending. See [save-layout-progress.md](save-layout-progress.md).
|
||||
|
||||
## 19. Allocation and initialization batch (2026-10-20)
|
||||
|
||||
Translated original TargetDeserialiser and its type-aware memory-pool constructor as a shared guest program. Added fixed configurable memory, a bounded 16-byte-aligned arena, an explicit x0-returning host adapter, mapped static-page identities, ASR/logical immediates and a restricted zero-vector store idiom. The original graph matches 512 desktop ARM64 object graphs; 66,048 new instruction comparisons and the existing 80,000 guest-program comparisons pass. Signed build 4 initializes all 64 pool objects on the iPhone using four allocations and 2,104 guest heap bytes. Same-launch save-header regressions pass. See [initialization-progress.md](initialization-progress.md).
|
||||
|
||||
This completes an initial allocation service, not general engine initialization or a complete allocator. MapDeserialiser integration, full image/global mapping, virtual calls, destruction/unwinding, MapGenSettings, entity loading, rendering and gameplay remain ahead.
|
||||
|
||||
## 20. MapDeserialiser integration batch (2026-10-20)
|
||||
|
||||
Translated the original MapDeserialiser C2 initialization CFG with its actual TargetDeserialiser C1 dependency and shared original pool constructor: 662 instructions in one bounded guest program. Added definitely-zero vector dataflow, zero Q-store pairs/writeback and bounded bzero. Desktop checks pass 512 original object graphs, supplied/owned target paths, nine truncations, eight original version-error branches and twelve allocation failures. Added 4,096 native vector-addressing comparisons; prior compiler and header regressions pass.
|
||||
|
||||
Signed build 5 passes the original initialization graph on the iPhone from both a controlled prefix and the approved actual save's prefix, consuming nine stream bytes and 10,712 guest heap bytes through twelve allocations. The full world remains unloaded. All new source input ranges now match complete exported REA byte observations, closing the prior batch's pending byte-export check. See [deserialiser-progress.md](deserialiser-progress.md).
|
||||
|
||||
Next: MapGenSettings and subsequent Map::loadData dependencies. The current modules are isolated initialization probes, not a persistent integrated loader. General virtual dispatch, destruction/unwinding, full static-image mapping, object references, entities, rendering and gameplay remain pending.
|
||||
@@ -0,0 +1,54 @@
|
||||
# Loader allocation and initialization (2026-10-20)
|
||||
|
||||
Signed **build 4** runs the original TargetDeserialiser/memory-pool initialization graph on the iPhone 15 Pro, through ARM64 → WASM → wasm2c → native iOS. Its report records four allocations, a capacity of 64, 64 initialized targets and **2,104 bytes** consumed by the aligned guest arena. The version constructor, both MapHeader branches and the actual-save header probe also pass in the same launch. The save's header remains at decoded byte 333 with three raw values of 720001. This initializes one isolated loader dependency; it does not load a world or provide gameplay.
|
||||
|
||||
## Original code and adapters
|
||||
|
||||
| Input | Address | Bytes | SHA-256 |
|
||||
| --- | --- | --- | --- |
|
||||
| TargetDeserialiser C2 normal body | `0x101ab7c74` | 108 | `2ada8a5d403d8d30f9bc053f3cad0e74d08103d6c96bcdbf4db44d481ba6d4d0` |
|
||||
| TypeAwareMemoryPool C2 normal CFG | `0x10244a4bc` | 1260 | `0e450e3ad613dded236f6627d78a07894d48b46e845baa40877d6929043b171a` |
|
||||
| Source vtable identity range | `0x102fc2d98` | 32 | `c991e960829a78707ab67384d3dbf5d296aaa8fc9f33878ae7c1e7571f779dce` |
|
||||
|
||||
The complete source ARM64 SHA-256 is pinned to `2cb4de127b50d54c6c5666acfef2263fae16d11a3361ad39353ecf8a66a4d9c7`. These inputs are checked before generation. The translated program contains 342 original instructions and shares guest registers, stack and an instruction budget between both functions.
|
||||
|
||||
The allocator is a **host-side service**, not translated operator new. It returns 16-byte-aligned offsets into a bounded per-run arena, leaves bytes uninitialized and reports exhaustion explicitly. A new opt-in import ABI snapshots the guest register context and reloads only x0 after a successful call. Other guest registers retain their local values; host calls invalidate the compiler's NZCV facts. Failed calls abort the run, with partial guest state discarded when the module is freed.
|
||||
|
||||
Guest memory is a fixed 1 MiB, with a 64 KiB arena at offset 131072, an isolated stack and a controlled output object. Addresses are checked at 64-bit width before narrowing. The ADRP source page `0x102fc2000` maps to guest page 65536. The constructor stores the resulting vtable **identity** at guest offset `0x10da8`; no vtable bytes, native pointers or callable virtual methods are installed there. Virtual dispatch remains unsupported.
|
||||
|
||||
New instruction support is limited to non-flag-setting logical immediates, ASR immediates, declared ADRP pages, BRK traps and contiguous MOVI-zero/Q-store sequences. The vector idiom cannot transport values across calls or branches. General SIMD remains unsupported.
|
||||
|
||||
The constructors' exception landing pads at `0x101ab7ce0..0x101ab7d0c` and `0x10244a9a8..0x10244a9d4` are excluded. Delete and length-error imports return an explicit unsupported-service status; allocation failure returns status 5. No C++ exception unwinding, destructor or general free service is supplied. No startup, Steam, world or rendering code is introduced into the iOS binary.
|
||||
|
||||
## Validation
|
||||
|
||||
- **512 original ARM64 object-graph comparisons**, varying guest object and heap offsets. Each compares the complete 144-byte output object and the complete allocated graph, normalizing native heap pointers and the vtable identity. Guards include alignment gaps, padding, unallocated RAM and preserved x19–x30/SP.
|
||||
- Eight insufficient-heap cases exercise aborts before each allocation can succeed; exact 2,104-byte capacity succeeds. Overflow-size, zero-size allocation, invalid arena/context and instruction-budget checks also pass.
|
||||
- **66,048 native instruction comparisons** cover 129 ASR/logical-mask forms, signed input edges and randomized values. Extended-memory writes, near-end accesses, addresses above 4 GiB and twelve rejection cases pass.
|
||||
- Existing **80,000 guest-program comparisons** and **20,000 original MapHeader comparisons** still pass after the shared compiler changes.
|
||||
- Physical build 4 reports share launch ID `D0C96658-439A-466B-99A8-768D93E86E57`, build identity `Oct 20 2026 19:31:52` and report time `2026-10-20T15:33:10Z`.
|
||||
- The signed executable's imports/dependencies contain no Steam integration, and desktop oracle symbols are absent.
|
||||
|
||||
The desktop oracle executes copied original ARM64 words, relocating only direct BL calls and the ADRP/ADD vtable identity reference. The iOS target links only generated C and runtime adapters. Finite comparisons cover constructor initialization and allocation aborts; they do not establish arbitrary mutation paths or whole-engine equivalence.
|
||||
|
||||
## Reproduce
|
||||
|
||||
```sh
|
||||
python3 tools/build_target_deserialiser.py build/factorio.arm64
|
||||
python3 tests/check_a64_initialization.py
|
||||
python3 tests/check_a64_program.py
|
||||
python3 tools/build_map_header.py build/factorio.arm64
|
||||
python3 tools/package_ios.py
|
||||
```
|
||||
|
||||
Generated sources, function/call manifests and desktop validation are under `build/target-deserialiser/`. Downloaded device reports are `iphone-*-result.json`; `python3 tools/check_target_device.py` checks them against the signed build and existing desktop save result, then writes `device-verification.json`. Original instructions and generated game code remain in ignored local artifacts.
|
||||
|
||||
## REA evidence and resolved provenance check
|
||||
|
||||
REA observed the original constructor assembly in records `ev_1a00dc8f1f23ce6c1e54f8d189d163fbdd012621890e0288643b8f83a8b11611` (TargetDeserialiser) and `ev_b08fa6c3b275200505c3e78c71046526444b5c0ec603ef09d18d5db53dbf0eab` (memory pool). Complete MCP responses are retained in `evidence/1792510440817-2-procedure_assembly.json` and `evidence/1792510440863-3-procedure_assembly.json`. The local evidence index combines these unchanged records with the previous export in `evidence/target-initialization-final.json`.
|
||||
|
||||
The three source ranges were initially hash-pinned directly from the unchanged Mach-O and independently decoded with LLVM; the extra REA byte export was pending at the end of build 4. **Resolved in the build 5 batch:** complete matching REA `read_bytes` observations are now exported in `evidence/deserialiser-final.json`. `tools/check_initialization_rea.py` validates all three ranges and writes a passing `build/target-deserialiser/rea-provenance.json`. See [deserialiser-progress.md](deserialiser-progress.md) for the records. Later compiler support extends the original contiguous zero-store idiom to definitely-zero facts checked across control flow.
|
||||
|
||||
## Next dependency
|
||||
|
||||
The next step from build 4 was integration into the original MapDeserialiser constructor, including its additional allocations, bzero and version-error handling. **Build 5 now completes that isolated initialization probe**; see [deserialiser-progress.md](deserialiser-progress.md). Next is MapGenSettings at the beginning of Map::loadData. Full image relocation, object reference reconstruction, nonempty startup settings, entities, simulation, graphics, sound and touch controls remain unimplemented.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Factorio iPhone port: initial investigation
|
||||
|
||||
Status: two RNG functions, the map-version constructor, and a branching map-header constructor statically translated to WASM and compiled into a native, Steam-free iOS prototype. The prototype reads save-version headers and runs on the connected iPhone 15 Pro. No playable game port implemented. See static-translation.md, world-loading.md and translation-progress.md for reproduction and exact validation scope.
|
||||
|
||||
## Installed target
|
||||
|
||||
- Steam installation: `~/Library/Application Support/Steam/steamapps/common/Factorio/factorio.app`.
|
||||
- Version: 2.0.77, read from the installed Info.plist.
|
||||
- Executable SHA-256: `00b014d78df07e05ab1cee853904c8f7b3814ca652c043461a9d49fbad46cdf6`.
|
||||
- REA identifies a universal Mach-O with x86_64 and arm64 slices.
|
||||
- Selected ARM64 slice: offset 106659840, size 101261968 bytes; build platform MACOS, minimum OS 11.0, SDK 11.0.
|
||||
- Adjacent `factorio.dSYM/Contents/Resources/DWARF/factorio` exists. Its ARM64 UUID matches the inspected executable: `EBA8DC1C-9981-3657-8656-16F6A8424198`. Full type/source metadata recovery has not been performed.
|
||||
|
||||
## Porting implications
|
||||
|
||||
The ARM64 slice is a macOS executable, not an iOS executable. Its dependency inventory includes AppKit, Cocoa, Carbon, ForceFeedback, desktop OpenGL/OpenCL/OpenAL, and `@loader_path/libsteam_api.dylib`. A platform marker change or IPA wrapper alone cannot replace these interfaces.
|
||||
|
||||
Metal and GameController are also linked. This is evidence of dependencies only; it does not establish the renderer's architecture, active execution paths, or reusable source availability.
|
||||
|
||||
A source-based port needs an iOS build of the simulation engine plus platform adapters for application lifecycle, graphics presentation, audio, touch/controller input, writable storage, threading and networking. Dependencies must be built for iOS. The existing Lua/data files do not by themselves provide a replacement for the native engine.
|
||||
|
||||
A binary-based reconstruction first needs focused recovered interfaces and behavior specifications. Pseudocode is not original C++ and does not by itself produce a buildable engine. Save compatibility, multiplayer determinism, mod support and performance remain unverified.
|
||||
|
||||
## Implementation milestones
|
||||
|
||||
1. Binary reconstruction is the selected path. The RNG prototype establishes a bounded translation pipeline; recover startup and simulation dependencies next.
|
||||
2. Build an iOS application hosting an engine boundary, with Metal presentation and touch commands. A shell alone must not be presented as a Factorio port.
|
||||
3. Implement and validate one vertical slice: load a small world, advance simulation, display entities, place a miner and transport its output.
|
||||
4. Validate save/load, deterministic behavior where required, device memory, sustained frame rate and thermal behavior on a specified iPhone.
|
||||
5. Expand features only after the vertical slice works.
|
||||
|
||||
## Evidence and checks
|
||||
|
||||
`rea-evidence.json` is the exported REA bundle. Mach-O inspection parent: `ev_83862cd660d0d09fdefd22e0fe60f714fd098b266c655142eb1a30a812ebbefc`. Architecture, build metadata and dependency pages were inspected. Import/export completeness is explicitly not guaranteed by REA; symbol counts do not prove recovered functions or complete source.
|
||||
|
||||
`xcodebuild -showsdks` lists iOS 26.5 and iOS Simulator 26.5 SDKs. Unsigned and locally signed device builds compile. The app is installed and running on the iPhone 15 Pro. The macOS native equivalence harness passes 876,544 RNG calls, 1,024 RNG seed constructor calls and 20,000 map-version constructor comparisons; unsupported code and changed source bytes are rejected. The phone's native map-version self-test and generated ZIP probe also pass.
|
||||
|
||||
The initial REA procedure search failed because the Hopper launcher relies on Spotlight. A reversible `/Applications/Hopper Disassembler.app` symlink to the existing user-installed app resolves vendor-helper discovery. No indexing settings or saved MCP client or REA configuration were changed. Full analysis took 5m27s, exceeding the original MCP session's 120s startup timeout and leaving retained cleanup state; the generated document was manually closed. The Hopper demo cannot save databases. A separate MCP client with a 600s timeout uses a locally extracted, unmodified ARM64 slice (`build/factorio.arm64`, SHA-256 `2cb4de127b50d54c6c5666acfef2263fae16d11a3361ad39353ecf8a66a4d9c7`) for focused function evidence. See evidence files for the actual success/failure of each operation.
|
||||
|
||||
Steam integration is excluded from the target. Current artifact import and dependency checks pass. Whole-game Steam exclusion remains dependent on lifting the engine and verifying the local startup graph; see [steam-boundary.md](steam-boundary.md).
|
||||
|
||||
The longer isolated REA run succeeded. Canonical function Evidence is retained in `evidence/factorio-functions-final.json`; `tools/check_rea_rng.py` confirms all three code/constant ranges match the original installed binary's ARM64 slice. The successful isolated session was saved to `build/factorio-rea.snapshot.json` and closed. Its results identify an inlined Steam startup path in `GlobalContext::init`, so omitting only the standalone Steam factory would be incomplete.
|
||||
@@ -0,0 +1,82 @@
|
||||
# Save stream positioning and guest calls
|
||||
|
||||
2026-10-20. This batch connects the translated map-header constructor to a bounded real-save probe. It does not load a playable world.
|
||||
|
||||
## Results and authority
|
||||
|
||||
The approved `_autosave1.zip` has version 2.0.77.0, flag 0. The recovered metadata layout places MapHeader at decoded map-stream offset **333** and the next byte after its three fields at **357**. The three raw fields are **720001 / 720001 / 720001**. Their individual meanings are not yet established.
|
||||
|
||||
The offset is derived from the instruction-level serialized-field order described below. It is not a recorded full-game execution trace. At that derived position, the original ARM64 MapHeader constructor and the translated constructor agree on all three values, with exactly three eight-byte reads. `build/save-layout/desktop-real-save.json` records the source-save hash, offsets, values and native comparison. Signed build 3 ran on the iPhone 15 Pro and returned the same offsets and values from the same approved save. Its input SHA-256, launch ID and probe ID match the companion reports. Results and the signed binary hash are retained in `build/save-layout/device-verification.json`.
|
||||
|
||||
The scenario envelope is a **host-side reconstruction** in `runtime/save_layout.c`; it is not translated original Scenario code. MapVersion and MapHeader execute original byte-guarded translated functions. The envelope adapter deliberately supports only 2.0.77.0/flag 0 and an empty dictionary for startup PropertyTree settings. Other versions, nonempty/other settings, malformed booleans, excessive lengths, recursion and read budgets fail explicitly.
|
||||
|
||||
## Recovered stream order
|
||||
|
||||
1. MapDeserialiser reads four little-endian uint16 version components and one flag byte. Its version object starts at object offset `0x18`. The selected valid-version path initializes helpers without consuming additional serialized fields.
|
||||
2. The normal save path calls ScenarioExecutionContext's deserializing constructor before Map::load. ScenarioLocation consumes three compact-length strings; Difficulty consumes one byte.
|
||||
3. ScenarioExecutionContext reads booleans, a string, additional booleans, a compact three-component Version, fixed-width metadata fields, a vector of ModIDs, and startup PropertyTree settings. ModIDs contain a string, three compact uint16 version components and a four-byte field.
|
||||
4. The modern path reads two EndingInfo values and a final boolean. EndingInfo includes LocalisedStrings, a vector of LocalisedStrings and an ordinary string. LocalisedString starts with a type byte: zero consumes no further bytes; other values read a string, a one-byte child count, and recursive child strings.
|
||||
5. The selected path then calls Map::load and Map::loadData. Map::loadData begins by constructing MapHeader. The retained Map constructor assembly does not preserve/use its incoming reader before its first call; its observed constructor dependencies initialize engine state rather than this serialized envelope.
|
||||
|
||||
Compact string/vector lengths use a byte unless it is `0xff`, which introduces a little-endian uint32. Compact Version/ModVersion components use the same sentinel with a uint16 extension. These two encodings must not be conflated. This batch does not implement recursive PropertyTree settings; rejecting them avoids assuming their complete wire representation.
|
||||
|
||||
The envelope adapter reports the next required prefix size when input is short. The ZIP adapter retries bounded prefixes, up to 128 reads and a 1 MiB prefix budget. No full extraction is required. Whole-member CRCs, the remainder of the map stream, object references, mods, entities, or world integrity are not validated.
|
||||
|
||||
## Compiler/runtime changes
|
||||
|
||||
`tools/a64_program.py` adds a multi-function dispatcher over declared original instruction addresses. It shares the guest's registers, stack, memory and one instruction budget across direct BL calls, tail branches and RET. Original stack save/restore instructions implement nested return-address preservation. No native recursion, runtime compilation or JIT is used.
|
||||
|
||||
RET accepts the external return sentinel or known guest-call continuations. Unknown computed returns produce status 4; external sentinels aliasing declared code are rejected. Indirect calls and arbitrary computed returns remain unsupported. The call graph/flag analysis is conservative; a rejected program is not automatically evidence of invalid original game code. Host stream errors still return an explicit status rather than implementing C++ unwinding.
|
||||
|
||||
MapHeader now enters through the original four-byte public C1 tail branch at `0x10010582c`, then reaches its 332-byte C2 body at `0x1014c6aac`. Both inputs are byte guarded. The desktop oracle relocates the alias's B to its copied body and redirects only declared stream reads. The iOS library excludes the oracle.
|
||||
|
||||
## Verification
|
||||
|
||||
- **80,000 native comparisons** for hand-authored multi-function instruction fixtures: nested calls, recursive LR saves, tail calls, shared-memory mutation, preserved callee-saved registers, memory guards, exact shared-budget exhaustion and invalid return/entry statuses. These are compiler tests, not recovered game functions.
|
||||
- **20,000 original MapHeader comparisons** now run through its original public entry and body in the multi-function dispatcher. Modern/legacy paths and failure behavior still pass.
|
||||
- **62 bounded ZIP prefix cases** compare exact decoded bytes and check output guards/failure atomicity across stored/deflated outer members, data descriptors, buffer boundaries, empty/oversized requests and truncated streams.
|
||||
- **140 scenario-envelope cases** cover variable string lengths, sentinel encodings, mod counts, nested LocalisedStrings, every truncation of a controlled fixture, malformed values, unsupported settings and recursion/size limits.
|
||||
- Existing MapVersion equivalence and fifteen archive-version fixtures pass.
|
||||
- All six translated code/constant/entry ranges match complete exported REA byte evidence.
|
||||
- The real-save constructor comparison passes at offset 333 on macOS. The iPhone real-save probe, modern/legacy constructor self-tests and version regression pass in one launch. The signed artifact passes a Steam import/dependency audit and contains none of the desktop native-oracle symbols.
|
||||
|
||||
Reproduce after the RNG build:
|
||||
|
||||
```sh
|
||||
python3 tools/build_version.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tests/check_a64_program.py
|
||||
python3 tools/build_map_header.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tests/check_save_prefixes.py
|
||||
python3 tools/build_save_layout.py
|
||||
python3 tests/check_save_layout.py
|
||||
python3 tools/check_rea_rng.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio' evidence/save-layout-final.json --include-version --include-map-header --include-map-header-entry
|
||||
build/save-layout/verify_native '/absolute/path/to/save.zip'
|
||||
python3 tools/package_ios.py
|
||||
```
|
||||
|
||||
## REA evidence
|
||||
|
||||
Complete canonical bundle: `evidence/save-layout-final.json` (64 records). The first session lost its Hopper connection after the main constructor graph was captured; its evidence was preserved in `save-layout-pass1.json`. The restarted session completed the remaining readers and closed successfully into `build/save-layout-rea.snapshot.json`. Matching LLVM symbol/instruction checks were used while the second analysis prepared; they are supplementary, not REA records.
|
||||
|
||||
| Observation | REA evidence ID |
|
||||
| --- | --- |
|
||||
| MapDeserialiser constructor | `ev_3143ceb6dee72f0a56235e02cd4a5ed942c3826860ea21c315847bb186053650` |
|
||||
| ScenarioExecutionContext serialized order | `ev_a4333ddb98e9e43cd6eacf68a19936091fb88eb905df0037617599b1f64952c9` |
|
||||
| ScenarioLocation strings | `ev_17cb5c99e94825134f5ec68367c0dc43d89c520bffd31830815fd785006d3cf4` |
|
||||
| String loader | `ev_36139f8e3f048ef8ad9744727fa699868cea0cb6c45785a552284adf3b3091a9` |
|
||||
| Boolean validation | `ev_8654783b6211640b058d9dd0904eef6b49fcd73230410956eb6b4aff686835cf` |
|
||||
| Version component widths | `ev_705e76d3df68f5080b2401c46ae2a8c9f0eff826727469271c101c8d73be0d68` |
|
||||
| ModVersion component widths | `ev_eedbdacc998513468e3f7550844f2421f16ae817f129f10863cc7810aa002b93` |
|
||||
| EndingInfo order | `ev_8eb93855422407b11b683ef67324e000b41916c072b18543e9b94bcffba8d1dc` |
|
||||
| Recursive LocalisedString | `ev_8c14f94ef7ba1133a664ab94262e1d527f6e3cd687a80d0fc61a8f7f3a1cf9df` |
|
||||
| LocalisedString vector count | `ev_b723c046c47b29ec10d022700ed74861aad1449048c57233526c6083c83f105f` |
|
||||
| Public MapHeader entry bytes | `ev_525aeb761646ca31af2fbacfc51ec786e6036b6259fd09505063144d0a5feba1` |
|
||||
|
||||
Hopper merged Difficulty's entry with a preceding procedure. Its assembly includes the relevant block at `0x1001bf6ac`; procedure names and reported lengths alone are not reliable function-boundary authority. Source bytes, decoded call targets, matching symbols and the specific observed blocks were used together.
|
||||
|
||||
## Next engine work
|
||||
|
||||
1. Translate the compact Version/ModVersion readers and other metadata utilities as composable guest functions, reducing reconstructed host behavior where useful.
|
||||
2. Implement a scalable guest memory image and explicit allocator/global/import contracts needed by the actual MapDeserialiser and Map constructors.
|
||||
3. Expand supported PropertyTree settings using recovered wire cases and validated guest code.
|
||||
4. Recover and translate MapGenSettings and the earliest Map::loadData state allocations. Entity/world reconstruction, simulation, graphics, audio and controls remain ahead.
|
||||
@@ -0,0 +1,89 @@
|
||||
# Static-translation research (ARM64 → WASM → native)
|
||||
|
||||
> This is the earlier, function-by-function translation track. The playable port uses the
|
||||
> retargeted original binary instead; see [../native-port.md](../native-port.md). Commands below
|
||||
> are run from the repository root.
|
||||
|
||||
The intended target is local single-player Factorio, without Steam integration or runtime JIT. Current deliverable: **two RNG functions, the original map-version and map-header constructors, and the MapDeserialiser/TargetDeserialiser/memory-pool initialization graphs translated to WebAssembly and compiled through wasm2c into a native iOS prototype**. The application can read version headers, initialize the original deserializer from a save's version prefix and, for a limited 2.0.77 profile, locate and decode map-header fields from save ZIPs. Full world loading, rendering, simulation and gameplay are not implemented.
|
||||
|
||||
Pipeline: local ARM64 Mach-O → REA-inspected instructions → byte-guarded WASM translation → wasm2c → Clang → native iOS application. RNG functions use reviewed specialized translations; `MapVersion` uses the new bounded automatic integer lifter. The lifter now also supports bounded integer control flow, comparison flags and immediate branches, verified with separate native instruction fixtures. This is not yet a whole-program ARM64-to-WASM compiler. Glue5 supplies the model for the WASM-to-native half; it expects an existing WASM game build and does not supply the missing Factorio binary lifter.
|
||||
|
||||
## Reproduce on Apple Silicon macOS
|
||||
|
||||
Requires Xcode, Python 3, CMake, Git and xcodegen. WABT is built locally at a pinned revision. The following commands fetch only tool sources:
|
||||
|
||||
```sh
|
||||
git clone --depth 1 --branch 1.0.42 https://github.com/WebAssembly/wabt.git third_party/wabt
|
||||
git -C third_party/wabt submodule update --init third_party/picosha2
|
||||
```
|
||||
|
||||
Then supply your installed game executable:
|
||||
|
||||
```sh
|
||||
python3 tools/build_rng.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tests/check_lift_guards.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tools/build_version.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tools/build_map_header.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tools/build_save_layout.py
|
||||
python3 tools/build_target_deserialiser.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tools/build_deserialiser.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tests/check_a64_guards.py
|
||||
python3 tests/check_a64_cfg.py
|
||||
python3 tests/check_a64_program.py
|
||||
python3 tests/check_a64_initialization.py
|
||||
python3 tests/check_a64_zero_vectors.py
|
||||
python3 tests/check_save_prefixes.py
|
||||
python3 tests/check_save_layout.py
|
||||
python3 tests/check_save_headers.py
|
||||
python3 tools/package_ios.py
|
||||
```
|
||||
|
||||
The specialized lift rejects unrecognized function or constant bytes before producing artifacts. Supported inspected build: Factorio 2.0.77 ARM64. Original installation files are read only. Generated game code and evidence stay in ignored local directories.
|
||||
|
||||
Outputs:
|
||||
|
||||
- `build/rng/factorio_rng.wasm`, generated C/header and `libfactorio_rng_ios.a`.
|
||||
- `build/rng/manifest.json`: source identity, addresses and hashes.
|
||||
- `build/rng/verification.json`: native equivalence and Steam import audit.
|
||||
- `build/version/factorio_version.wasm`, generated C and `libfactorio_version_ios.a`.
|
||||
- `build/map-header/factorio_map_header.wasm`, generated C and `libfactorio_map_header_ios.a`.
|
||||
- `build/map-header/device-verification.json`: both version-dependent MapHeader paths pass on the iPhone using controlled payloads; same-launch version and real-save header checks also pass.
|
||||
- `build/save-layout/device-verification.json`: build 3 reads the actual save's MapHeader at decoded offset 333; its three raw fields match the Mac/original-constructor result. Supported envelope: 2.0.77.0, flag 0, empty startup settings.
|
||||
- `build/target-deserialiser/libfactorio_target_ios.a`: original loader initialization graph with a bounded guest heap.
|
||||
- `build/target-deserialiser/device-verification.json`: build 4 initializes 64 pool objects with four allocations on the iPhone; same-launch version, map-header and real-save regressions pass.
|
||||
- `build/deserialiser/device-verification.json`: build 5 initializes the original MapDeserialiser from the actual save's nine-byte prefix; 12 allocations, 10,712 guest heap bytes, same-launch regressions pass.
|
||||
- `build/FactorioRNG-unsigned.ipa`: historical filename; now includes the RNG and save-header prototype, requires signing to install.
|
||||
- `build/ios-verification.json` and `build/ios-build.log`.
|
||||
|
||||
## Verified scope
|
||||
|
||||
`RandomGenerator::getInt()` and its unsigned-integer seed constructor are translated. The verification executable compares 876,544 generated values and their mutated state against original ARM64 instruction words, plus 1,024 constructor calls. It covers 6,848 starting states and five linear-memory offsets, including unaligned states and the end of memory. Guard bytes check for unintended writes. These finite comparisons are evidence, not proof of equivalence for every state.
|
||||
|
||||
The native oracle is linked only into macOS verification executables. The iOS app links wasm2c output and runtime, with ordinary allocated data memory and explicit bounds checks. It does not load original machine code or compile WASM at runtime. The RNG prototype was installed and launched on the connected iPhone 15 Pro; the user confirmed it worked. The updated save-header prototype also runs on that device: its native constructor self-test and generated-archive probe pass. Results are in `build/version/iphone-self-test.json` and `build/version/iphone-save-probe.json`.
|
||||
|
||||
The automatically lifted version constructor passes 20,000 native comparisons; ZIP/zlib header reading passes fifteen fixtures. It reads version 2.0.77 from the user's autosave locally on macOS. With explicit user approval, the actual autosave was also copied to the iPhone; its native header reader returned version 2.0.77 successfully. See [world-loading.md](world-loading.md) for exact scope, REA provenance, limitations and next entry points.
|
||||
|
||||
## REA workflow
|
||||
|
||||
REA MCP performs artifact inspection and focused disassembly. Initial Mach-O evidence is in `rea-evidence.json`; subsequent local MCP responses are in `evidence/`. `evidence/factorio-functions-final.json` retains successful RNG disassembly, exact byte reads, and Steam/startup dossiers. LLVM's tools independently supplied the initial bounded instruction listings and debug UUID comparison. Evidence from these tools is kept distinct.
|
||||
|
||||
The extracted ARM64 image is byte-identical to the original universal binary's ARM64 slice. After exporting the canonical REA bundle, validate its three byte ranges against the installed input:
|
||||
|
||||
```sh
|
||||
python3 tools/check_rea_rng.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio' evidence/factorio-functions-final.json
|
||||
```
|
||||
|
||||
This passes and writes `build/rng/rea-provenance.json`. The completed isolated REA session was exported to `build/factorio-rea.snapshot.json` and closed successfully.
|
||||
|
||||
The installed Hopper vendor helper requires `/Applications/Hopper Disassembler.app` when Spotlight is unavailable. A symlink there pointing to the user-installed copy of Hopper (in `~/Applications`) resolves this. No Hopper binary was changed. The original full analysis took 5m27s, beyond REA's default 120s timeout; the demo also cannot save `.hop` databases. `tools/rea-session.mjs` starts a separate REA MCP process with a 600s startup timeout, without changing saved MCP client or REA configuration. Pass the vendor `Contents/MacOS/hopper` helper, not the GUI executable.
|
||||
|
||||
## Next engine milestones
|
||||
|
||||
The detailed staged plan is in [implementation-plan.md](implementation-plan.md). Completed work and limits are in [translation-progress.md](translation-progress.md), [save-layout-progress.md](save-layout-progress.md), [initialization-progress.md](initialization-progress.md) and [deserialiser-progress.md](deserialiser-progress.md).
|
||||
|
||||
1. Lift the startup graph and replace its inlined Steam initialization with local services. REA identified the interface boundary and an inlined copy in `GlobalContext::init`; changing the factory alone is insufficient.
|
||||
2. Extend the bounded integer lifter beyond straight-line code: full guest memory mapping, indirect-call dispatch, relocations, TLS, atomics, SIMD and C++ exception handling. Initial bounded direct guest calls, tail calls and known returns are implemented in `a64_program.py`. Initial bounded branches, NZCV support and original MapHeader constructor validation pass.
|
||||
3. Translate world loading and simulation, then native graphics/audio/touch adapters.
|
||||
4. Validate a small playable world and save/load on an actual iPhone before expanding compatibility.
|
||||
|
||||
See [porting-notes.md](porting-notes.md) for target identity and [steam-boundary.md](steam-boundary.md) for Steam scope.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Steam-free single-player target
|
||||
|
||||
User-directed scope: exclude Steam integration; retain basic local gameplay. Steam matchmaking, cloud saves, achievements, rich presence, overlay and Steam networking are outside this port's target. The installed macOS game is not modified.
|
||||
|
||||
The current translated RNG library and iOS prototype contain no Steam imports or Steam dynamic dependency. `tools/build_rng.py` rejects Steam undefined symbols; `tools/package_ios.py` checks both imports and linked libraries before packaging.
|
||||
|
||||
## Original binary observations
|
||||
|
||||
REA Mach-O import inventory observed eleven direct Steam imports, including `SteamAPI_Init`, callbacks, shutdown and SteamInternal interface acquisition. Import coverage is not exhaustive, and virtual interface calls extend beyond these named imports.
|
||||
|
||||
Independent LLVM symbol inspection found `SteamContextInterface`, `SteamContext`, and `DummySteamContext`. REA/Hopper subsequently confirmed the factory disassembly and decompilation. The interface factory is at ARM64 VA `0x100267594` (`SteamContextInterface::createContext(bool, bool)`). Its observed branches are:
|
||||
|
||||
- Nonzero second argument (`w1`) selects an object referencing `DummySteamContext`'s vtable.
|
||||
- With the second argument zero, bit zero of the first argument (`w0`) branches to a base-interface object at `0x100267640`, without entering the Steam initialization block.
|
||||
- The other branch contains calls into the Steam path and constructs `SteamContext` if initialization succeeds.
|
||||
|
||||
Parameter names and caller-level meaning have not yet been established. These observations do not prove full game startup through either optional context. The correct integration point is the factory/interface boundary, not a blanket success-returning replacement for every Steam import.
|
||||
|
||||
REA also found that the Steam initialization stub has two observed callers: the factory and `GlobalContext::init` at `0x100161584`. The latter contains an **inlined** Steam initialization block beginning at `0x100161608`. Editing only the out-of-line factory would miss it. At `0x100161604`, startup branches on `GlobalContext::headlessMode`; headless startup constructs the base context without Steam. The graphical port must select local services independently of headless mode, while preserving graphical startup. Whole-function body ranges and complete indirect-reference classification are unavailable from Hopper's public API, so these observations are not a complete reachability proof.
|
||||
|
||||
## Retained REA evidence
|
||||
|
||||
Canonical bundle: `evidence/factorio-functions-final.json`, ARM64 slice SHA-256 `2cb4de127b50d54c6c5666acfef2263fae16d11a3361ad39353ecf8a66a4d9c7`.
|
||||
|
||||
- Factory dossier: `ev_4ebc9cbefaeb34d2e4f642c8cfa48d5035442aa80f6a438bf6481d215d1fe694`.
|
||||
- Steam initialization stub/callers: `ev_c2713194813cae2a2677b7d73a284f9f99b4b9214e59f58cf8ee2a0ffc159f09`.
|
||||
- Global startup dossier: `ev_7f2a2b88000ba82d85771c0f368eae9cf0a1f071beb7d73506ba6bbf3c0a22eb`.
|
||||
- Base `isSteamApiInitialized()` assembly returns zero: `ev_0b47cba381075942160840ec0d392a8f05803c3c8a2ebb672bb01c4a282f4a5b`.
|
||||
|
||||
The interface includes cloud storage, locale, networking, callbacks, on-screen keyboard, stats and achievements. Local replacements must preserve actual method contracts and object layout once lifted callers use them. Returning invented success or a fake Steam identity would hide missing behavior; the port should expose unavailable optional services and use local file storage and native text entry.
|
||||
|
||||
## Remaining work
|
||||
|
||||
Replace the inlined startup integration when that engine function is lifted, audit optional-service checks, and prove no Steam-only branch is reachable from the chosen local startup. A full translated engine does not exist yet, so Steam exclusion has only been verified for the current prototype.
|
||||
@@ -0,0 +1,64 @@
|
||||
# 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.py` rejects 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.json` and `assets.json`. Save compatibility and the gameplay configuration remain unselected.
|
||||
- `tools/a64_lift.py` now 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 retain `run(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.py` automatically translates the original 332-byte / 83-instruction `MapHeader::MapHeader(MapDeserialiser&)` at `0x1014c6aac`. It has six direct read call sites but performs three reads per successful invocation. The native memory-stream boundary replaces `readOrThrow`; arbitrary C++ unwinding remains unimplemented.
|
||||
- `runtime/map_header_bridge.c` exposes 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. Recovering `MapDeserialiser` initialization/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 widens `0xffffffff` to `UINT64_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.json` retains 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:
|
||||
|
||||
```sh
|
||||
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
|
||||
|
||||
1. 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.
|
||||
2. Add general guest-to-guest calls so translated constructors compose without implementing each callee as a native bridge.
|
||||
3. Recover the Map constructor's required allocations/global references and translate its smallest reachable dependencies.
|
||||
4. 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](save-layout-progress.md). The original batch status above is historical.
|
||||
@@ -0,0 +1,55 @@
|
||||
# World-loading translation: first stage
|
||||
|
||||
The iOS prototype now runs an automatically translated `MapVersion::MapVersion(Deserialiser&)` constructor. It reads the save's version header. It does **not** load entities, surfaces, mods, prototypes, or a playable world.
|
||||
|
||||
## Translation and authority
|
||||
|
||||
`tools/a64_lift.py` decodes ARM64 instruction words and emits WAT for a bounded straight-line integer subset. `tools/build_version.py` compiles the original constructor at `0x10150bf28`, 192 bytes / 48 instructions, with body SHA-256 `b9254262cde89c9e79a9fdf6d700860ff9fd623d53c576efaf431600096c8a9b`. A changed body, unknown call, unsupported instruction, missing return, or extra bytes after return fails compilation.
|
||||
|
||||
Registers remain 64-bit. Guest addresses are checked before conversion to the prototype's 64 KiB WASM memory. The compiler supports only the declared execution model; it does not yet implement arbitrary branches, SIMD, native imports, global relocation, indirect calls, or exceptions. ARM encoding reference: [Arm A64 ISA](https://developer.arm.com/documentation/ddi0602/2026-03).
|
||||
|
||||
REA confirms the exact constructor bytes and instructions in `evidence/world-loading-final.json`:
|
||||
|
||||
- Constructor dossier: `ev_5b212f1169e5fff89ef9e6675af58a9df4616c5260c451a6d403a822935e36d0`.
|
||||
- Complete 192-byte read: `ev_e17c26cc53fcb5bc8e04b1342129594def2f2646cc186f462e281e394b436d45`.
|
||||
- `Deserialiser::readOrThrow` dossier: `ev_31eb669fe011e10190a6f9c1aa32ba33c2ad5b5a38cb37c46db03066d36f33b2`.
|
||||
|
||||
The constructor makes five reads of 2, 2, 2, 2 and 1 bytes. It packs four little-endian 16-bit components into a 64-bit field and stores the final byte separately. The final byte's meaning is not established. The original `readOrThrow` dispatches through a stream virtual method and throws a C++ exception on a short read. This bounded prototype replaces that host boundary with a memory stream and an explicit error status. It does not claim to preserve C++ exception unwinding for arbitrary game code.
|
||||
|
||||
## Archive adapter
|
||||
|
||||
`runtime/save_header.c` reads the ZIP central directory, locates `level.dat0` (or `level.dat`), handles stored or deflated ZIP entries, and decodes the nested zlib stream's first nine bytes. It streams bounded prefixes instead of extracting the save or loading the whole world into memory. It rejects ambiguous roots, encryption, unsupported central-directory layouts, invalid offsets and malformed compression. Each compressed/decoded prefix is limited to 1 MiB.
|
||||
|
||||
This is a **header probe**, not whole-archive validation: it does not verify the entire member CRC or the rest of the zlib/map stream. ZIP64 central directories and encrypted archives are unsupported. The generated test archive contains a header and synthetic text, not a real map.
|
||||
|
||||
## Checks completed
|
||||
|
||||
- 20,000 native comparisons against original constructor instruction words, with the same stream adapter on both sides; output bytes, five-read sequence, callee-saved registers, SP and memory guards match.
|
||||
- Short-header failures leave the public result unchanged.
|
||||
- Fifteen ZIP/zlib fixtures cover success, streaming descriptors, malformed/truncated inputs, unsupported flags, ambiguous roots and offset validation.
|
||||
- The local macOS probe reads version 2.0.77 from the installed `_autosave1.zip` without modifying it.
|
||||
- The updated iOS application builds with no Steam imports or dependency. It was signed, installed and launched on the connected iPhone 15 Pro. Its native constructor self-test passed, and it read version 2.0.77 from a generated header-only ZIP. Results are recorded under `build/version/iphone-*.json`.
|
||||
- After explicit user approval, `_autosave1.zip` was copied into the iPhone application’s `Documents/ProbeSave.zip`. The application was relaunched and returned status 0 and version 2.0.77. The actual-save result is retained in `build/version/iphone-real-save-probe.json`; `device-verification.json` records this test, and `generated-device-verification.json` retains the earlier synthetic test. This verifies the version header only; the world has not been loaded.
|
||||
|
||||
Run:
|
||||
|
||||
```sh
|
||||
python3 tools/build_version.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio'
|
||||
python3 tests/check_a64_guards.py
|
||||
python3 tests/check_save_headers.py
|
||||
python3 tools/check_rea_rng.py '/absolute/path/to/factorio.app/Contents/MacOS/factorio' evidence/world-loading-final.json --include-version
|
||||
python3 tools/startup_inventory.py
|
||||
python3 tools/package_ios.py
|
||||
```
|
||||
|
||||
## Remaining startup and world work
|
||||
|
||||
The retained REA `GlobalContext::init` dossier observes 151 direct callees and 643 basic blocks, plus indirect calls with unresolved targets. `build/startup/frontier.json` lists this observed frontier; it is not a complete dependency graph or runnable startup implementation.
|
||||
|
||||
LLVM symbol inventory identifies the next candidate entry points (names/addresses, not recovered behavior): `Scenario::loadFactory` at `0x1000fa1d0`, `Map::load` at `0x1014c93f8`, and `Map::loadData` at `0x1014c9618`. Follow-up live REA callee queries became unavailable after the Hopper connection was lost; no graph completeness is claimed. The completed evidence was exported and a new snapshot retained in `build/world-loading-rea.snapshot.json`.
|
||||
|
||||
Next: recover those loader dependencies, add the required control-flow and memory/import support to the lifter, and replace graphical startup's inlined Steam block with a local optional-services context. Graphics and simulation remain unimplemented.
|
||||
|
||||
A subsequent REA session recovered direct callee frontiers for all three loader roots and translated the branching MapHeader constructor. See [translation-progress.md](translation-progress.md) for evidence, checks, and remaining scope.
|
||||
|
||||
The next batch adds a restricted scenario-envelope adapter and locates MapHeader at decoded offset 333 in the approved autosave. The original native constructor agrees with its translated output there. This is header decoding, not world loading; see [save-layout-progress.md](save-layout-progress.md) for profile restrictions and evidence.
|
||||
Reference in new issue
Block a user