Port status
The engine room. Here, measured and checkable, is how much of the 1988 game we have verified — and how. If you just want to play, you need none of this: the game works without your reading a single figure — your door is How to play. This page is for whoever wants to lift the hood.
This page is GENERATED from the repository's artefacts on every build of the site: the routine census, the coverage ledger and the bug registry. Not one figure is written by hand. If one more routine is read tomorrow, the number below changes by itself.
The game can be finished from start to end
with the derived plot locations, not the binary's canonical ones — this is Class E of the declared divergences · see class E
Coverage of the original binary
202,800 of 202,800 bytes · 0 outstanding
Adjudicated is not verified: it means we know whose each byte is and what class it belongs to, not that its logic has been checked.
Reading the binary, row by row
- 722 with the body read and verified
- 162 deferred with a written reason (hardware drivers)
- 1 still open, and it never closes for structural reasons
885 rows censused in total
0 unexplained.
What the binary is made of
- 75.0 % Executable code
- 14.0 % Game text
- 6.2 % In-memory state
- 3.7 % Data tables
- 1.0 % Loose data
- 0.1 % Linker padding
- 0.0 % File header
202,800 bytes split across 7 segment classes
Declared browser tests
census with --list · not a count of passes
826
826 tests in 104 files, counted with --list
- 330 Desktop
- 6 Mirror of the original
- 488 Mobile
- 2 Production site
This figure says how many EXIST, not how many pass. Running them costs a machine window, so the result carries its date: on 2026-09-12, at b083ad30f, 820 were executed and 720 passed. The remaining 0 are an upper bound on defects —under load some tests fail by running out of time—; the breakdown is on /verificacion.
Sealed dungeon rooms
112 / 112
7 dungeons × 16 rooms · 0 queued
Published reverse-engineering notes
562
562 of 1,402 travel to the public repository · 840 are about our own process and are not published
The original's bugs, and what OpenU5 does with each
- 13 Ones OpenU5 fixes
- 13 Ones it reproduces on purpose
- 4 Typos kept as part of the original
- 10 Latent defects, with no demonstrated live path
- 11 Investigated and dismissed: NOT bugs
51 entries in the canonical registry
What is NOT verified
A status page that only shows what is done is not a status page. These are the five conscious boundaries, each with its detail below.
- Random-stream parity against a real runChests, objects and dungeons: their registers cannot be seeded without the original machine, so two independent models are crossed instead of DOSBox.read the detail
- Combat AI movementThe formula is derived from the disassembly and cited; what is missing is the live witness, because its roll stream is not captured reliably.read the detail
- The Ultima IV character import pathDerived and documented, not ported. A scope boundary, not an oversight.read the detail
- Deliberate additionsMusic (the PC original was silent), high-resolution tiles, smooth camera. They do not touch the logic and each one is declared.read the detail
- The engine is repeatable as measured over one session, not proven over allThe same keys reproduced the same session byte for byte, across two runs, comparing the whole trajectory. That is what is claimed, not one word more.read the detail
This is how much of the original Ultima V has been read, and what OpenU5 carries, with the exact figure and the command that reproduces it. There is no progress bar: there are counts over artefacts that live in the repository and that you can open yourself.
What this page measures, and what it doesn't
Worth saying before the numbers, because this is where nearly every misunderstanding gets in:
- It does measure how much of the original binary is adjudicated, censused and read — routine by routine, with the depth of each reading.
- It does not measure "how much of the game works". That isn't a number: it's how verification works, where the harnesses live and so does the list of what is not checked.
- There is no green/amber/red subsystem taxonomy. No such artefact exists in this project, and manufacturing one for the front page would be inventing the evidence.
Reading the binary
| Bytes of the original adjudicated | 202,800 / 202,800 = 100.0 % · 0 outstanding |
| Original files censused | 25, across 796 segments |
| Of those, executable code | 152,185 B = 75.0 % |
| Census rows | 885 |
| Rows understood at depth C or E | 722 |
| Rows with the body read and verified | 722 |
| Rows deferred with a stated reason | 163 · of which 163 are hardware drivers |
| Unexplained | 0 |
| Verified-inherited without a citation | 0 |
Why "rows" and not "routines"
The census's unit is the ROW, not the function. A row covers an address range and carries one name; and some rows contain more than one entry point — code reached from outside, midway through the range, which is therefore another function wearing its neighbour's name.
Calling them "routines" would be the right figure under the wrong label, which is precisely the defect this page exists not to commit: the number doesn't change, what changes is what it claims.
What is known today, with its limit:
- The project has a detector (
re/tools/entradas_absorbidas.py) that finds them by crossing the census with the disassembly, and separates the classes by strength of evidence: acalltarget from outside is a function, no argument; a jump target may be a jump table or a badly placed cut between two rows. - 🔴 That detector is a LOWER BOUND and says so: it does not resolve indirect dispatch.
- 🔴 And its count is NOT in the committed census, because the census is not regenerated (that would destroy the curated names). So the exact number cannot be published yet, and that is why there is none here: there is an honest label and a declared limit.
What does not change: the 722 rows whose bodies were read have been read. What is being bounded is what counts as "one".
The single outstanding row, which must be read as a pair
1 game row is still pending verification, and it will never close, for structural reasons — it isn't short of work; its shape doesn't admit the kind of verification that would be asked of it. The real outstanding work is 0.
The three figures always travel together: "1 outstanding" on its own would announce a queue that no longer exists. The reading pass is closed, and what is left — 1 row — is never going to move.
A note on the 722
That number appears in the census under two names —body_verified and by_depth.E— and it is the same measurement, not two pieces of evidence confirming each other. Quoting it twice would be counting one thing twice, so it appears once here. The generator checks on every build that the two still agree; if they diverge, the site does not build.
Coverage is not truth
100.0 % does not mean the game is 100 % verified. It means every byte of the files EA shipped is adjudicated to an identified segment: we know whose it is and what class it belongs to. That is a statement of bookkeeping, and it is smaller than it looks.
It's worth seeing the split, because "the binary" is not all code:
| Segment class | Bytes | |
|---|---|---|
| Executable code | 152,185 | 75.0 % |
| Game text | 28,347 | 14.0 % |
| In-memory state | 12,510 | 6.2 % |
| Data tables | 7,575 | 3.7 % |
| Loose data | 2,021 | 1.0 % |
| Linker padding | 146 | 0.1 % |
| File header | 16 | 0.0 % |
What sits on top of that base, and is a real scale of knowledge:
- Adjudicated — the byte belongs to an identified segment. This is the 100.0 %.
- Understood (depth C or E) — there is a derivation of what it does. 722 rows.
- Read and verified — someone walked the body instruction by instruction and cited it. 722 rows.
The 163 deferred rows are not an oversight: each carries its reason in writing, and 163 of them are hardware drivers (timer, keyboard, EGA, disk) whose fine semantics no ported subsystem exercises.
Bugs in the original
The project keeps a canonical registry of the 1988 game's defects, with the grade of evidence for each and the decision taken. Split by what OpenU5 does with them:
| Category | Entries |
|---|---|
| Ones OpenU5 fixes | 13 |
| Ones it reproduces on purpose | 13 |
| Typos kept as part of the original | 4 |
| Latent defects, with no demonstrated live path | 10 |
| Investigated and dismissed: NOT bugs | 11 |
The distinction ordering that table is not severity, it is whether the defect shows while playing. A bug that changes what the player sees is reproduced; one that corrupts data or hangs the game is fixed and declared.
The last row is the hardest to maintain and the most telling: things that looked like defects, were investigated and turned out not to be. A registry that only accumulates findings and never retracts isn't measuring, it's collecting.
Delivered with a public surface
The port does not deliver binary reading alone: these pieces can be touched from the site, and their counts are derived from the artefact on every build — one line per delivery and not one screenshot, because the public pages carry no image of the game.
- Moments gallery — 10 prefabricated games in the /byo catalogue, all of them available. The published catalogue (
demo-byo/public/momentos/momentos.json) is baked from the engine's defs and is a fixed point: editing it by hand turns a test red. - Records table and shared games — 3 API endpoints serve it (
demo-byo/functions/api/), and every shared game can be replayed in the viewer, key by key. - Recorded games — 7 real key sequences the engine re-executes, each with its video on /en/games. No editing: what you see is the re-execution.
- Usage analytics — no figure to count here: on by default, with session recording, and once the visitor declines the SDK is not even inserted (
game/src/web/analitica.ts). What is measured and how to decline it: /en/privacy.
What is NOT verified
A status page that only shows what's done is not a status page. These are the five conscious boundaries, all documented in their subsystem's notes. Each has its card at the very top, on the first screen; here is the detail.
Random-stream parity against a real run
Comparing roll by roll against the 1988 game means putting its random number generator into a known state. For chests, objects and dungeons that cannot be done from outside: the seed is touched in places you cannot reach without the original machine.
What is done instead is crossing two independent models — the one derived from the disassembly and the one the engine runs — and requiring them to agree. That is a strong check and it is not the same one: two models that share a reading error agree just as well. Hence this is here, not in the verified list.
Combat AI movement
How each creature picks a target and where it moves is read in the assembly, instruction by instruction, and cited in its subsystem's notes. What is missing is a run of the original observed next to the clone's: the channel used to measure those traces does not capture its roll stream reliably, and a measurement that sometimes drops data is no witness.
This is a gap of instrument, not of knowledge — but the project does not count as verified what it has only derived.
The Ultima IV character import path
The original lets you bring your party over from Ultima IV. That path is read and documented, and it is not implemented in OpenU5: it was decided out of scope, not attempted and failed.
It is stated here because the difference matters: a scope gap closes when someone wants that scope; a verification gap closes by measuring.
Deliberate additions
Not everything OpenU5 does differently is a defect awaiting a fix: some things were added knowing the original lacked them. Music, because the PC version was silent. Tile smoothing and the camera, because today's monitor is not a 1988 one.
The rule that keeps them out of the logic is concrete and checkable: a recorded session replays identically on the faithful skin and on the smoothed one. If one of these additions touched what the engine computes, that replay would stop matching.
The engine is repeatable as measured over one session, not proven over all
This one holds up the other four, which is why it is spelled out. The measurement exists and it is a good one: the same list of keys produced the same state byte for byte across two runs, comparing the whole trajectory and not just the final result.
What there is not is a proof that this holds for every possible session. A measurement over one case is not a property of the system, and the difference between measured and proven is exactly the one this project refuses to blur.
How to re-measure all of this
None of these commands needs a browser, and none takes more than a few seconds except the first:
python3 re/tools/ledger.py
python3 -c "import json;print(json.load(open('re/ledger/frontier.json'))['summary'])"
The first walks the binary and apportions the bytes; the second prints the whole census, with every field this page summarises. The bug registry is a text file: docs/bugs-del-original.md.
Everything is published under GPL-3.0-or-later at github.com/kokoima/openu5.
Expiry
These figures come from tree 85657e7e, the same one that built this page. They are regenerated on every build of the site: there is no hand-written copy that can fall behind. A status document that isn't re-run is worse than none, because it gives the impression that someone checked.
These figures come from tree 85657e7e.
The commands that reproduce every figure in this panel
python3 re/tools/ledger.py # cobertura y composición
python3 -c "import json;print(json.load(open('re/ledger/frontier.json'))['summary'])"
python3 tools/estado-plan.py # salas de mazmorra
python3 docs/publicacion/web/censo_notas.py # notas que viajan al repo público
cd game && npx playwright test --list # pruebas de navegador DECLARADAS
cd game && npx playwright test -c playwright.mobile.config.ts --list
python3 -c "import json;print(len(json.load(open('demo-byo/public/momentos/momentos.json'))))" # momentos publicados
find demo-byo/functions/api -name "*.ts" | wc -l # endpoints de la tabla de récords
ls game/partidas/*.json | wc -l # partidas grabadas (replays)