Commit graph

4 commits

Author SHA1 Message Date
CRT
909bfa0f9f Land the mill's ladder, and stop the table calling six models static
Two things, both found by pulling on the handcart's thread.

THE LADDER. I said three models had parts floating a few millimetres. One did.
The other two readings were my own measurement error and worth writing down,
because it is the same error as the vertex-sampling ones: I measured each part
against the model's BOUNDING BOX minimum, and a part deliberately bedded into
the ground drags that datum below the floor, so every correctly placed part
beside it appears to float by exactly the bedding depth. The forge's base
stones settle 6.8 mm and the well's drystone kerb 9.3 mm, both on purpose, and
the bellows and the well frame sit exactly on the floor their own source
declares. Nothing wrong with either.

The post mill's tail ladder was real: 14.5 mm clear. It already carried two
analytic corrections for the same corner, both correct about what they
describe, and neither accounting for the chamfer — the corner they compute the
drop of has been cut off, so the residual was very nearly the chamfer itself.
Rather than add a third derivation to a spot that has now been got wrong three
times, the assembled ladder is measured and put on the floor. A bounding box
cannot be wrong about this the way arithmetic can, and it stays right when the
chamfer or the rail section changes.

`restsOn` now covers the mill's trestle and ladder too, and its doc comment
carries the bedding caveat, so the next person to reach for it knows the datum
is the bounding box and knows what that costs.

THE ANIMATED COLUMN. `catalog-table.ts` decided it with `built.action`, which
is the VIEWER's hand-registered button and exists for five models. So the
generated table published the grindstone, the stone well, the post mill, the
cauldron, the forge and the handcart as static — six of the eleven models that
carry actions, including three of the most animated things in the kit.

The obvious replacement is worse. `core/kit.ts` wraps update as
`update: (dt) => options.update?.(dt, runtime)`, so it is a function on every
model whether or not that model does anything with it, and switching to it
marked all thirty-seven animated. One flag too narrow, its replacement too
broad.

So the generator now runs each model — a second of frames, then its action if
it has one — and asks whether anything moved. That catches what moves by
itself. What moves ON DEMAND it cannot catch, because the motion is behind an
interface the catalogue never exposed, so `Entry` now carries `actionNames`
from the model's own actions. Eleven models, and both documents list them.

Worth keeping: this table is generated precisely so it cannot go stale, it says
so in its own prose, and it was wrong for months. Generated is not the same as
correct — it only moves the mistake into the generator, where nobody looks.

Also in the npm README, which is the page npmjs.com renders: `vibe3d init` is
now the first line rather than unmentioned, `@types/three` is called out (three
ships no declarations, so without it the installed source does not typecheck),
the `@/` alias and `allowImportingTsExtensions` have a Project setup block, and
the one relative link is gone — npm resolves those against the repository, and
this one pointed at a directory the package deliberately does not ship.
`homepage` now points at the live site instead of a repository that is private.
2026-08-27 16:36:24 +07:00
CRT
a038f622dd Put the config step before the command it is required by
`vibe3d add @medieval-kit` resolves the namespace through `models.json` and
stops dead without it:

    Registry @medieval-kit is not configured in models.json

The registry README printed the two install commands FIRST and the
`models.json` entry underneath, introduced with the word "first". A reader
copies the top block, runs it, gets that error, and leaves. `vibe3d init`
seeds only `@scifi-kit`, so this is every new reader, not an edge case.
Config block first now, commands after it.

The cover card said `bunx vibe3d add @medieval-kit/wooden-chest`. Both address
forms are valid, but the bare namespace resolves through the registry's
`defaultItem` and installs the whole kit, which is what someone looking at a
card of the whole kit is being offered. A card has room for a command and not
for the `models.json` entry that command needs, so the shorter promise is the
honest one — and naming one arbitrary model implied a precision the card cannot
deliver.

Verified against the CLI rather than inferred from it:

    add @medieval-kit/registry   -> Registry @medieval-kit has no item named registry
    add @medieval-kit            -> Would install @medieval-kit · 46 files
    add @medieval-kit/grindstone -> Would install @medieval-kit/grindstone · 10 files
2026-08-27 15:26:45 +07:00
CRT
b29762a540 Draw the card with a command instead of a browser
The card a shared link unfurls to was a picture of the models with no title on
it, and the models on it were too small to tell apart. Both are fixed, but the
part worth writing down is how.

The first attempt was an HTML page composed in the site's own fonts and
screenshotted. It looked right and it was wrong: the model count on it becomes
a picture of a number the moment the shutter closes, and `check-docs.ts`
cannot read a picture. That is the exact failure that check exists for — the
READMEs claimed 27 models when there were 37 — and a card is the worse place
to have it, because nobody reading one can see the kit to know it is stale.

So `bun run cover:build` counts the catalogue and draws the numbers it counted.
`site:build` runs it, which means the published card cannot be older than the
models it shows, and the day a model is added the card says 38 without anyone
remembering to make it.

Drawing type needed type. `scripts/text.ts` reads the outlines out of a `.ttf`
and fills them with a scanline pass — four sub-scanlines a row with exact
horizontal coverage, which is more code than sampling a 4x4 grid and visibly
better at the 16px the two mono lines are set in. It is deliberately partial:
format 4 cmap, simple and composite outlines, advances, no kerning or shaping.
What it cannot draw it throws on, because a headline missing a letter is not
something you notice before it is published. Archivo and IBM Plex Mono come
with it under the OFL, the same two faces the site is set in.

The renderer moved to `scripts/raster.ts` so the card and the contact sheet
draw from the same code and cannot disagree about what a barrel looks like;
`render.ts` is now the command line over it, unchanged in what it does. Its
one new flag is `--ground`, a flat background instead of the per-cell
gradient: forty gradients side by side read as banding, and on the page's own
ink the models float on the card as one field instead of sitting on it as a
grey slab.

Ten columns by four, because 1.91:1 and 37 models want 40 cells and at that
shape the cells come out square, which is what lets a barrel look like a barrel
at 116 pixels. The last row is centred — three empty cells left flush is a
notch in the corner, and a short row centred is how a contact sheet ends.

Byte-identical across runs, so it does not churn the diff on every build.
2026-08-27 15:00:15 +07:00
CRT
dfee59381e Give the published page a card
A link to the site showed a URL and a blank square. It now carries a title, a
description, a canonical URL, an icon and a full set of OpenGraph and Twitter
tags, so a page nobody has opened yet arrives as something you can look at.

Two of those tags have to be ABSOLUTE and it is not a style question: a
scraper fetching the page has no base to resolve `/cover.png` against and
drops it silently. `build-site.ts` now copies the images into the output as
well, because an `og:image` pointing at this host has to have the file ON this
host — a card that 404s shows nothing, and nothing about the page itself looks
wrong while it happens.

The cover is a real 1200x630 render rather than a crop of a square one, which
took a change to the renderer worth having anyway. `newFrame` and
`frameCamera` both took a width and a height already; only `renderOne` was
insisting they be equal. And with a non-square frame the old camera fit showed
its flaw: it fitted the bounding SPHERE, which is one number and wrong for
anything not roughly round. The kit scene is wide and flat, so its sphere is
far taller than the scene is, and fitting that into the vertical field left the
models in a band across the middle with empty air above and below — most of a
1.91:1 card. It now fits the BOX, iteratively, because perspective is not
linear in distance: place the camera, project the eight corners, move by how
far the worst one lands past the edge. Two passes is inside a pixel.

That change improves every render, not just the card: on the contact sheet
every model is visibly larger in its cell and nothing crops.

`check-docs` now watches `viewer.html` too. Its description quotes the model
count and the triangle total, which is the same sentence that went stale three
times in the READMEs before that check existed — and a stale number in a card
is worse than one in a README, because nobody reading the card can see the kit
to know it is wrong. The description says "37" rather than "thirty-seven" for
exactly that reason: a word is not a number the check can find.

Also repointing the npm README's two images from `raw.githubusercontent.com`
to the live site. They were pointing into a private repository and would have
404'd on npmjs.com; now they do not depend on the repository being readable at
all.
2026-08-27 14:34:59 +07:00