KetylBody
The ketyl radical's look — the thing the player IS, and the brightest object in the still.
KetylBody
The ketyl radical's look — the thing the player IS, and the brightest object in the still.
The player is a sodium benzophenone ketyl: a ketone that took one extra electron and went deep blue-violet, and that dies the instant it meets water. So the body is built as the molecule it is rather than as a ball of light. A carbonyl carbon at the centre, an oxygen off-axis carrying the charge, two phenyl rings in benzophenone's propeller — each ring six thin glowing rods closing a hexagon — the C=O drawn as the double bond it is, and one small fast spark on an inclined orbit for the unpaired electron. Around that: a shell of light with an edge you can see, a soft aura with no edge at all, and the afterimages the radical lays down as it drifts.
It reads no input and moves nothing. Where the ketyl IS belongs to KetylPilot;
what it LOOKS LIKE belongs here.
What it listens for
| Event | What it does |
|---|---|
charge(v, max) | the light in it, the speed of the spark and the rings, the pitch of its hum |
wetness(v, max) | takes the colour out first, then the light; the corona breaks into beads |
discharged(pos, dir) | flares, and throws a forward half-circle of cyan-white light out to Still.DISCHARGE_RADIUS |
surged() | flares shorter, and lays the trail down four times as fast |
quenched() / sealed() / runStart() | the two endings and the reset |
Still.state.charge / .wetness are read directly until the first event of that
kind arrives, so the radical is right on its very first frame rather than after
core has spoken once.
How one material serves forty parts
Every piece draws through one of two shaders in /source/ketyl, and every piece
reads the same per-instance lane:
lane 0 = ( charge 0..1, wetness 0..1, flare 0..1, fade 0..1 )
renderer.instanceData.set writes it. That is what lets one .material serve
the nucleus, both rings, the whole trail and the discharge fan while each of them
differs in a number. A block nothing has written yet is all zeros, which would
read as a dead, invisible radical, so both shaders fall back to (1, 0, 0, 1)
when the whole lane is zero.
Fields
| Field | Default | What it is |
|---|---|---|
scale | 1.45 | size, as a multiple of Still.KETYL_RADIUS |
trailNodes | 14 | how many afterimages the trail is made of |
trailStep | 0.045 | seconds between trail samples while moving |
trailLife | 0.85 | seconds an afterimage takes to go out |
lightIntensity | 26 | peak intensity of the point light the radical throws |
lightRange | 11 | that light's range, in world units |
humVolume | 0.62 | how loud its own hum sits under the rest of the mix |
claimBloom | true | register a bloom pass if nothing else in the world has |
claimBloom yields: it looks for an effect already named bloom and does
nothing if one is there, so whichever slice owns the atmosphere wins simply by
registering its own.
The sound of it
/source/ketyl/bake_hum.luau computes ketyl_hum.soundClip in Luau — a low
fundamental with its fifth and its octave, three of them half a hertz apart so
the tone beats, and a thin shimmer above. It travels with the body on the
ambient channel, under everything else. Charge pitches it up, water pulls it
flat and shuts it, a discharge lifts it.
It is a loop, so every partial is a whole number of cycles across its two
seconds and it is encoded as codec = "pcm". Re-bake it with
execute { file = "/zero/source/ketyl/bake_hum.luau" }; it is idempotent.
Scoped to this part · feeds back into the world's score.