Skip to content

Compatibility

OPHTML ships two packages and a C runtime as one product, one version number apart. This page lists every version claim the tree carries, what a runtime accepts from a blob, how each registry treats a prerelease, and the platforms the toolchain runs on.

Versions table

Run python3 tools/check-versions.py --except-tag to prove the five numbers below agree with each other and with the code that writes them.

$ python3 tools/check-versions.py --except-tag
ok - packages/baker derives its version from ps2ui_bake.__version__ and declares it nowhere else
ok - @ophtml/layout 0.10.0 and ophtml 0.10.0 are the same version in the two spellings
ok - PS2UI_VERSION and uib.VERSION are both 7
ok - docs/format-uib.md's header table says version 7
ok - docs/format-uib.md's Versioning list explains v7
ok - CHANGELOG's newest section is headed '0.10.0 — 2026-09-26', dated, and is the release the packages carry
ok - CHANGELOG's open section names format v7
ok - CHANGELOG's 0.9.0 section records the format it shipped (v7)
ok - CHANGELOG counts zero format moves since 0.9.0, and v7 -> v7 is 0
ok - CHANGELOG counts the drift from 0.9.0, the section below it
ok - README's Quick start note names 0.10.0, 0.10.0, format v7 and the drift since 0.9.0
ok - and docs/assets/ophtml-logo-releaseVersion0100-plain-white-darkbg.png is actually there
ok - README's header logo names 0.10.0, which is the release being cut
ok - @ophtml is scoped and publishes with access: public
ok - @ophtml/layout 0.10.0 is a release and publishes to `latest`
ok - docs/releasing.md exists and still names __version__ and the tagging step (keywords, not correctness)
ok - layout is named @ophtml/layout
ok - baker is named ophtml
ok - packages/layout/README.md is there
ok - packages/baker/README.md is there and pyproject.toml declares it
ok - CHANGELOG's 0.3.0 section is the one v0.3.0 shipped with its recorded edit
ok - CHANGELOG's 0.4.0 section is the one v0.4.0 shipped
ok - CHANGELOG's 0.5.0 section is the one v0.5.0 shipped
ok - CHANGELOG's 0.6.0 section is the one v0.6.0 shipped
ok - CHANGELOG's 0.7.0 section is the one v0.7.0 shipped
ok - CHANGELOG's 0.8.0 section is the one v0.8.0 shipped
ok - CHANGELOG's 0.9.0 section is the one v0.9.0 shipped
ok - the recorded v0.3.0 edit ('**Tagged is not published.**' -> '**Tagged and published.**') is still the one difference it excuses
# rule 23 compared 7 of 7 released section(s) with their tags
ok - CHANGELOG's Tagged is not published paragraph is gone, matching PUBLISHED = True
ok - the npm package is scoped: @ophtml/layout
ok - README.md names the packages it tells people to install
ok - CHANGELOG.md names the packages it tells people to install
ok - docs/releasing.md names the packages it tells people to install
ok - docs/tutorial-uc3.md names the packages it tells people to install
ok - docs/PLAN.md's format history runs v1 through v7
ok - ci.yml runs this file unflagged exactly once, so the tag rule is evaluated (2 invocation(s) in total)
ok - and it is the last `run:` step in the workflow, so a red tag rule cannot mask the checks before it
ok - CHANGELOG's 0.10.0 section says what changed (12 entries under 3 heading(s))
ok - the Changelog page restates the open 0.10.0 section one for one: 12 bullet(s) for 12 entries
ok - the changelog.mapping row names the 7 number(s) rule 14 counts
skip - the tag rule, deferred to the full unflagged run at the end of this job (--except-tag)
component version reads
ophtml (PyPI) 0.10.0 ps2ui_bake.__version__
@ophtml/layout (npm) 0.10.0 packages/layout/package.json
ui.json IR 1 IR_VERSION in packages/layout/src/index.js
.uib format 7 VERSION in packages/baker/ps2ui_bake/uib.py
PS2UI_VERSION (runtime macro) 7 runtime/ps2ui.h

ps2ui --version prints ps2ui 0.10.0. ps2ui-layout --version prints ps2ui-layout 0.10.0. PS2UI_VERSION is the frozen .uib format version, not a mechanism that stops the baker and the runtime drifting apart by itself. Baker and runtime agree because ps2ui vendor-runtime ships both files from one package in one commit; see the cross toolchain.

Format compatibility

Format v7 is the last incompatible .uib layout. The pledge states every future addition lands in a feature bit, never a struct stride. 0.3.0 introduced v7; 0.4.0, 0.5.0, 0.6.0 and 0.7.0 each record zero format moves since the release below them, in the changelog.

$ python3 tools/check-format-frozen.py
ok - the format is v7, the version the pledge froze
ok - MAGIC is 0x31424955, unchanged
ok - _CLUT is '<HHI', 8 bytes
ok - _CMD is '<BBHhhHHHHHHHHH6x', 32 bytes
ok - _FOCUS is '<HHHHHHIhhHH', 24 bytes
ok - _FONT is '<HHHHHHIH2xI', 24 bytes
ok - _GLYF is '<IHHHHhhH2x', 20 bytes
ok - _HEADER is '<IHHHHHHIHHIIIIIIIHHIIHHIIHHHH', 84 bytes
ok - _KERN is '<IIh2x', 12 bytes
ok - _SCREEN is '<IIIHHHHH2x', 24 bytes
ok - _SLOT is '<IIhhHHBBHHHHh', 28 bytes
ok - _TEX is '<BBHHHIII', 20 bytes
ok - _TINT is '<BBBB', 4 bytes
ok - FEAT_DYNAMIC_TEXT is still bit 0
ok - FEAT_KERNING is still bit 1
ok - FEAT_SLOT_SPACING is still bit 2
ok - FEAT_STREAMED_TEX is still bit 3
ok - FEAT_ROLE_TINTS is still bit 4
ok - FEAT_KNOWN (0x1f) still admits every frozen bit
ok - all 11 Struct(s) in uib are in the record
runtime version blob version result
any v7 product (0.3.0 through 0.7.0) any v7 product (0.3.0 through 0.7.0) loads
any v7 product pre-pledge (0.1.0 or 0.2.0, format v1 through v6) refused, PS2UI_ERR_VERSION
pre-pledge (0.1.0 or 0.2.0) any v7 product refused, PS2UI_ERR_VERSION

A 0.5.0 runtime loads a 0.7.0 blob and a 0.7.0 runtime loads a 0.5.0 blob, because both write format v7 and ps2ui_load compares only the header's version field against PS2UI_VERSION.

Registries

Under 0.x a prerelease and a release both publish, but the two registries guard a stranger against installing an unverified prerelease in different ways.

registry package prerelease mechanism current state
npm @ophtml/layout publishConfig.tag set to next while the version is a prerelease; a plain npm install resolves the latest dist-tag, so the prerelease stays unreachable by it no publishConfig.tag, version 0.10.0 — a release, so latest is correct
PyPI ophtml pip excludes a prerelease from a plain pip install unless no stable version satisfies the request 0.10.0 is a release, so a plain pip install ophtml resolves it

A release drops publishConfig.tag, or sets it to latest; pip needs no equivalent step, because a real release already satisfies a plain install once one exists. check-versions.py holds both directions: a prerelease refused latest and a release refused any other tag.

Platforms table

platform requirement source
Node.js 18 or newer packages/layout/package.json
Python 3.9 or newer packages/baker/pyproject.toml
Pillow 9 or newer packages/baker/pyproject.toml
uharfbuzz 0.51.7 or newer; pip installs it packages/baker/pyproject.toml
macOS, Windows nothing beyond the two wheels, since 0.9.0 CHANGELOG.md 0.9.0 section
host C compiler cc or clang, for make -C runtime test runtime/Makefile
gsKit (host tests) vendored headers pinned to commit 43122eb96289167975b56caa45beb71eb8684fa2 runtime/vendor/README.md
PS2SDK / ps2dev toolchain ghcr.io/ps2dev/ps2dev:latest, deliberately unpinned .github/workflows/hw.yml

Install both packages per installation. Until 0.9.0, macOS and Windows needed fribidi. 0.8.0's ps2ui-fontgen measured through Pillow's Raqm engine, which Pillow compiles in and which loads fribidi from the machine at run time, so a stock Mac or Windows box refused until fribidi was supplied. registry.yml run 35809073289 measured it: a plain install refused on all three; brew install fribidi alone cleared it on Intel but not on Apple silicon, which needed a Pillow rebuild; and the MSYS2 DLL on PATH cleared it on Windows.

0.9.0 measures through uharfbuzz, whose wheels carry HarfBuzz whole, and reproduces 0.8.0's tables exactly. ci.yml runs ps2ui fontgen on stock macOS arm64, macOS x86_64 and Windows runners on every pull request, with nothing installed but the package. The .uib format and the runtime are not involved either way; this is a property of the authoring host.

What each platform has actually been run on

platform evidence
Linux (ubuntu-24.04) every job in ci.yml and hw.yml, on every push
macOS arm64 registry.yml, weekly and on release; ci.yml's ps2ui fontgen job on every pull request
macOS x86_64 the same two
Windows x86_64 the same two; the first registry.yml run, 35809073289, passed every Windows leg

Nothing else has been tried. A platform missing from this table is not known to fail; it is not known at all.

The host test suite compiles against real gsKit declarations instead of a hand-written stub, on either compiler, so a struct-shape or prototype mismatch fails the build rather than passing silently. The console build takes its PS2SDK and gsKit headers from the same unpinned ps2dev image, kept unpinned on purpose so an upstream change shows up as a watch rather than staying frozen out of sight.

Names

OPHTML is the product. The format and its tooling keep the name they always had: .uib, ps2ui.h, and every ps2ui, ps2ui-bake, ps2ui-check, ps2ui-fontgen and ps2ui-layout command. Only the two registry names moved, to ophtml on PyPI and @ophtml/layout on npm.