Log inGet started
World

The Eighth Ketyl

Branchmain
Branchesmain
Commite99ff0a216

README.md

KetylCore

The core slice's own shared surface, and nobody else's. Conductor and KetylPilot are the two halves of the rules of THE EIGHTH KETYL, and this is where they agree about what the controls read, where the ketyl is, which way it faces and whether it may move.

Other slices talk to core through the Still event bus, which is the contract the whole game shares. Nothing outside the core slice reads this module.

local Core = require("game.KetylCore")

Why it exists at all

Editing a component hot-reloads it, and a hot-reload re-executes the chunk: anything a component held in a file-scope local comes back at its default while the instance keeps running, and anything it subscribed to in awake() gets subscribed a second time. So the run's clocks and the registrations live here, and the components register plain functions that this module calls. Re-registering replaces; it never stacks.

Editing this file is the other direction, and it behaves differently than the shape above suggests: the module table stays the same object, its functions are replaced, and every local behind them starts over — so the run's clocks, the body, and the record of who holds the controls all come back empty. The dependent components' awake() does not run again to notice. Each of them therefore watches for the one reading that says it happened — nobody holding the controls, or an empty handler table — and re-arms or re-installs from update.

The same problem applies one level up — this file can be reloaded too, and the run that subscribed is the only one holding the unsubscriber. modules.onUnload is the answer: it hands the outgoing run a moment, with its own locals still in scope, to drop everything it put on the Still bus and on the controls, just before the next run replaces it. Within a run, wire() and arm() release before they register, so calling either twice is a replacement too.

The surfaces

SurfaceWritten byRead by
Core.runConductorConductor
Core.bodyKetylPilotConductor
Core.inputthe armed input mapKetylPilot
Core.pressConductorthe armed input map
Core.handlersConductorthe bus forwarders

Core.body.placeAt is the one write that goes the other way: the Conductor asks for a teleport by putting a {x, y, z} there, and the pilot performs it and clears it.

The numbers

Everything about the still, the ketyl's speed, its charge and its wetness is in Still.module and read from there. What is here is the shape of the run — the parts of the loop that are rules rather than fiction, which no other slice needs to agree with: INTRO_SECONDS, GROWTH_GRACE, ASCEND_RADIUS, ASCEND_TIME, PLATE_MARGIN, FACE_TURN, the hover bob, ENDING_LOCKOUT, and the two warning thresholds.

The rules, as functions

approach · hoverY(n) · clampToPlate(x, z) · riserDistance(x, z, n) · arrivalPoint(n) · dischargeFalloff(dist, cosAngle) · controllable(phase) · invulnerable(now). No state, no side effects — a test can call them without a running game.

dischargeFalloff is the shape of a discharge's arc written once. Core does not apply it; the blennoma slice decides what was hit. It is here so that both answers come from one function rather than two that drift.