# Duke Nukem II port staging area (RigelEngine)

This directory contains Ultimatum-owned Phase 0 planning, reproducibility
metadata, and the data-free engine seam for the proposed Duke Nukem II port
through [RigelEngine](https://github.com/lethal-guitar/RigelEngine).

There is no upstream engine source, original game data, generated asset,
public route, or deployable bundle here. `upstream.lock.json` identifies the
exact RigelEngine commit audited during Phase 0; `engine/patches/` holds the
minimal Ultimatum patch stack; `engine/runtime/` holds the runtime-mount hook.

Original Duke Nukem II data stays user-supplied and ignored. Never add
`NUKEM2.CMP`, `NUKEM2.F1`–`.F5`, `NUKEM2.EXE`, `DN2HELP.EXE`, `ORDER.FRM`,
`VENDOR.DOC`, extracted assets, saves, high scores, `UserProfile_v2.rigel`, or
`Options.json` to this directory or to any public build.

## Phase 0 status

- Engine pinned and file/submodule hashes recorded.
- License and third-party obligations inventoried (see `THIRD_PARTY_NOTICES.md`).
- Data-pack (`.CMP`) container format characterized and implemented as a bounded,
  tested parser (`adapter/cmp-package.js`, `tests/cmp-package.test.cjs`).
- Data-free build seam produced: two CMake/source patches plus a pre-js mount
  hook. No `.data` artifact is produced.
- **Data-free WASM build reproduced and runtime-verified** on 2026-09-21 with the
  repository's pinned Emscripten toolchain. The engine mounted six user files at
  `/duke`, reached `Game started`, rendered the title screen, and wrote its
  profile to the platform-controlled `/userdata`.
- The user-supplied archive was identified by the engine as the **shareware**
  episode. Both shareware and registered data are bring-your-own-data: the
  player imports their own copy, it stays in their browser, and Ultimatum never
  distributes game data.
- **Not done:** adapter/registration, catalog, public route, iOS. Phase 0
  changed no platform code.

## Phase 1 status

- `adapter/adapter.js` implements the full `GameAdapter` and passes the shared
  conformance suite (see `adapter/adapter.test.cjs`).
- The real engine's binary `UserProfile_v2.rigel` was captured, restored on
  reload (`Found user profile version 2, loading`), and round-tripped through the
  platform binary save envelope and the adapter's save descriptor.
- The intent-to-engine input bridge (`platform/rigel-engine-session.js`,
  `platform/input-map.js`) drives the real engine from the main menu into
  **Level 1**, and real **quick save/quick load** succeed (`Quick save created` /
  `Quick save loaded`). Verified in Chromium via the `spike/` host.
- Engine behavior note: RigelEngine's quick save is in-memory and session-scoped,
  so it does not survive a page reload; durable persistence is the profile path.
- **Pending:** durable in-game menu save, cancellation/return evidence, and the
  Firefox/Safari/mobile matrix.
- No registry/catalog entry; Phase 2 owns portal wiring.
- Details: `docs/engineering/DUKE_NUKEM_II_PHASE1_ADAPTER.md`.

## Public web route (2026-09-22)

The site build now packages this host at `/duke2/` and lists it in the catalog
as an **experimental** title. `clients/site/scripts/package-duke2-runtime.mjs`
verifies the pinned engine artifacts against `upstream.lock.json`, copies the
host and its platform dependencies, and includes the pinned RigelEngine
corresponding source plus the Ultimatum patch stack under
`source/ports/duke2/`. Original game data is never included. The shareware
archive is linked out to archive.org rather than proxied or re-served.

Engine/source licensing in `upstream.lock.json` remains recorded; the
experimental listing is a bring-your-own-data preview. Players import their own
shareware or registered data, which stays in their browser and is never
distributed by Ultimatum.

## Bring-your-own-data flow

The spike host (`spike/`) shows a **game data screen** before launch with three
simple options:

1. **Download shareware (episode 1)** — a link to the freely distributed archive
   on archive.org. Ultimatum does not proxy, mirror, or re-serve it; download the
   ZIP, then import it here.
2. **Upload a ZIP** of your data (shareware or registered).
3. **Choose a folder** of your data (shareware or registered).

All three feed the platform `InstallPipeline`:

```text
files/ZIP -> inventory -> adapter.detect -> createInstallPlan -> staged copy
          -> adapter.validateInstall (bounded .CMP parser) -> atomic publish
          -> IndexedDB (games/duke2/installs/...)
```

On launch the host reads the active install (`getActiveInstall`), mounts its
role files at `/duke`, and starts the engine. If there is no install, the data
screen is shown. The `DATA` button in the system bar reopens it to replace the
data. Data is stored only in this browser; nothing is uploaded.

Supporting pieces:

- `packages/platform/dist/ultimatum-platform.js` — the shared browser bundle of
  `packages/platform` (the UMD modules expect nested namespaces that plain
  script tags don't produce). Regenerate with
  `node packages/platform/scripts/build-browser.cjs` after changing
  `packages/platform`. The spike server is scoped to serve it alongside the
  port's own modules.
- `platform/idb-storage-provider.js` — IndexedDB `StorageProvider` with
  revisions for compare-and-swap publication.
- `platform/unzip.js` — bounded ZIP reader (store/deflate) for ZIP import.
- `scripts/serve-spike.mjs` — scoped LAN server that exposes only the spike
  host, platform, adapter, and engine build.

## Reproduce the seam

1. Resolve the pinned checkout and submodules:

   ```sh
   node ports/duke2/scripts/resolve-upstream.mjs
   ```

2. Build data-free against an activated pinned emsdk:

   ```sh
   ports/duke2/scripts/build-wasm.sh
   ```

   The script applies nothing itself; the patch is already represented in the
   checkout it clones. It passes `-DWEBASSEMBLY_GAME_PATH=` (empty) and
   `-DULTIMATUM_MOUNT_JS=` so no game data is baked into the artifact.

3. Mount ignored user-supplied data at runtime. The host sets
   `Module.ultimatumGameFiles` and `Module.ultimatumProfileFiles` before the
   generated module script executes; the hook writes `/duke` and `/userdata`.

See `engine/README.md` for the seam and `docs/engineering/DUKE_NUKEM_II_PHASE0_AUDIT.md`
for the evidence and open questions.

## Tests

The bounded `.CMP` parser has synthetic-fixture tests and needs no game data:

```sh
node --test ports/duke2/tests/cmp-package.test.cjs
```
