PolyfillIndex
| Version | No release yet, documented from main |
| Download | GitHub |
| Help | Discord · Report a bug |
🛟 Need help or found a bug? Get support at support.doodesch.de/polyfill.
The public list of which Schedule I mods still run on which version of the game, at polyfill.doomods.com. Every row comes from a copy of the game that launched with Polyfill installed and whose owner chose to send what it found.
This repository is the whole site: the board, the endpoint the mod posts to, and the Postgres schema behind both.
Running it
Section titled “Running it”One command, after npm install:
npm run devThat serves http://localhost:3000 against an empty index, which is the real state on day one and therefore the state worth developing against.
To see the layout with rows in it:
POLYFILL_INDEX_SOURCE=sample npm run devSeven invented mods across two game versions. None of them exist, every page says so in a banner, and every row carries its own “example” tag.
Production sets POLYFILL_INDEX_SOURCE=db and a DATABASE_URL. Set the first without the second and
the site logs the mistake and serves the empty board rather than failing every request.
npm run build && npm start # production buildnpm run typecheck # types onlyThe stack, and why
Section titled “The stack, and why”Next.js (App Router) + TypeScript + hand-written CSS. No UI library, no CSS framework, no webfonts.
- Next.js, because the address
POST /api/reportis already compiled into the shipped mod (Polyfill/Report/Share.cs). It has to answer on this origin, so the site and the ingest endpoint are one deployment rather than two. Server rendering also matters here: a player googling “does X work on 0.4.6f13” should land on a mod page that is already HTML. - It matches the sibling site.
WeedtimesWebis Next.js plus Postgres on the same Dokploy host, so the Dockerfile, the env plumbing and the deploy are a repeat rather than a new problem. - No UI library. This is three routes. A component library would be overhead, not leverage, and
the design depends on exact control of two colour themes and one type scale. The dependency tree is
next,react,react-domand the types. - No webfonts. The player reading this is on a phone on mobile data with a game downloading in another window. A webfont costs bytes, a flash of invisible text and a layout shift, in exchange for a character the board does not need. The board’s character comes from its grid, its rules and its tabular figures.
The pages
Section titled “The pages”| Route | The question it answers |
|---|---|
/ | Does this mod still run on the game version I have? |
/mod/[slug] | What happens to this one mod, on every version, and exactly what is missing. |
/about | Where the rows come from, what is collected, what is not, and how to get a mod removed. |
/api/report | Where the mod posts at launch. Not a page. |
/ and /mod/[slug] are written for two readers at once. Everything a player sees uses the game’s
words. The technical detail sits below a labelled boundary on the mod page, because that is where the
author is reading and the symbol names are the whole value to them.
Design
Section titled “Design”DESIGN.md carries the durable rules. The short version:
- The surface is a status board, not a dashboard: one row per mod, a status cell in a constant position, hairline rules, square corners, no shadows and no cards.
- Colour appears in exactly one place, the status cell, and means exactly one thing.
- No red in the verdict scale. Green runs, blue repaired, amber gaps remain. Red is a condemnation, and this index reports on other people’s work; the worst thing it says is that some of what a mod needs is gone. Green/blue/amber also survives colour vision deficiency, which green/amber/red does not.
- Every verdict carries three channels: the word, a glyph, and the hue, in that order. Remove all colour and the board still reads.
- Both themes are declared explicitly, never derived from each other, and the theme has three states: light, dark, and following the system.
- The empty index is the design case. A page that only looks right with a thousand rows would be wrong for the whole period anyone is watching.
The data
Section titled “The data”Everything the site can ever show is what one launch actually sends
(Polyfill/Report/Share.cs), which is:
# polyfill-share 1# game=0.4.6f13M|name|version|author|verdict|findingCountF|mod name|kind|symbol|outcome- Verdicts:
clean,adaptable,blocked. - Finding kinds:
type,member,field,harmony-target. - Outcomes:
applied,refused,stood-down,none.
There is no player, no install id, no path and no save in it, so there is none of that in the model either. The index counts reports and cannot count people, which the About page states rather than glossing over.
src/lib/share-format.ts parses that document and is deliberately paranoid: the endpoint is
unauthenticated by design, so unknown verdicts, missing columns and absurd lengths are ordinary input.
Unreadable lines are counted and dropped, never half-understood.
Honesty rules that are built in, not just documented
Section titled “Honesty rules that are built in, not just documented”- The default data source is empty. Sample data takes a deliberate environment variable, so a deploy that forgets to unset something publishes nothing rather than publishing fiction.
- Sample mods are invented. Real mods are written by real people, and a public page saying a named mod has gaps is a statement about their work. The fixture names nobody real.
- A row backed by one report says so, in the row, next to the verdict.
- Disagreeing reports are shown as disagreeing, not averaged into one confident word.
- An empty search explains itself: a mod that is absent has not been reported by anyone, which is not a statement about the mod.
What is deliberately not here
Section titled “What is deliberately not here”- Accounts. Nobody signs in, so there is nothing to sign in to and no session to leak. A mod author asking to be removed is verified by hand through support.doodesch.de/polyfill, against the Nexus or GitHub page that carries their name.
- A removal form. Removal sets
withdrawn_aton the standings rows, which keeps the row so the next report cannot resurrect the listing. Deleting it would put the mod straight back. - Links to a mod’s own page. The payload carries no URL, so an outbound link would need a curated table or a Thunderstore/Nexus lookup. The slot is not faked in the meantime.
- A lifetime “players” figure per mod. Raw reports are deleted after 30 days, so the only durable counts are the per-mod ones, and the front-page total comes from an opaque row per installation rather than from summing those.
If the data turns out different from what we assumed
Section titled “If the data turns out different from what we assumed”- If most rows end up backed by a single report, the confidence note becomes the loudest thing on the board. It would then be better as a column than a sentence per row.
- If a mod’s name changes between versions, the slug changes with it and the history splits. A stable key from the assembly name would fix it, but the shared payload does not carry one.
- If verdicts disagree often, the “reports disagree” line is too long for a row and should collapse to a marker that opens the detail.
- If the index grows past a few thousand mods, server-side filtering stays correct but the board needs pagination, which it does not have.