Official hub for PS5PCEM, an experimental PlayStation 5 emulator written in Zig. The site tracks game compatibility, publishes measured performance results, and links the latest downloads.
Built with Next.js 16 (App Router), React 19, TypeScript and Tailwind CSS v4. Every route is statically prerendered.
The site speaks the same twelve languages as the emulator's launcher, in the
same order (Language / language_labels in src/launcher_common.zig):
en English |
ru Русский |
de Deutsch |
fr Français |
zh 简体中文 |
es Español |
ar العربية (RTL) |
pt Português |
tr Türkçe |
ja 日本語 |
hi हिन्दी |
ko 한국어 |
The first eight shipped in the launcher with 0.3.0-alpha.1 and 0.3.0-alpha.4; Turkish, Japanese, Hindi and Korean ship in 0.3.6. /languages lists them with their launcher IDs.
Every page lives under a locale prefix — /ru/compatibility,
/ar/games/ghost-of-yotei. An unprefixed request is redirected by
src/proxy.ts, which picks the visitor's best match from
Accept-Language and falls back to English, so older unprefixed links still
work. Arabic gets dir="rtl" on <html>, and the layout uses logical CSS
properties (ms-, ps-, start-) so it mirrors correctly.
English is served from the site root with no prefix — /compatibility, not
/en/compatibility. This is load-bearing: those are the URLs the site was first
indexed under, and putting a prefix on them turned every indexed page into a
redirect, which cost the site its search visibility. Only the other eleven
languages are prefixed.
src/proxy.ts enforces the shape:
| Request | Response |
|---|---|
/compatibility |
200, internally rewritten to the prerendered /en/compatibility |
/en/compatibility |
301 to /compatibility, so the two never compete |
/ru/compatibility |
200, already canonical |
There is deliberately no Accept-Language redirect. Sending a crawler — or anyone following a shared link — to a different URL than the one requested is what broke indexing; language is chosen with the switcher in the header.
What every page carries, built in one place by src/lib/seo.ts:
- a self-referencing canonical, unprefixed for English;
- the complete reciprocal hreflang set, all twelve languages plus
x-default, using full BCP 47 tags (zh-Hans, notzh); - an absolute
<title>per page, so the layout template cannot double the brand, with a unique keyword-led title and description per page per language; index, followstated explicitly, plusmax-image-preview:largeso the captures can appear in results;- Open Graph and Twitter cards with a real 1920×1080 image and alt text.
JSON-LD comes from src/lib/schema.ts: WebSite and
SoftwareApplication on the home page, SoftwareApplication on the download
page (version, OS, licence, real download URL), ItemList of every title on the
compatibility page, and TechArticle + BreadcrumbList on each title page,
with the game as the article's subject. No aggregateRating anywhere — the
project has no review data, and inventing one would be both dishonest and a
structured-data violation.
sitemap.xml holds one entry per page per language with hreflang alternates,
and takes lastmod from the content — the newest recorded run, or the release
date — rather than from the build clock.
Each route exists once per locale: (9 pages + 32 technology articles + 33 title pages) × 12 languages = 888 prerendered pages.
| Route | Contents |
|---|---|
/[locale] |
Overview, current release, compatibility summary, the two renderers (Vulkan and experimental DirectX 12), YouTube channel |
/[locale]/download |
Latest build, SHA-256 checksums, requirements, quick start, release history |
/[locale]/compatibility |
Every title on record with its result, known limits and measured timings |
/[locale]/games/[slug] |
Per-title detail: result, what works, what does not, measured performance, the full dated test history, and every capture from those runs |
/[locale]/status |
Subsystem-by-subsystem state of the emulator |
/[locale]/tech |
Index of the emulator's mechanisms, grouped by area |
/[locale]/tech/[slug] |
One mechanism per page: how it works, what works, what is still missing — including the Vulkan renderer and the Direct3D 12 backend |
/[locale]/media |
Recent runs and development captures, each captioned with what it actually shows |
/[locale]/extract |
PKG extractor: debug packages, launcher button, what is and is not unpacked |
/[locale]/launcher |
The launcher's four pages, what happens on Launch game, the Vulkan / DirectX 12 choice, the environment it passes to game-run, where settings and saves live, the Linux GTK version, and its release timeline |
/[locale]/languages |
The twelve interface languages: launcher ID, the release each arrived in, text direction, how to switch, and the site URL for each |
The site does not invent claims. Its data files mirror the emulator repository's own documentation, and the test suite pins them to it:
| Site data | Source in the emulator repo |
|---|---|
src/data/compatibility.ts |
docs/project-status.md — slugs, tiers, captures, confirmation dates |
src/data/history.ts |
docs/development/*.md and docs/release-notes/*.md — one entry per dated run |
src/data/subsystems.ts |
docs/implementation-status.md |
src/data/release.ts |
GitHub release v0.3.6, its release notes, and SHA256SUMS.txt |
src/data/extractor.ts |
zig-out/bin/pkgextractor.exe behaviour |
src/data/launcher.ts |
src/launcher_common.zig, src/launcher.zig, src/launcher_linux.zig and docs/release-notes/*.md — languages and their IDs, settings, environment variables, timeline |
src/data/graphics.ts |
src/graphics_api.zig, src/game_run.zig and docs/architecture/d3d12.md — the two renderers, their PS5_GRAPHICS_API values and platforms |
src/data/technologies.ts |
docs/architecture/*.md and docs/implementation-status.md — one entry per technology article, with its own updated date when it is newer than the shared audit |
public/images/ |
docs/images/ |
The split is deliberate: src/data/ holds only locale-independent facts
(slugs, tiers, dates, file names, image paths, hashes). All prose lives in
src/i18n/:
| Translations | Contents |
|---|---|
src/i18n/config.ts |
Locale list, native names, BCP 47 tags, text direction, path helpers |
src/i18n/dictionaries/<locale>.ts |
UI strings; English is the reference shape, so a missing key is a type error |
src/i18n/content/<locale>.ts |
Per-title prose and test-history text, keyed by slug and by history id |
src/i18n/tech/<locale>.ts |
The technology articles, keyed by slug |
The content is written for the site rather than copied from the repository, and is kept exactly as narrow as the source: a rendered menu is never described as a playable game, and claims the project withholds stay withheld in all twelve languages (the test suite enforces this for Ghost of Yōtei).
When the emulator publishes a new build or updates project-status.md, update
the matching data file and run the tests — several assertions are written to
fail on stale figures.
A run's release identifies the published version containing its reported
changes. Keep null for work not yet included in any release; the site displays
Development build in all twelve languages. Run dates and FPS remain those
of the original measurements, which may precede publication. Title metadata,
report badges and capture captions share the same labels.
The October 11, 2026 audit checked publication timestamps through the GitHub releases API and verified report commits against release tags with GitHub's compare API:
| Previously unversioned reports | First release containing the changes |
|---|---|
| Jets and Quake II, September 15–16 (2 entries) | 0.3.2 — September 24, 14:59:54 UTC |
| September 25–October 5 (51 entries) | 0.3.3 — October 4, 23:47:06 UTC |
| Jurassic Park font-atlas fix, October 8 (1 entry) | 0.3.4 — October 8, 23:26:59 UTC |
| October 9–10 (9 entries) | Development build — not yet released |
For example, the September 28 Big Helmet Heroes startup fix is included in 0.3.3, whereas its October 10 menu work postdates 0.3.4. Yōtei's October 5 cache report is included in 0.3.3: the release's UTC timestamp falls on October 5 in Minsk. Use commit inclusion to resolve such boundaries instead of assigning the preceding release by date.
npm install
npm run dev # http://localhost:3000npm test # Vitest: data integrity + component and page rendering npm run typecheck # tsc --noEmit npm run lint # eslint npm run build # production build
npm test runs seven suites, 156 tests:
tests/i18n.test.ts— the locale list matches the launcher's twelve languages in order; Arabic is RTL and everything else LTR; version strings are isolated on RTL pages; every BCP 47 tag is valid; every English key exists in all twelve dictionaries with the same{placeholders}; every title and history entry is translated in every language; no translated summary is still the English text; and the "not playable" wording survives in all twelve languages.tests/seo.test.ts— the recovery is pinned here: every originally indexed URL resolves without a redirect,/en/*returns 301 (not 307) onto the unprefixed path,Accept-Languagenever triggers a redirect, no canonical or sitemap URL ever contains/en, every page in every language has a canonical and a full hreflang set, titles and descriptions are unique per page per language and fit a result snippet (with a lower floor for Chinese and Japanese), and the JSON-LD serializes without nulls and claims no rating.tests/compatibility-data.test.ts— pins the dataset toproject-status.md: the exact set of titles, which ten are confirmed completable, Ghost of Yōtei gradedingameand neverplayable, every measured figure in the English source prose, and every referenced capture present inpublic/. Also checks the history: unique ids, real slugs, ISO dates, release tags, newest-first ordering, and source links that point into the emulator repository.tests/release-data.test.ts— every download URL resolves to the matching release tag, both binaries carry their published SHA-256 digest, the release history is newest first, and the formatting helpers are correct.tests/compatibility-table.test.tsx— filtering, search, expand/collapse, alt text on every capture, and that Russian rows link to Russian detail pages.tests/pages.test.tsx— every page renders its data in English and in a second language, title pages list each recorded run with its build label and report link, an unknown slug callsnotFound(), the home page presents both renderers with Vulkan as the default, the launcher page lists every environment variable, explains the graphics API choice and links its timeline to release notes or development commits, the languages page lists all twelve languages in launcher order with their IDs, the language switcher offers all twelve pointing at the same page, and external links open withrel="noopener".tests/tech.test.tsx— every technology article exists in every language with the English shape, related links point at real pages, and the index and articles render in English and Arabic.
- Update
latestReleaseand prepend toreleaseHistoryinsrc/data/release.ts, with sizes and SHA-256 digests from the release'sSHA256SUMS.txt. - Update
src/data/compatibility.tsfrom the newproject-status.md, and copy any new captures intopublic/images/. - Add an entry to
src/data/history.tsfor each new dated run. Setreleaseto the first published version containing its changes, ornullwhile they are unreleased. Revisit these entries when publishing the next release. - Write the prose for anything new in
src/i18n/content/en.ts, then in the other eleven content files. TypeScript will not let a locale fall behind. - Run
npm testand fix whatever the pinned assertions report.
Only if the emulator's launcher adds one. Append it to locales in
src/i18n/config.ts with its native name, BCP 47 tag and direction, then add
src/i18n/dictionaries/<code>.ts, src/i18n/content/<code>.ts and
src/i18n/tech/<code>.ts and register them in src/i18n/index.ts. Add its
launcher ID and first release to launcherLanguages in src/data/launcher.ts.
The routes, sitemap and hreflang alternates follow automatically.
The site is not affiliated with or endorsed by Sony Interactive Entertainment. No games, console firmware, system libraries or keys are distributed here. The emulator itself is licensed under the GNU GPL, version 3 or later.