Commit graph

33 commits

Author SHA1 Message Date
CRT
1148d3ed25 Add the shield and the forge, both unfinished
Committing these because the code should not be lost, not because they are
ready. Both pass every check and neither looks good enough to ship, and the
catalogue table and contact sheet are deliberately NOT regenerated so the
kit does not advertise them yet.

The shield opens a category the kit did not have at all -- no arms or armour
anywhere -- and its construction is right: planks cut to the chord of the
circle rather than squared off, a rawhide rim gripping their ends, an iron
boss over the hole the hand goes through, and quarters painted on the
diagonal by giving each plank's two halves different colours, which costs
no geometry because the halves exist anyway.

The forge is the other half of a smithy that has had an anvil and no fire
since early on: a raised hearth at working height, a bed that `setLit` puts
out without taking the charcoal with it, a tapering stack over the back,
and a bellows blowing through the side.

Four faults found and fixed on the way, and three of them are the same
mistake:

A two-rotation chain put the bellows boards on the wrong axis -- thickness
along Z, length along Y -- so both boards stood on edge in nearly one plane
with every face coplanar. The lever went the same way, ending up across the
bellows instead of over it and touching the post at neither end, which the
support check called a floating bar. And the shield's boss was turned -90
degrees about X instead of +90, which pointed the dome into the back of the
shield and left only its open rim showing from the front -- the reason the
boss rendered as a flat black ring.

The fourth was subtler and took three passes to see. Every plate of the
bellows -- two boards, a gusset, two ribs -- was built from one profile, so
all five ended at exactly the same x and began at exactly the same x. I
staggered the tails and the check moved to the noses; staggering both is
what fixed it.

What is still wrong, and neither is small: the forge's fire bed reads as a
black hole with a few orange specks rather than a bed of coals, and the
bellows still reads as loose boards rather than a bag of leather. The
shield's paint comes out pastel where the reference is deep, and it leans
further than it should.

34 models, 28 728 triangles, all checks pass, 0 floating cases, build clean.
2026-08-23 15:47:44 +07:00
CRT
ad449f1677 Add the vegetable heap
Eight kinds mixed by two sliders: how many of them appear, and which one
leads. Turnip, cabbage, onion, leek, parsnip, carrot, tomato, potato.

The first six are what a medieval European kitchen had and the last two are
New World crops that only reach Europe after 1492 -- they are in because a
kit is more useful than it is a museum, and the carrot is orange rather
than the purple a medieval one would have been. The kinds are ordered so
that the period-correct set costs no thought: `lead: 0` with `kinds: 6`
gives exactly those six and never reaches the two after them.

Two parts, `bulbs` and `roots`, because those are the two ways a vegetable
rests -- on its base or on its side -- and a consumer wanting one should not
have to take the other.

Four faults on the way in, and every one of them was about placement rather
than shape.

The header claimed each vegetable is authored with its lowest point at the
origin, and I asserted that instead of measuring it: a leek's blades lean
four times its own radius away from the axis, and a cabbage's loose leaves
hang below its base. Whichever vegetable dug in deepest then defined the
model's floor and made every other one look as though it were hovering.
Reading the bounding box and setting each one down by what it actually
measures makes the claim true by construction rather than by an argument
that has to be right about eight shapes.

The leaves were flung off their vegetables outright. They were built
already in place and then rotated, and rotation turns a body about the
ORIGIN, so each one moved away by its own distance times the sine of the
angle. Origin, rotate, place -- which is the order the hoe's neck already
has a comment about.

Tightening the heap so it read as a pile rather than a display then made
the bulbs collide, twice. Standing vegetables were never turned about their
own axis, so every turnip presented its facets at the same phase and
neighbours met in parallel faces. And where the turnip's cream body meets
its purple shoulder -- two lathes, because `colourTop` interpolates across a
whole profile and bled the purple down to the root -- both pieces were
capping an end that is buried inside the vegetable. Two turnips touching
met on those hidden discs. They are not generated any more, which is the
same saving `bandGeometry` makes on the inside of a hoop.

32 models, 26 939 triangles, all checks pass, 0 floating cases, GLB
round-trip clean, Conformant, build clean.
2026-08-23 15:21:08 +07:00
CRT
9d027fcd06 Add the cauldron and the handcart
Two models that each fill a hole the catalogue could name.

The kit had a torch and a lantern and no HEARTH, which is the wrong way
round: a cooking fire is what everyone in a medieval scene is facing and
the only light in most of them. The cauldron hangs from a tripod over a
ring of hearth stones, with embers that `setLit` puts out. Its belly is
wider than its mouth, it has three short feet because it stands in the
ashes as often as it hangs over them, and it hangs by a bail rather than by
its rim.

And there was a `cart-wheel` with nothing to put it on -- while every
container in the catalogue, crates and sacks and barrels and baskets, is a
thing you would MOVE. The handcart's axle sits aft of the bed's centre so a
load leans on the puller rather than away from him, and its shafts leave
the bed at an angle and rest on the ground, which is what lets one person
lift them to waist height without the load shifting.

The cauldron found a fault in the mill on its way in. Both build their
splayed legs the same way -- lean about X, swing about Y, translate out
along the radius -- and the lean was POSITIVE, which carries the top the
same way the translate does. The two add, so the head ends up at the full
outboard radius and the foot at the axis: the tripod stood on its apex and
the mill's quarter-bars stood on their heads, splayed over the cross-trees
and gathered at the post, which is precisely backwards for a brace whose
job is to carry a load out to the ground. Nothing had caught it in the
mill, because a bar joins post to cross-tree either way round and the
bounding box is identical; in the cauldron it separated the tripod from the
ground and the support check finally had something to say.

Lowering the pot to where it could actually cook then broke its chain, and
for a reason this session has seen before: the link radius was derived by
dividing the drop by the link COUNT while the links are laid out over
count-1 gaps, so each link came out a little smaller than half its own gap
and the chain came apart as soon as the drop grew. The tavern sign's chain
had the same slip.

31 models, 25 221 triangles, all checks pass, 0 floating cases, GLB
round-trip clean, Conformant, build clean.
2026-08-23 14:57:31 +07:00
CRT
ad6aed30dd Add the stone well, and stone to the palette
A village well: a drystone kerb, a timber frame over it, a windlass with
its collars and crank, and a rope and bucket that lower down the shaft.
`setDepth` and `setWinding` drive the bucket; its part origin is the
roller's axis, so winding is a translation and costs nothing per frame.

The kit had no masonry at all, which for a medieval catalogue is a hole
rather than an omission -- a well, a trough, a millstone, a boundary wall
and a hearth are all stone before they are anything else. `stone` is the
twelfth slot. Its colour sits close to weathered oak on purpose: what
separates the two at a glance is not hue but that stone scatters light
completely flat, and roughness is not something vertex colour can carry,
which is exactly the argument the slot table makes for keeping iron and
steel apart.

Four faults on the way in, and three of them are ones this session has seen
before in other models.

The blocks were written footprint-first as [wall, arc]. `rotateY` carries a
footprint's +Z out along the radius and its +X round the circumference, so
every block sat across the ring rather than along it -- each one sticking a
block's length out of a wall a block's width thick. The kerb came out as
rubble thrown at a circle.

The rope started at the roller's axis, which put its whole surface inside
the barrel's hollow interior touching nothing. Then it started in the eye
of the wound coil, which is also a hole. It has to begin within the coil's
material, between 1.0 and 1.34 of the roller radius, so that it crosses the
coil's outer face on the way down.

The actions set `anchor.position.y = 0`, which destroys what `origin` had
just done and dropped the whole rope and bucket by the height of the
roller. Everything the actions touch is now an offset from where the origin
put the anchor.

And the masonry was over the triangle budget at 2 784, almost 1 900 of
which were chamfers on forty-four blocks -- edges the roughen pass softens
anyway, and stone is the one material here where a sharp arris is not
wrong. Plain tapered boxes bring the model to 1 376.

Two things about the wall itself, both from looking at it. Varying each
block by a tenth in arc and thickness put every stone at its own radius and
its own width; a dry wall is irregular the way a hand-cut stone is
irregular, which is a few percent. And blocks cut to exactly their share of
the arc cannot close: a flat face on a curve leaves a wedge of daylight at
every joint, so they are cut long and bite into each other -- safe here
because each is rotated to its own angle and no two adjoining faces are
parallel.

29 models, 22 043 triangles, all checks pass, 0 floating cases, GLB
round-trip clean, Conformant, build clean.
2026-08-23 14:18:08 +07:00
CRT
6b86bb383b Refresh the catalogue table and contact sheet
The mill and the sign both changed shape after the table was last generated,
so it was claiming 20 523 triangles against 20 667 and drawing the sign the
wrong way round.
2026-08-23 14:02:29 +07:00
CRT
a531931069 Hang the tavern sign along its arm instead of across it
The board hung the wrong way round. Its width ran in X while the bracket
projected in Z, so the sign stuck out sideways from the end of the arm --
which is not how an inn sign hangs. The arm comes out from the post and the
board hangs UNDER it, in line with it, so it faces the road both ways and
can be read walking past. Everything that positions the board, its battens,
its lugs and its chains moves from one axis to the other, and it swings
about the arm's own axis now rather than across it.

Turning it the right way also removed a piece. The cross-bar existed
because two chains set either side of a centre-line arm passed it on both
sides and touched nothing; with the board in line, the chains hang from two
points ALONG the arm and the arm is the cross-bar. The note explaining why
that piece had to exist is worth keeping in the history: it was a correct
fix to a problem that only existed because of the orientation.

28 models, 20 667 triangles, all checks pass, 0 floating cases, GLB
round-trip clean, Conformant, build clean.
2026-08-23 13:30:04 +07:00
CRT
1d4950445f Sweep the mill's sails clear of it, and give the ladder a door to reach
Two faults, both reported from looking at the model.

The sails passed through the buck. A tilted shaft spreads its sails through
depth -- at 0.14 rad a 3.5 m sail swings 0.49 m fore and aft -- and with the
hub only 0.08 m in front of the gable the whole lower half of the disc was
inside the building. Both halves of the fix are what a real mill does: the
shaft tilts a few degrees rather than eight, just enough to keep the sails
off the boarding and put some of their weight on the thrust bearing, and it
projects far enough out that the sweep happens clear of the wall. Far
enough and no further -- the clearance the disc needs is sailLength times
sin(tilt), about 0.26 m, and the first correction overshot to nearly a
metre, which left the sails hanging off the gable with only the shaft
between them.

And the tail ladder climbed to blank boarding. A mill is entered from its
tail, through the one wall the sails never sweep past, and the miller comes
up the same ladder he turns the mill with; a flight of steps ending at a
plain wall was the one thing about this model that could not be true. The
door is boarded proud of the wall with two ledges and a frame, because
there are no booleans in this geometry and a door of applied planks is how
a plank building is actually closed.

28 models, 20 679 triangles, all checks pass, 0 floating cases, build clean.
2026-08-23 13:27:06 +07:00
CRT
1888616315 Land the spilled coins, plane the bench's tenons, and sharpen the support check
Three defects reported from looking at the models, and the first of them
says something about the check that was supposed to catch it.

Every third coin was lifted to "land on top of the previous one" while its
position stayed independently scattered, so it was almost never above
anything and simply hovered about 3 mm off the ground. A stacking coin now
takes the previous coin's PLACE as well as its height. The tilted ones were
lifted by half their thickness plus their circumradius; a coin is a
nine-sided prism and can come to rest on a face rather than a corner, so
the lift uses the apothem and its own radius, erring towards bedding into
the ground rather than floating above it.

The support check missed all of that, and the reason is worth writing down:
its voxel is the model's extent over the resolution, so at 64 a 0.19 m coin
pouch gets a 3 mm voxel -- exactly the size of the gap it existed to find. A
check coarser than the defect it hunts is worth very little. At 112 the
whole suite still runs in fifteen seconds.

Raising it immediately turned up a real fault nobody had seen. The log
pile's sawn ends were cut to exactly the body's end radius and placed
exactly at its rim, so disc and lathe met along a seam of no width: they
touched and never overlapped, and there was nothing joining them but two
edges in the same place. The caps are now three percent wider and seated
slightly inside, which is two millimetres here and true of a real log
anyway, since bark wears back from a cut face.

And the bench's through-tenons stood 42 mm proud of the seat -- a block of
oak the size of a thumb sticking straight up out of the surface you are
meant to sit on. A real through-tenon is wedged and then planed back, so
what shows is end grain and a hairline of shoulder. Three millimetres
proud, ten housed below.

28 models, 20 547 triangles, all checks pass at resolution 112, 0 floating
cases, build clean.
2026-08-23 13:23:09 +07:00
CRT
2b6e2b4d02 Lay the mill's windshaft along its own axis, and band it
Opening the viewer showed the mill reporting one material while declaring
two. It declared an `iron` slot and used it nowhere: the windshaft's own
comment called it iron-banded and then painted it oak like everything else.
Four sails pull on one shaft from four directions and what holds the whips
to it is iron strapping, so the bands are real and now exist.

Adding them exposed something that had been wrong since the model was
written. `taperedBoxGeometry` takes its third argument as a Y height, so
the shaft -- written with no rotation and only a z offset -- was a short
vertical post standing inside the buck rather than a shaft projecting from
the front gable. Buried in the body it survived every render unseen. The
bands, built the same way, came out coaxial with it, and the z-fight check
reported that immediately.

Two smaller things settled on the way.

The bands first went into `trestle`, where the support check found them
hanging at the model's origin: they are authored around the hub, so they
only make sense in the frame of the part whose origin is the hub. They
belong to `sails` anyway, since the shaft turns with them -- in a real mill
the sails are keyed to it.

And the four sails converged on the hub in perfect symmetry with their
faces meeting there. The fix is how the sails are actually carried: TWO
stocks pass through the shaft at right angles, one behind the other, each
carrying a sail at both ends. Four timbers cannot occupy one crossing, and
the check was right to say so.

Worth recording how the shaft was found, because reasoning about it failed
three times. Rebuilding the four sails alone, in every combination of whip,
rail and bars, gave zero overlaps -- which ruled the sails out and left only
what else was in that part.

28 models, 20 547 triangles, all checks pass, 0 floating cases, GLB
round-trip clean, Conformant, build clean.
2026-08-23 13:15:12 +07:00
CRT
d159acfd97 Add the post mill
The largest thing in the kit, and the one whose name is its whole design:
the entire body -- millstones, gearing, miller and all -- is balanced on one
massive vertical post and turns bodily on it, so the sails can be pointed
into whatever wind there is. The miller does that by walking the tail
ladder round, which is why the ladder is not a way up but a lever, and why
it reaches so far behind the mill.

Four parts, in the order the thing is built: `trestle` (two cross-trees,
the post standing on them, four quarter-bars), `body` (the boarded buck and
its pitched roof), `sails` (a windshaft leaving the front gable, with its
own origin so `setTurning` spins it alone), and `ladder`. `setTurning`,
`setAngle` and `update` drive the sails at about ten revolutions a minute
-- a working mill turns slowly, not in the blur people draw.

The sails are where the triangles went, and deliberately. A sail is not a
blade, it is a LATTICE the miller dresses with canvas according to the
wind. Drawn solid the silhouette is wrong in the one place everyone looks,
so the bars are modelled and the budget is taken from the buck's boarding,
which reads perfectly well as vertex colour. 1 516 triangles.

Four faults on the way in, all found by measuring rather than by looking:

The roof pitches were built `slope * 2` long and tilted by the complement
of the roof angle, which made two panels 2.79 m across a building 1.80 m
wide, standing up like the covers of an open book. Each pitch covers half a
roof, and the tilt is measured from the horizontal because
`chamferedBoxGeometry` takes an X-Z footprint.

The rails of the tail ladder were tilted the wrong way round. `run` is
positive -- the head sits at a larger z than the foot -- so rotating by
`-lean` laid the rails along the opposite diagonal to the one the rungs
were placed on. They crossed near the middle and diverged towards the ends,
and the bounding box looked correct the whole time because the translation
put the ladder where it belonged and only its internals were wrong. The
support audit found it as loose rungs, and found it only at the small end
of the height slider, where the voxel grid is fine enough not to bridge the
gap: the joint was never real at any size.

Fixing the ladder's foot, which sat 0.10 m below the cross-trees so the
mill hovered above whatever it was placed on, then displaced the rails
perpendicular to their own axis by almost exactly a rung's half-depth --
correcting the rails alone was worse than not correcting at all. Rails and
rungs now share one lift.

And the lowest sail tip came within 0.27 m of the ground, which reads as a
mill about to plough its own field.

28 models, 20 523 triangles, all checks pass, 0 floating cases, GLB
round-trip clean, Conformant at 30 items, build clean.
2026-08-23 13:01:33 +07:00
CRT
21b0393e1f Bring both READMEs up to date, and give them a picture
The registry README's model table was generated once and had gone stale in
exactly the way its own note warns about: it claimed 15 425 triangles
against the current 19 007, listed a barrel at 806 triangles that now has
rivets and six hoops at 1 690, and described "the barrel's 13 staves" when
there are 17. Regenerated from the models themselves.

Both READMEs now open with a contact sheet of the whole kit. It lives in a
tracked `media/` directory rather than in `renders/`, which is ignored
because it is rebuilt output -- a README cannot reference a file that is not
in the repository. The registry's copy points at the raw GitHub URL, since
the npm package ships only dist, README and LICENSE and a relative path
would resolve to nothing there.

The root README gains a Showcase section -- the 60 s default, why it is the
default rather than 30 s, and what makes the variation visible -- and the
support check as a fifth note among the checks worth explaining. It is the
most distinctive thing in the suite and the only one that catches a whole
class of fault the others are blind to by construction.

Also: the check count in that section said ~500 and the suite now runs 818.
2026-08-23 12:25:14 +07:00
CRT
96defca639 Stop createTinter sharing one Color, and fix what that was hiding
The tinter returned a single Color and mutated it on every call. The
reasoning was sound -- the geometry helpers read the colour immediately, so
an allocation per call is waste -- and it holds right up until two tints
appear as arguments of the SAME call:

    latheGeometry(profile, 7, origin, tint('char', 0.06),
      { colourTop: tint('charHot', -0.12) })

Both arguments are the same object and both carry whatever the second call
computed. That is why the torch's pitch head rendered hot orange instead of
black: the char never reached the geometry. The same mistake turned up
twice more in one session -- the anvil's stump came out painted in end
grain on every face, and the shovel nearly went the same way. Three
occurrences means the hazard belongs to the helper, not to the callers, and
a few hundred allocations at build time is not a price worth one class of
silent, invisible bug. `steelTint` and `ironTint` already allocate; the two
disagreeing was itself part of the trap.

With the colour actually arriving, the torch head is also fatter -- at 2.5
shaft radii it was a spindle, where a torch head is a fist of tow and rags
bound on, and it is the whole reason the object is not a stick -- and its
flame is shorter than the head that feeds it rather than twice its height.

The cart wheel's tyre was 23 mm of iron on a 0.52 m radius, set wider than
the wheel itself, and read as a pneumatic. A real tyre is 8 to 12 mm of bar
shrunk onto the felloe. It could not simply be narrowed: the felloe's axial
thickness jitters by ±7%, so anything from 0.93 to 1.07 of the width will
sooner or later share a plane with some piece, and the earlier fix for that
flicker was to go wider than the whole range at 1.1 -- which is what made it
look pneumatic. Going UNDER the range solves it the same way and is the
truer shape: the hoop is set into the tread, and on a worn wheel a sliver
of felloe shows at each edge where the iron has bedded in.

27 models, 19007 triangles, all checks pass, 0 floating cases, GLB
round-trip clean, Conformant, build clean.
2026-08-23 12:10:20 +07:00
CRT
64e8932d1b Bind the book the way a board binding is bound, and soften the pouch
The page block was written as 1.035 of the cover, spilling past it on three
sides -- the comment above it said so plainly, and it is backwards. The
overhang is called the square and it is the entire point of a board
binding: the boards take the knocks so the leaves do not. Written the wrong
way round the model read as a ream of paper with a leather lid, which is
what it had always looked like. Flush at the spine, inset on the three free
edges, and the leather now shows around the block.

With that, the boards are also thicker. At a ninth of the book each they
came to 14 mm against 48 mm of paper; a medieval binding is oak boards
first and leather second, and they are substantial.

And the book now has its corner bosses. They are the most recognisable
thing about a book of this period and there were none. Not ornament
either: a volume this heavy was stored flat, often on its side, and the
bosses hold the leather clear of the shelf so it does not abrade -- the
same edges the square protects.

The pouch's body goes from nine sides to thirteen. Leather has no facets,
and the body is the whole silhouette of that model.

The spilled coins beside it stay. That is not a fault: the config field
says "number of coins spilled outside", so the scatter is the model's own
idea and pulling it in would be redesigning rather than fixing.

27 models, 19007 triangles, all checks pass, 0 floating cases, build clean.
2026-08-23 12:04:00 +07:00
CRT
85f423fe10 Build the hoe's blade as a dished sheet instead of a bent box
The blade was a `chamferedBoxGeometry` put through `bendGeometry`. That has
four levels in Y, and once the blade was deep enough to look like a hoe the
bend had to work a 50-degree arc across those four: the cutting edge came
out as a row of steps rather than a curve. `dishedSheetGeometry` exists for
exactly this and is what the shovel's blade already uses -- one seamless
concave surface, curved at every level. It also removes the last
bend-a-box from this model, which is the pattern the helper now refuses.

The blade is deeper and properly wedged. A hoe is narrow where the neck
carries it and wide where it meets the ground, and at 0.115 of the tool's
length with sides nearly parallel it read as a tab riveted to a stick.

Two mistakes on the way, both mine, both caught by looking:

I lowered `neckSweep` from 112 to 95 after reading a render in which the
blade happened to sit edge-on to the camera, and mistook foreshortening for
a wrong angle. Reverted.

Then `curve` was written as `-0.34 * dish`. It is an absolute rise in
metres, not a ratio, so that asked for 340 mm across a 230 mm blade --
seven times its own half-width -- and the sheet came out as a pair of
wings. The shovel scales it by its own blade width and this now does the
same. The dish sliders on the two models run over different ranges, so the
coefficient differs; the quantity does not.

One thing deliberately NOT changed. The reference generated for this model
is a plain strap hoe, while this one is a gooseneck -- a curved forged neck
carrying the blade ahead of the shaft axis, as its description says. Both
are period-correct, and pulling the model towards the photograph there
would be redesigning it rather than fixing it. The blade's depth and wedge
are a different matter: those are true of a hoe whichever neck it hangs on.

27 models, 18759 triangles, all checks pass, 0 floating cases, build clean.
2026-08-23 11:57:59 +07:00
CRT
2772064c14 Thicken the bench and the table, and cut the arch into the bench's legs
Across the contact sheet several pieces read as flimsy, and I called that
systemic before measuring it. Measured, it is two models out of three.

The bench seat was height * 0.09, which is 40 mm on a bench 1.62 m long --
one fortieth of its own span, against roughly one twentieth in the
reference. The table top was height * 0.045, a 33 mm plank across 1.9 m.
Both are now about half again as thick. Medieval furniture is riven rather
than machined: the board is whatever the log gave, and a thin slab over a
long span also looks like it would flex, which is the specific way these
were reading as cheap.

The fence, which looked as thin as either, measures fine -- 150 mm posts
on a 1.25 m fence. What made it look spindly is that the default is 4.8 m
long, so the whole run gets scaled down to fit the frame while the
reference shows a single short section. That is a framing artefact, not a
defect, and nothing about the fence changes here.

The bench also gets the arch cut out of its feet. It is not decoration: a
plank leg standing on its whole edge rocks on any floor that is not flat,
and no medieval floor was, so the cut leaves two feet per board and gives
the bench four points to find the ground with. It is also the most
recognisable line in the silhouette and the reference has it. There are no
booleans in this geometry -- it is all non-indexed triangle soup -- so the
notch is made by building the board in three pieces and not filling it.

27 models, 18647 triangles, all checks pass, 0 floating cases, build clean.
2026-08-23 11:51:01 +07:00
CRT
7e14d9b314 Make the wooden tools wooden, and give the spade its handle
Three faults across two tools, and two of them come down to a model being
named after a material it was not made of.

The pitchfork had three steel tines, 0.24 m on a 1.5 m shaft -- 16% of the
tool against roughly 22% in the reference -- fanned out like a cultivator.
A hay fork has four or five, they are the working length of the thing, and
they stay close to parallel so a forkful stays on them. They are also
wood: the model is called wooden-pitchfork and a hay fork really was cut
and steamed from a branch, because iron is heavy at the end of a long shaft
and hay needs no edge. The socket stays iron, since that is the ferrule.

The shovel's blade was a sheet of polished steel. The strap that runs up
behind it has been modelled as an iron shoe all along -- a shoe for a board
that was being rendered as steel. A medieval digging shovel is a shaped
board with iron along its lip, so the blade moves to oak and the shoe
becomes the only metal on the head.

And it had no handle: the shaft simply ended in a swelled knob. A digging
spade is worked with both hands with the lower one round a crossbar, which
is what separates a spade from a stick with a plate on the end, and it sits
right at the end of the silhouette where the eye lands. The bar crosses the
grip swell rather than butting onto it.

Two notes for later. The wider tine spread pushed the fork past its size
guard at 0.35 m across; 30-40 cm is the head of a real hay fork, so the
limit moved rather than the geometry. And `createTinter` returns one Color
and mutates it while `steelTint` and `ironTint` allocate per call -- the
new code copies out of the first before the next call, but the two helpers
disagreeing about this is a trap in itself.

27 models, 18599 triangles, all checks pass, 0 floating cases, build clean.
2026-08-23 11:21:37 +07:00
CRT
7d043cd141 Put the anvil on the block it is used on
The file header has claimed this model "comes to ~0.75 m with a real anvil
stump" for as long as it has existed, while shipping a 0.34 m anvil on its
own, lying on the floor. An anvil is bedded into the end grain of a log:
the block kills the ring, and it puts the face at the smith's knuckle. The
stump is its own part, so anyone wanting the bare anvil for their own bench
hides `parts.stump` -- the same arrangement the bell's frame uses.

Two things went wrong on the way in, and the second corrects the first.

Passing `woodTint('oak', ...)` and `woodTint('oakEnd', ...)` as two
arguments of one call hands the callee the same object twice: the tinter
returns one Color and mutates it, which its own docstring warns about. The
block was painted end-grain on every face. That is a real bug and the fix
stands -- but it was not why the stump looked wrong, and the measurement is
what said so. Against the bench and the table on the same oak slot our
stump is LESS saturated, not more (0.35 against 0.45 and 0.49), so the
colour handling was never anomalous.

The actual gap was in one axis: value 0.54 against the reference's 0.28. A
chopping block is old, split and weathered nearly black, which is a
genuinely darker piece of wood than fresh oak, not a rendering artefact.
Sweeping the lift and measuring lands at hue 30 / saturation 0.28 / value
0.34, against 31 / 0.29 / 0.28.

The block is also wider than first drawn. The anvil overhung it, and a
block the anvil overhangs looks like it would go over the first time
anyone swung at it.

27 models, 18517 triangles, all checks pass, 0 floating cases, build clean.
2026-08-23 11:13:46 +07:00
CRT
363a20e991 Make bendGeometry refuse geometry it cannot bend
Every vertex is mapped by its own Y, so the curve only exists where there
are Y levels to sample it at. Handed a `boxGeometry` -- two levels, top and
bottom -- there is no midpoint to displace: the top face swings to one
angle, the bottom to another, and what comes out is a sheared wedge. It
looks like a curve in the source and is not one in the geometry, and
nothing anywhere reports it.

This session found that same mistake in three separate models: the
tankard's handle, and both the brace and the curl of the tavern sign. In
each one the author had written a curve, the geometry had never curved, and
every render ever produced had shipped the wedge. The sign's was the
expensive one -- the sheared curl pushed the bracket's top above the post it
is bolted to, and it was also hiding a second fault underneath it. Three of
the same bug is a pattern, so the helper now refuses rather than waiting
for a fourth.

The error names the fix, because the tempting response is to adjust the
curvature and the curvature was never the problem: give the piece levels,
or use `arcBarGeometry`, which sweeps a real arc and asks for the two
things that actually matter -- where the ends land.

Mutation-tested both ways: a box is rejected, a five-level lathe still
bends, and all four remaining callers (the broom's shaft, the hoe's neck
and blade, the pitchfork's tines) build unchanged, so they were legitimate
bends all along.

27 models, 18481 triangles, all checks pass, build clean.
2026-08-23 11:07:43 +07:00
CRT
82844abc3e Reshape the sack so it stops being an amphora
The profile put its widest point a quarter of the way up and narrowed from
there all the way to the neck, which is the silhouette of a vase. A filled
sack does close to the opposite: it SPREADS where it meets the ground,
because the grain settles and cloth has no stiffness of its own, then runs
nearly parallel up most of its height, and gathers in sharply only where
the cord is tied. Almost the entire difference between the two shapes
lives in the bottom level and in where the taper starts.

With the body right, the collar was what still read as a vessel: it opened
out to 0.46 of the radius over a fifth of the sack's height, which is the
mouth of a bottle. Above the tie there should be a hand's width of gathered
cloth and no flare to speak of.

27 models, 18481 triangles, all checks pass, 0 floating cases.
2026-08-22 14:05:31 +07:00
CRT
6bae10f593 Stand the bell's frame clear of the bell, and stop painting it brass
Three faults, one of them mine and one that had been there from the start.

The braces were the change this session declined and that reached the repo
anyway, in 715d6af: moved outboard of the uprights, where they connect to
nothing and read as broken sticks. Reverting them was not enough, because
the version they replaced was also wrong -- a single diagonal tilted about
Z, in the same plane as the bell, which had to be shoved in front of it and
drew a line straight across the one silhouette in this model that has to
read cleanly. Neither is what a bell frame does. A real frame is two
A-frames, one at each end of the beam, braced fore-and-aft in their own
planes, with the bell swinging in the gap. Tilting about X instead of Z
puts them there and the bell is never in the way.

The uprights stood at beamLength * 0.34, which at the default yoke is 0.165
against a bell radius of 0.18. With their half-width they spanned 0.147 to
0.183 and ran through the mouth of the bell. Nothing objected: the support
test sees one connected mass, which is precisely what a post driven through
a bell looks like. They now stand at the larger of the yoke's reach and the
bell's radius plus a margin, so a short yoke cannot close the frame onto
the bell -- and the beam's span is derived from that rather than from the
yoke alone, because decoupling the two first left the rail too short to
reach the uprights at the bottom of the slider and the two A-frames came
apart into separate objects.

The foot sat height * 0.16 below the mouth, leaving 24 mm between sill and
lip. A bell hangs clear of its sill; at that gap it read as resting on one.

Colour last. The bell was painted from the `brass` entry and rendered at
hue 39 / saturation 0.56 / value 0.56, against a reference at 33 / 0.34 /
0.33. That is not a subtle disagreement and, unlike the hay bale, the
measurement can be trusted here: a bale photographs desaturated because it
is thousands of straws shadowing each other, while a cast bell is a smooth
surface, so the number is the material rather than the texture. A separate
`bronze` palette entry, derived by inverting the renderer's own gain,
brings it to 30 / 0.31 / 0.43. It is a palette entry and not a slot -- the
mesh still resolves through `brass` -- so the coin pouch and the book's
corner fittings, where fresh yellow bronze is right, are untouched.

27 models, 18457 triangles, all checks pass, 0 floating cases, build clean.
2026-08-22 14:02:22 +07:00
CRT
177f06c9f7 Give the sign real ironwork, and hang the board from what it hangs on
The sign read as broken, and the reason was the same one the tankard's
handle had: `bendGeometry` applied to a `boxGeometry`. A box has two levels
in Y, and two levels cannot bend -- they shear. Both the brace and the
curl were sheared parallelograms rather than curves, and the sheared curl
is what pushed the bracket's top to 0.579 while the post it is bolted to
ends at 0.442. An arm standing above its own post is not ironwork, it is a
mistake, and it had been in every render of this model. Both are now real
sweeps from `arcBarGeometry`, with their ends placed inside the post and
inside the arm rather than corrected towards them afterwards.

Replacing the curl immediately exposed a second fault that it had been
hiding. The board's part origin sat at [0, pivotY, 0] -- the post's centre
line -- while every piece of the board and its chains is authored hanging
from the cross-bar at z = reach * 0.86, half a metre away. At rest that is
invisible. The moment the swing action runs, the assembly turns about a
point it does not hang from and the tops of the chains sweep off the bar
they are looped through. The support audit had been reporting this as a
detached board only because the sheared curl was accidentally filling the
gap; with the curl the right size, it failed honestly. The origin is now
the cross-bar and the geometry is written relative to it: same place in the
world, correct axis to turn about.

Two proportions with it. The post was 62 mm square carrying a 0.72 m board
two metres up -- thinner than the board is thick, and part of a habit of
under-sizing timber that the bench, the table and the fence share. And the
chain drop was 0.12, so the five links that connect board to bracket were a
smudge; the chain is half of what makes a hanging sign read as hanging.

Lengthening the drop grew the links from radius 0.0195 to 0.0434, at which
point they interlock in earnest and their six-sided facets, all built at
the same phase, met coplanar. Each link is now indexed differently about
its own axis -- deterministically, so the seeded stream is untouched, and
a real chain does not index its links either.

27 models, 18369 triangles, all checks pass, 0 floating cases, build clean.
2026-08-22 13:40:31 +07:00
CRT
88147f759e Give the bale back its cylinder, and its cords back their thickness
Against a photograph this was the worst model in the kit: an amorphous
pale lump with spikes in it, where the reference is plainly a drum of
straw with three cords round it.

Three numbers were doing the damage. The body is a seven-sided lathe --
too few sides to read as round on its own -- and it was then displaced by
0.045 of the height, which is 19 mm against a radius of 210 mm. A tenth of
the radius is not texture, it is deformation, and it left no axis to
recognise. Meanwhile the ends were pulled in by a third, turning the
silhouette into a cinched sack rather than a bale with the broad flat ends
the cord holds it square against. Thirteen sides, a sixth of the
displacement and a third of the end fade, and the form comes back. The
stray stalks are what should say "loose straw", and they can only do that
against a body that holds its shape -- so they are shorter too, since at
0.30 of the height they read as spikes driven in rather than stalks
working loose.

The cords were worse, in the quiet way. The ring radius was written as
`0.5 * cinch(x) + cord * 0.35`, which adds four millimetres to a
normalised 0.5: the body is built in radius-1 space and squashed
afterwards, so anything radial has to be in those units. The hoop stood
0.0044 proud before the squash and 1.85 mm after it. Three cords have been
modelled, merged, exported and invisible in every render this model has
ever produced. They are now expressed in the body's own units and sit in
the groove they pull, which is what a tie does.

27 models, 18257 triangles, all checks pass, 0 floating cases.
2026-08-22 13:30:48 +07:00
CRT
f136250707 Make the besom spray, and stop cutting its twigs to one length
The head read as a lampshade pushed onto a stick, and two numbers were
responsible.

The spray angle is derived as atan((tip - tie) / span), which at a tip
radius of 0.102 came to five degrees. Five degrees is not a besom; the
reference fans out until the spread at the sweeping end is comparable to
the length of the bundle itself, which is about twenty. The tip radius
carries that, and the slider range moves with it.

The twig lengths varied by three percent, so every twig finished within a
few millimetres of its neighbours and the bundle ended in a straight cut
across the bottom. That single edge is most of what made it look
manufactured: no bundle of cut birch ends level, and the ragged tip is
what identifies a besom on sight. Lengths now spread by about a third --
downwards only, never above the nominal span. Centring the spread on 1.0
first produced a 1.32 m broom from a declared length of 1.2, which the size
guard caught and which was a real inconsistency rather than a tolerance
problem.

Two supporting changes. The bundle ran from the binding down for 36% of the
broom against roughly half in the reference, so it is longer. And each
sheaf was a 31 mm slat, which at thirty-two of them read as a fan of laths;
they are narrower now and there are forty-six, keeping about the same mass
in the bundle with the pieces at the right scale.

One correction to the record: the head was not, as I first estimated by
eye, a fifth of the broom. Measured, it was 46% of the whole and 36% below
the binding. The eye was reading a diagonal reference against a vertical
render.

27 models, 18021 triangles, all checks pass, 0 floating cases.
2026-08-22 13:25:08 +07:00
CRT
ebc5cbec6d Close the basket's weave, fill it, and stop calling willow straw
Three defects, two of them mine.

The weave had gaps you could see the contents through. The band height was
a flat 0.11 of the model height while the spacing between rows is 1/rows --
0.167 at the default six -- leaving a 12 mm hole between every course. It
was a slatted crate, not a weave. The height now derives from the row
count so rows meet at any setting.

The fruit was invisible, and that was a regression I introduced. An earlier
pass fixed produce that hung level with the rim over a gap by resting it on
the base. That was right; what did not follow was that a basket deeper than
its contents then needs enough contents to fill it. Nine apples in two
layers topped out 12 cm below a 10 cm rim -- correctly supported, and
completely out of sight. The basket is shallower, holds fifteen, and the
layers nest at 0.95 of a diameter rather than 1.5, so the heap now mounds
over the rim, which is the only state a fruit basket is ever drawn in.

Closing the weave then exposed two collisions. Overlapping rows meet in
coplanar faces at the nodes where the undulation crosses zero -- on a
cylindrical basket every row shares one radius -- so alternate rows now sit
slightly proud of and behind each other, which is how a weaver's rod really
passes the stakes. And the rim's inner surface sat at 0.16055 against the
liner's 0.16065: a tenth of a millimetre apart, by coincidence, and the
same 22-sided prism. The rim is now the heavy rod it should be and clears
the liner.

Colour: the basket drew from the `straw` palette and rendered at hue 41,
saturation 0.59 against a reference at 29 and 0.50 -- bright yellow
plastic. Willow is a wood, so both the colour and the material slot move to
oak, which measures 7 and 0.03 away instead of 12 and 0.09.

It still does not look like the photograph, and the measurement cannot say
why. Filtering to pixels above 0.18 saturation discards precisely the grey,
weathered pixels that give the reference its character, so the statistic
reports agreement that the eye does not see. Setting the lift to 0.12 to
make willow "paler than oak" turned it salmon -- raising HSL lightness
holds saturation, so a saturated brown climbs towards peach. Sweeping the
lift over 0.00 / 0.04 / 0.08 moved the measurement by 0.01, which is the
clearest evidence that this axis is not where the remaining gap lives.

27 models, 17853 triangles, all checks pass, 0 floating cases, build clean.
2026-08-22 12:08:56 +07:00
CRT
01d00cdd26 Give the tankard a handle you could hold, and the lantern one at all
Both models had something in the place a handle belongs, and against a
photograph neither was a handle.

The tankard's was a flat box put through `bendGeometry` and then pushed
clear of the body by a hand-derived offset. Measured, the result stood
7 mm proud of a body 47 mm in radius, with the rest buried in the stave
wall -- no finger gap at all, which is why it read as a plank glued to the
side. The offset formula did not describe what `bendGeometry` does to the
geometry, and rather than deriving a better correction for a curve that
cannot be inspected, the handle is now an actual arc from
`arcBarGeometry`, specified by the two things that matter: where the ends
land and how far the belly stands off. It now stands 24 mm clear. The
proportions go with it -- the reference is a fist-sized vessel, ours was a
beaker.

The lantern's was a horizontal `bandGeometry` ring sitting on the flue: a
suspension eye, not a bail. A lantern is defined by being carried, so the
bail is the first thing the eye looks for. It is now a vertical arch whose
sweep runs about 20 degrees past a half circle at each end, which turns the
ends downwards and outwards so they cross the roof cone instead of sitting
tangent to it -- a clean half circle was rejected by the support check as
floating, correctly.

Removing the ring stranded the tongue that used to join it to the flue, and
leaving it in place produced the z-fight: its sides sat at z = +-0.00488
against the bail's +-0.00512, a quarter of a millimetre apart across the
whole apex. A bail that reaches into the roof needs no strap to hold it on.

Worth recording how that one was found. The first guess was that
`arcBarGeometry` self-intersects once the sweep passes PI. Building the
bail in isolation across six sweeps and segment counts gave 0 overlaps
every time, which killed the theory before any time went into fixing the
wrong thing.

27 models, 17433 triangles, all checks pass, 0 floating cases.
2026-08-22 11:54:54 +07:00
CRT
f64fdfd1eb Tune the oak palette against measured references, and record what did not work
With a reference photograph for every model it became possible to ask a
question the kit could not previously answer: is the colour right? Not
"does it look nice", but how far is it, in degrees and in saturation,
from the real material.

Our oak hue turned out to be correct already -- 26 degrees against 27 in
the photographs -- but saturation ran a median 0.08 high across ten wooden
models, worst on the ones that are almost entirely bare timber: the ladder
and the fence were each 0.16 over. That is the gap between weathered oak
and new pine. After the adjustment the median difference is 0.003 and the
mean absolute error drops from 0.097 to 0.055.

The same measurement said straw and linen were too yellow and too washed
out, and acting on it made them visibly worse, so that change is reverted
and the reason is written into the palette. A bale photographs as
desaturated because it is thousands of straws each shadowing its
neighbour; pushing a flat lowpoly surface to that number does not
reproduce the texture, it just makes the surface garish. The statistic was
real and the inference from it was wrong.

Two further measurements closed off the remaining colour theories. Our
saturation spread is 93% of the references', so the surfaces do not lack
variation, and the hue was never off. Whatever still separates these
models from their references is geometry, not colour -- which is where the
next pass goes.

Also adds the remaining reference shots: all 27 models now have one.

27 models, 17393 triangles, all checks pass, 0 floating cases.
2026-08-22 08:14:16 +07:00
CRT
75909a6219 Fix the log pile's axis, and finish translating the repo
The pile was the model I had been told twice was wrong and had twice failed
to find anything wrong with. A reference photograph of a real woodstack
found it in one look: ours was five long sticks lying across each other,
and the reference was forty short logs nested into courses.

The cause was a single rotation. `latheGeometry` builds along Y, and the
logs were turned with `rotateZ(PI/2)`, which sends +Y to -X -- so every log
lay along the X axis. That is the same axis the layout spaces them along.
Each log was 0.62 m long and its neighbour's centre was 0.13 m away, so
every log ran through roughly five of its neighbours. `restingHeight` was
right the whole time; it was solving tangency for an orientation the
geometry did not actually have. Rotating about X instead sends +Y to +Z,
which is the axis the solver assumes, and the pile immediately settles into
courses that rest on each other.

Nothing in the check suite could see this. Support saw one connected mass
and passed it, which is exactly what a heap of interpenetrating logs looks
like to a connectivity test.

Two smaller things the same photograph showed. The file header claims the
sawn ends are "far lighter than the bark" and that the pale discs are the
first thing you see; at +0.07 lightness they were a slightly different
brown, so the offset now delivers what the comment promises. And twelve
logs read as some firewood rather than as a stack, so the default is denser.

Also: .gitignore and .gitattributes were still entirely in Turkish. The
earlier translation sweep matched on file extension and never looked at
extensionless files. src/viewer.css tripped the scan too, but that was 80
occurrences of the CSS `var()` function, not Turkish.

27 models, 17393 triangles, all checks pass, 0 floating cases.
2026-08-21 19:34:44 +07:00
CRT
a0659e24ac Enforce "nothing floats", and rebuild the barrel from a reference photo
Two rules were missing, and each one had been quietly failing for a while.

The first is structural. Every check in this repo asked about surfaces --
winding, coplanarity, triangle counts -- and none of them could see a piece
of a model hanging in the air with nothing holding it up. A chest lid
separated from its chest passed everything we had. scripts/support.ts
voxelises the model, finds the connected components of occupied space and
requires each one to reach the floor; scripts/audit.ts runs that across
default, min, max, both sweep ends, actions and the midpoint of a morph, so
a variation cannot break what the default gets right. That audit started at
22 failing cases: the bell and the sign had no supporting structure at all
and now have a frame and a post, the lantern was in four pieces, the sack's
collar floated above its body, the basket's fruit hung level with the rim
instead of resting on the base, and the ladder's rungs were sized by a
formula that disagreed with where the rails actually were. It now reports 0.

The second is that we had no target. The feedback loop was an offline
render of our own output, which can only answer "is this broken", never
"is this right". Given a reference photograph of a cask, five defects were
obvious in seconds that three rounds of self-review had missed: four hoops
where there should be six, no rivets at all, a chime cut so shallow it did
not read, and a stave count low enough to leave a visibly polygonal
silhouette. All four are fixed here.

Two things the rivets taught us, both kept as comments rather than as
scars: a stud that tapers outward tilts its side normals back towards the
axis, which the radial check correctly reports as reversed winding -- a
real rivet head flares, so the honest shape is also the passing one. And
once seated deep enough to look right, the stud's buried inner cap falls
inside the shell the check inspects, so it is not generated at all, exactly
as bandGeometry already declines to build the inside of a hoop.

27 models, 16973 triangles, all checks pass, 0 floating cases.
2026-08-21 19:23:11 +07:00
CRT
50c7fc60b9 Translate the whole repository to English
The kit is going public, so everything a reader sees is now English:
27 model sources, the core vocabulary, meta.ts (titles, descriptions and
every slider label), the viewer UI, all script output, REFERENCE.md and
the READMEs.

Rules applied: only comments, doc comments and human-facing strings were
touched. Identifiers, numbers, logic, registry item ids, part names and
material slot names are unchanged — those are a published contract and
were already English.

Verified rather than assumed: for all 53 changed .ts files the code
skeleton (source with comments stripped and string contents replaced by a
placeholder) is byte-identical before and after. Full gate is green —
typecheck, registry conformance, ~510 model checks, GLB round trip and a
production build.

Also fixed here:
- index.html and viewer.html were missed by the translation pass because
  the file list only covered .ts/.md/.css. Both had lang="tr" and Turkish
  titles.
- The viewer formatted triangle counts with toLocaleString('tr-TR'), which
  prints 15.425 rather than 15,425.
- REFERENCE.md's model table header did not match what
  scripts/catalog-table.ts emits, so the next regeneration would have
  silently changed it. Aligned to the generator.
- Removed scripts/probe.ts and scripts/zdiag.ts; they were throwaway
  diagnostics that got committed by accident.
2026-08-21 14:35:13 +07:00
CRT
72f219d087 Süpürge, çapa ve çiti yeniden kur; kapanma/alaca parametrelerini sınırla
Üçü de render'a bakılarak elden geçirildi. Ortak sebep: her birinde
modelin KONUSU eksikti.

çit — birleşim yoktu. Dört kare çubuk ve önlerine konmuş iki lata;
derinlik/boy oranı 54:1, yani karton. Artık gerçek zıvana var: direk iki
yanak + köprü bloklarından kuruluyor, delik geometrik olarak var oluyor,
kiriş içinden geçip uçlarda karşı yüzden taşıyor. Ayrıca kiriş dağılımı
formülü kaç kiriş olursa olsun hep 0.28–0.78 aralığını dolduruyordu —
üstü ve altı doldurmak yapısal olarak imkânsızdı. 440 → 860 üçgen,
derinlik 0.09 → 0.31 m.

çapa — kaz boynu yoktu. Ağzı sapın ekseninden öne taşıyan kıvrık boyun
olmadan model "kürsü / yol tabelası" diye okunuyordu. Boyun latheGeometry
ile kurulup bükülüyor, ağız boyun ucunun teğetine hizalanıyor; ikisi de
hesapla, gözle değil.

süpürge — demet içi boş bir koni kabuğuydu: bütün levhalar tek halkada.
Üç eşmerkezli halka (6/10/16) kütleyi kuruyor. Bağlar artık demetin dış
yarıçapına oturuyor; eskiden ayrı bir tahmin formülüyle levhaların İÇİNDE
kalıyor ve hiç görünmüyorlardı.

Ölçerek bulunan iki gerçek hata:

- bendGeometry'nin etkisi geometrinin y=0'a göre nerede durduğuna bağlı.
  Orijinde ORTALANMIŞ bir gövde simetrik bükülüyor: iki uç aynı yöne
  gidiyor, siluet değişmiyor. Çapanın ağzında Z aralığı 0.0337'den
  0.0327'ye DÜŞÜYORDU — yani yorumda "sorunu çözüyor" dediğim kavis hiç
  yoktu. Tabanı orijinde kurulunca kaçış 44 mm. Yardımcıya yazıldı.
- Süpürgede açılma işareti tersti: rotateX(+f) levhanın ucunu -Z'ye
  atıyor, rotateY onu EKSENE doğru çeviriyor. Levhalar ekseni geçip karşı
  tarafa savruluyordu; demetin kabuk olmasının asıl sebebi buydu. Gözle
  değil ölçüyle yakalandı (genişlik 0.23 beklerken 0.13).

Sistemik: kapanma yarıçapı ve alaca hücresi modelin en büyük ölçüsünden
türetiliyordu. 4.89 m'lik çitte hücre 0.27 m çıkıyor, yani bütün direk tek
hücreye düşüyor ve doku sistemi hiçbir şey yapmıyordu; kapanma yarıçapı
0.69 m ile her şeyi battaniye gibi karartıyordu. İkisi de mutlak sınırlara
alındı — ahşabın damar lekesi nesnenin boyundan bağımsızdır.

Doğrulama: test yamalarının gerçek config alanlarına işaret ettiği artık
denetleniyor. Çapa yeniden yazılırken `bladeAngle` alanı kalkmıştı ama
test hâlâ onu yamalıyordu, yani o varyant sessizce varsayılan modeli
sınıyordu.

render.ts: --angles ile turntable. Tek bir 3/4 açı yanıltıcı — çit ancak
tam yandan bakınca cılız görünüyor.
2026-08-21 14:05:22 +07:00
CRT
8a982160ac wicker-basket ekle ve dokümanı 27 modele güncelle
Kullanıcının prop listesindeki son madde meyve/sebzeydi; sepetle birlikte
kapatıldı çünkü kitte zaten sepet eksikti.

Örgü, biçimi yapımdan türetiyor: yatay halkalar önce düz bant olarak
üretilip köşeleri açılarına bağlı olarak içeri-dışarı itiliyor
(yarıçap × (1 + genlik·cos(dikeySayısı·açı + faz))), ardışık sıralarda faz
π kaydırılıyor. Dikey çubuk başına iki segment yeterli — dalga tam olarak
her çubukta bir artı bir eksi örnekleniyor.

Halka başına iç yüz üretmek altı sırada ~800 üçgen tutuyor ve modeli
lowpoly bütçesinin dışına çıkarıyordu; yerine 44 üçgenlik tek parça iç
astar var.

Yeni `produce` materyal yuvası (mumsu kabuk, roughness 0.52). Meyve rengi
`hue` alanından geliyor: aynı modelden elma, şalgam ya da lahana sepeti.
2026-08-21 09:50:34 +07:00
CRT
a3300ff233 Kit'i 26 modele çıkar, doğrulamayı ve inceleyiciyi genişlet
Modeller (13 → 26): chest, bench, torch, lantern, bell, sign, hay bale,
sack, broom, tankard, book, phial, coin pouch. Kürek, çapa ve yaba
render'lara bakılarak yeniden yazıldı.

core:
- createKitModel artık tipli `actions` ve `update(dt)` taşıyor
- BuiltPart.extras: bir parça birden çok materyal yuvası kullanabiliyor
  (sandığın kapağı = meşe tahta + demir kayış, birlikte dönmek zorunda)
- BuiltPart.origin: parça başına dönme merkezi (menteşe, sarkaç)
- bendGeometry / roughenGeometry / mottleGeometry
- 5 yeni materyal yuvası: steel, brass, straw, cloth, leather, glass
- bandGeometry({ inner }) — serbest halkalar kapalı katı olabilsin

my-registry/meta.ts: katalog metadata'sı artık build.ts ve viewer için
tek kaynak. Doğrulama modelle metadata'nın uyuştuğunu denetliyor ve bu
eklendiği gün dört modelde gerçek bir sapma yakaladı.

Doğrulama (~500 kontrol):
- metadata uyumu, eylem semantiği, kare hızı bağımsızlığı
- scripts/render.ts: tarayıcısız yazılım rasterleyici. Bütün doğrulama
  geometrikti; "bu kürek küreğe benzemiyor" diyebilmek için modele
  bakmak gerekiyordu
- scripts/verify-glb.ts: dışa aktar → geri oku → karşılaştır

GLB dışa aktarımı: viewer düğmesi ve toplu CLI aynı kodu kullanıyor.

Viewer:
- parça paneli (solo/izole) ve materyal yuvası paneli (canlı renk override)
- kit görünümü: 26 model tek sahnede, ölçek ve ton tutarlılığı için
- configure() sonrası gölge bayraklarının düşmesi düzeltildi
2026-08-21 09:44:58 +07:00
CRT
98cfbab180 Initial commit: @medieval-kit registry and viewer
A lowpoly medieval procedural model library for Vibe3D, plus the model
inspector used to build it.

Registry (@medieval-kit, published as @medieval-kit/registry):
- core: deterministic mulberry32 RNG, slot-based vertex-coloured materials,
  a geometry vocabulary (box, prism, stave, band, head, flip), and the
  stable-anchor part slots the Vibe3D protocol requires
- wooden-barrel: 806 tris, 13 separate staves, iron hoops, recessed heads
- wooden-crate: 360 tris, plank rows on corner posts, forged straps
- iron-brazier: 423 tris, typed actions, per-frame flame flicker, own light

The kit declares no renderer capabilities, so it runs on WebGLRenderer as
well as WebGPURenderer. Colour variation lives in vertex attributes rather
than in materials, so each model keeps one draw call per material.

Verification (bun run verify) covers 64 checks without a browser: geometry
validity, winding by two measures (radial alignment for bodies of
revolution, signed volume for closed solids), coplanar-face detection for
z-fighting, stable root and anchor identity across configure(),
deterministic seeding, material ownership, and idempotent disposal.
2026-08-21 06:15:58 +07:00