The TypeScript engine port
@tenmol/engine-ts is an in-browser TypeScript port of PyMOL’s engine, exposed as a
@tenmol/backend Backend so the web app can run against it instead of the Python bridge (see
architecture.md §10b). This document tracks
what is ported and how parity with real PyMOL is proven.
How parity is proven
The differential equivalence suite intools/parity runs the same command scripts through both
engines and compares every observable. It is layered:
- Golden fixtures (
tools/parity/fixtures/golden.json) — authoritative, hand-derived from the fixture’s known topology and PyMOL’s exact palette RGBs.tools/parity/test/equivalence.test.tsreplays the corpus throughLocalBackendand asserts zero divergence, plus independent ground-truth assertions so the gate is not engine-vs-itself. - Live differential (
tools/parity/test/live.test.ts, run withTENMOL_PARITY_REMOTE=<ws>) — runs the corpus through real PyMOL over the bridge and the TS engine in the same job and diffs live, so a fixture can never silently drift from PyMOL.--regenrewrites the golden from real PyMOL, making it the authority for the fast gate. - Generative (
tools/parity/test/generative.test.ts) — seeded random command sequences assert the engine is deterministic (what makes a golden gate sound) and that invariants hold. - Contract (
tools/parity/test/contract.test.ts) — both backends satisfyBackend, and an unported symbol rejects with the same error shape on both sides.
pnpm parity:engine (add --remote <ws> for the live diff). It is a CI gate.
Ported command slice (this increment)
Every ported symbol has an equivalence assertion that reddens if it drifts.
Rendering.
spheres and lines are emitted as Mode-G instance buffers in the exact
CmdWebGeometry layout the existing three.js renderer consumes (packages/engine-ts/src/geometry),
so the port renders in-browser with no server draw. Frame structure is asserted in unit tests;
byte-parity against the live C++ accessor is a follow-up row.
Not yet ported (rejects with NotPorted)
Everything else. An unported symbol rejects with a PymolError of type NotPorted (mirroring the
bridge’s NotAllowed) — never a silent no-op — so features degrade visibly and the differential
suite sees the gap. Next increments widen the slice against the same harness: exact turn/zoom
camera parity, more representations (sticks, cartoon), more file formats, and the full command
parser.