# Restored paper transitions

The native Origami preset in Windows x64 PowerPoint **16.0.10351.20054** is a
compressed, authored mesh sequence. Its motion is not generated by a procedural
paper-folding solver. We recovered the entire asset and ported the original
loader, normals, interpolation, camera, lighting, and time normalization rules.
The web demo uses that model. No video frames were fitted or sampled, and no
Office installation or Wine prefix was needed.

The binary resource is only **10,906 bytes**: one mesh, 134 frames, 29 vertices,
34 triangles. `data/origami.resource.bin` is the original resource;
`data/origami.mesh.json` preserves every encoded word and expands the default
positions, normals, and UVs. `data/origami.runtime.json` records the rendering
contract and its evidence. `demo/model.js` is the executable JavaScript port.

## Airplane and Crush

The demo also restores **Airplane** and **Crush**, the sheet-crumpling effect,
from the same Office build. They use the same factory, loader, evaluator,
camera, clock, material binding, and shaders as Origami. Their motion comes
from their own complete resources; no new motion was fitted or approximated.

| Effect | Native type | RCDATA ID | Bytes | Frames | Vertices | Triangles | Default duration |
| --- | --- | --- | --- | --- | --- | --- | --- |
| Origami | 59 | `0xd0a` | 10,906 | 134 | 29 | 34 | 3.25 s |
| Airplane | 58 | `0xd00` | 3,668 | 69 | 14 | 8 | 1.25 s |
| Crush | 54 | `0xd04` | 295,248 | 81 | 1,672 | 3,182 | 2.00 s |

The serializer at `0x18011244c` selects the preset name through
`0x1814c3b30 + (type - 0x30) * 8`. That establishes the name/type mapping.
The dispatcher at `0x1801b09b0` maps type 58 to `0xd00` and type 54 to `0xd04`,
then calls the same factory at `0x180fcc488`. The default durations come from
metadata at `0x181b7bf00` (Airplane) and `0x181b7be3c` (Crush). Each resource
contains one forward-stored, double-sided mesh. Its own ambient coefficient
is retained: approximately 0.6 for Airplane, 0.9 for Crush, 0.8 for Origami.
The factory selects zero extra temporal samples for all three effects.

`tools/recover_paper_effects.py` checks both reference DLL hashes, verifies the
name table, extracts each resource, reconstructs every byte, and records the
runtime contracts in `data/airplane.runtime.json` and `data/crush.runtime.json`.
The raw resources and lossless expanded records are in the corresponding
`.resource.bin` and `.mesh.json` files. Provenance and original-instruction
validation reports are under `research/static/` with each effect's name.

Airplane resource SHA-256:
`aab8022487bb93128efe80197bc5f5d4047c1e36aa8588e48ad5c941a9620a04`.
Crush resource SHA-256:
`feb7739c27a18d86c050a8273e9929c763dbacdaf3203b79c29cd140ff763800`.

Across all three effects, **3,380,536 binary32 words** (positions, normals,
UVs, all four coordinate-mirror combinations) and **384 full interpolated
buffers** match the original x64 instructions bit for bit. Each effect is
checked at eight times and four aspect ratios, with independent native camera
and clock checks. The WebGL renderer allocates vertex and index buffers from
the selected asset's topology and preserves the uploaded image when switching
effects. GPU pixel parity remains outside the restoration claim below.

To reproduce the additional recovery and validation:

```sh
artifacts/venv/bin/python tools/recover_paper_effects.py
artifacts/venv/bin/python tools/validate_native.py --effect airplane
artifacts/venv/bin/python tools/validate_native.py --effect crush
npm test
```

## What “exact” means here

Every resource byte parses and reconstructs exactly. Both the Python decoder
and JavaScript port agree with the original x64 instructions for all default
position, normal, and UV words. The JavaScript port also agrees across all four
mirror combinations: **93,496 binary32 words**, including their sign bits.

Another **128** full interpolated vertex buffers agree bit for bit at eight
progress values and four aspect ratios, including the final frame and values
immediately before it. Four camera matrices agree with the original camera
routine. The clock normalization is checked against its original instructions,
including reversal and integer-to-float rounding at large counter values.
`research/static/native-validation.json` contains the fixtures, hashes, and
reference viewports. `tests/fixtures/` retains the original-code outputs so the
normal test command works without the large Office download.

These are checks of the recovered model, not claims of identical GPU pixels.
The demo ports shader equations into WebGL. Its sampling, color handling,
antialiasing, and depth implementation use browser facilities. The rasterizer
has not been compared pixel by pixel against a running PowerPoint instance.
That distinction follows the project’s goal: exact recovered motion data and
rules, with a demo that may render differently.

## Arithmetic and authored geometry

The little-endian resource starts with a mesh count, then six 32-bit header
words: reverse storage flag, surface flags, material coefficient bits, frame
count, vertex count, triangle count. These are followed by triangle indices
(uint16), initial XY coordinates (int16), and frame increments. Initial XY is
divided by 32767, Z starts at zero.

Each later frame has translation XYZ and scale XYZ as binary32 values. If every
scale component is zero, only common translation is applied. Otherwise, every
vertex contributes one uint16 word: X uses bits 0–5, Y bits 6–10, Z bits 11–15.
Each delta is `translation + scale * f32(packed/maximum)`, rounded after each
operation, then added to the preceding position. Maxima are 63, 31, and 31.
Six frames (zero-based indices 3–8) contain only translation.

Direction bits negate initial coordinates and corresponding increments before
accumulation. UVs derive from the mirrored initial plane:
`u=f32(f32(x+1)*0.5)`, `v=f32(f32(1-y)*0.5)`.

For each authored frame, the loader computes `cross(c-a,b-a)` for every triangle,
normalizes each face, adds it to each triangle vertex in index order, and
normalizes each accumulated vertex vector. This is equal weighting per face,
not area weighting. The final length sums **Y² + X² + Z²**, with binary32
rounding; changing that order can change bits. The normal for a partially folded
frame comes from interpolating the stored normals, then shader normalization.
It is not recomputed from the interpolated surface.

## Clock, camera, and lighting

`FUN_1801d9480` computes binary32 elapsed ticks / frequency / configured duration,
with optional `1-progress` reversal. `FUN_1801d9c00` clamps it to [0,1]. There is
no easing curve between this clock path and Origami’s evaluator. The preset
metadata record at `0x181b7bf38` stores type 59 and **3.25 seconds**, now the demo
default. The duration remains configurable; it is not encoded in the mesh asset.

The evaluator multiplies normalized progress by 133, chooses adjacent frames,
and interpolates all six position/normal components with separate binary32
subtract, multiply, and add operations. X then receives the viewport aspect
ratio. The final-frame upper index is clamped to 133.

The camera is at `(0,0,-1.5)` with zero rotation; projection uses near 0.001,
far 1000, and vertical scale 3. The world matrix scales XYZ by 0.5. Its inverse
transpose scales normals by 2 before shader normalization. The camera routine
and matrix multiply order are preserved, including binary32 rounding.

The vertex shader lights the surface with a light at `(0,0,-5)`. The resource’s
approximately 0.8 coefficient is **ambient**, and its complement is diffuse:
`lighting = ambient + max(dot(normal,lightDirection),0) * diffuse`.
Specular RGB is zero for this preset. The original pixel shader combines
texture, lighting, and specular output. Its output alpha saturates to opaque;
the demo writes opaque alpha explicitly. The shader containers and disassembly
are retained, including the two normal-sign variants.

PowerPoint draws the incoming flat slide, followed by two culled mesh passes.
An odd number of coordinate mirrors swaps normal-sign/raster states; the second
pass uses the opposite state. The demo uses a solid incoming slide, and crops
uploaded images to the stage before making the source texture. These are demo
input choices, not modifications of the restored folding sequence.

## Quality settings

The inspected Origami loader and evaluator always use the same 29 vertices,
34 triangles, and 134 frames. No subdivision or alternate mesh detail level is
selected there. Shader containers retain their level-9 compatibility data.

The shared effect entry has a temporal accumulation branch: for a nonzero extra
sample count at `effect+0x278`, it draws additional future progress samples at
`(window/1000)/(count+1)` intervals. The Origami factory sets **count 0, window
10**, so that branch is disabled for the recovered preset. It changes sampling
in the shared renderer, not the asset topology.

This establishes the selected native preset path. It does not establish that
all Office releases, web PowerPoint, or application-wide software/failure
fallbacks use an identical implementation. Those are separate targets, and
we do not silently substitute their behavior into this restoration.

## Address map and source evidence

All addresses are preferred VAs in PPCORE.DLL with image base `0x180000000`,
except the resource location in PPRESOURCES.DLL.

| Concern | Original address | Evidence |
| --- | --- | --- |
| Type 59 selects asset 0xd0a | `0x1801b09b0` | `static/origami-dispatch/` |
| Origami factory, mirrors, temporal defaults | `0x180fcc488` | `static/origami-dispatch/` |
| Resource loader and normals | `0x180fd9a84` | `static/shader-decompilation/` |
| Origami frame evaluator | `0x180fda5e0` | `static/origami-evaluator/` |
| Clock normalization | `0x1801d9480` | `static/playback/` |
| Clamp and shared temporal sampling | `0x1801d9c00` | `static/effect-entry/` |
| Camera wrapper / projection | `0x1801b6540` / `0x1801b6590` | `static/camera-and-timing/` |
| Matrix multiplication | `0x1801b6818` | `static/rendering/` |
| World inverse for normals | `0x180fcfbac` | `static/rendering/` |
| Material binding and raster passes | `0x180fd4544` / `0x180fd46ac` | `static/rendering/` |
| Flat background | `0x180fdcacc` | `static/rendering/` |
| Positive-normal vertex shader | `0x1815afa20` | `static/origami-shaders/` |
| Negative-normal vertex shader | `0x1815b0290` | `static/origami-shaders/` |
| Textured lighting pixel shader | `0x1815b0b40` | `static/origami-shaders/` |

The asset is RCDATA type 10, ID 3338, language 1033 in PPRESOURCES.DLL,
RVA `0x10acf4`, file offset `0x1092f4`. SHA-256:
`680897ffe7cb39e0ee35fcf1871a525c1ea874afbe7262a3fca3533b431dbeda`.
Package acquisition and per-file hashes remain in `research/acquisition/`.
Raw Ghidra outputs are evidence, not original source: inferred types and cold
function boundaries can be wrong. Machine-code checks provide independent
validation of the arithmetic we ported.

## Reproducing the validation

`tools/validate_native.py` maps the original PE into Unicorn, supplies memory
fixtures, and executes the untouched loader math, interpolation, camera, and
clock instructions. Resource discovery and graphics setup are bypassed. The
hooks substitute correctly rounded sqrtf, exact sin(0)/cos(0), a frequency
query, and the stack-cookie check; they do not implement the folding arithmetic.
The script spells out addresses, fixtures, hooks, and instruction boundaries.

```sh
python3 -m venv --system-site-packages artifacts/venv
artifacts/venv/bin/pip install -r tools/requirements-analysis.txt
artifacts/venv/bin/python tools/validate_native.py
npm test
artifacts/venv/bin/python tests/browser_test.py
```

The first command that executes native arithmetic requires the extracted
PPCORE.DLL. Ordinary `npm test` requires only Node and the committed fixtures.
The browser test uses `/usr/bin/chromium` by default; `CHROMIUM_PATH` can override
it. It serves the repository on a temporary local port and checks compilation,
rendered coverage at the start/fold/end, upload, controls, mobile layout, and
reduced motion. Screenshots and its report go under `artifacts/validation/`.
