reference

Changelog

Every published release and the unreleased changes on main.

⌖ Explore the library in 3D → · View as Markdown

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

0.16.0 - 2026-10-24

Added

  • Rift, a pair of linked rifts for walkable shortcuts within one world. Each end is a ragged, breathing tear in the air with an iridescent pink, violet and cyan rim that sheds glitter, and shows the other end live with real parallax. Walk into either and you come out of the other, momentum and all. Set the far end with destination. With active={false} both rest as small shimmering bubbles. For travel to another world or page, Portal is still the one to use.
  • Grass takes a height preset: height="short" (a lawn), "medium" (the default, unchanged), "tall" (a meadow) or "very-tall" (reeds up to your chest). A number in units still works. Taller grass grows thicker blades that lean a little, so it doesn’t read as needles.
  • Keyboard navigation in <WorldEditor> and the workshop’s Build view: hold W A S D to pan across the ground (Q / E for down and up) and the arrow keys to orbit, with ⇧ to go faster. With a node selected, W A S D Q E step it along the world axis nearest the camera instead (0.1 m, or one snap step in the workshop; ⇧ for ten), and a burst of steps undoes as one. @runek/core exports the camera half as useOrbitKeys for your own orbit views.
  • The registry is versioned: /r/<version>/ serves each release’s components, and /r/versions.json lists them. runek add (and list, info, validate, skill) now read the snapshot that matches your project’s @runek/core, so the source you pull only uses core APIs you have. --core <version|latest> picks another one; --core latest also upgrades @runek/core. A registry without versions.json (a local checkout, a fork) works as before.

Changed

  • Breaking: rng(seed) scrambles the seed before its first step, so every seeded component looks different for the same seed after you upgrade. Re-pick any seed you chose by eye. Before, the first value drawn for a small seed was close to 0 (every Person seed below 100 came out with the same gender), and rng(0) returned 0 forever. Same seed, same result still holds within a version.
  • Duplicate moved from d to ⌘D in <WorldEditor> and the workshop, and the workshop’s World panel from w to ,, to make room for WASD.
  • Player’s first-person view is much wider and softer at the edges, closer to how people see. It shows 140° across on a 16:9 screen (was about 90°) through a gently curved lens, so the room wraps around you without the tunnel stretch a flat camera gives at that width. Like most games it follows the screen’s shape: an ultrawide sees more across (about 160° at 21:9, up to 200°), a narrower screen less, down to 110°, and a phone held upright shows a narrower slice of that. Focus falls off smoothly with the angle off straight ahead, so turning toward something brings it into focus. Tune them with the new fov and peripheralBlur props (peripheralBlur={0} turns the blur off). Clicks and hover labels follow the lens. Third-person and overhead views are unchanged. @runek/core exports useLensPosition for your own <Html> labels.
  • Portal is now a swirling vortex around a lightless core instead of a spinning ring: spiral arms, a bright photon rim, drifting motes that fall inward, a soft halo, and light pooled on the ground beneath it. It faces the camera from any side, and seed now shapes the swirl and the motes.
  • Palm grows a full, feathery crown instead of a handful of drooping ribbons: each frond is an arching midrib lined with leaflets, the young fronds stand up at the top, and the older ones arch out and hang lower, turning slightly yellow. fronds now defaults to 16 (was 11) and frondLength to 2.8 (was 2.2), so an existing seed grows a different crown.
  • Person turns its head toward the player from 5 units away instead of 9, so figures across a room no longer all stare at you. Set lookRadius to change it.
  • Book and Bookshelf books are only hoverable and clickable once the player’s avatar is within reach (3 units from its eyes), so a shelf across the room can’t be opened from where you stand. Tune it with the new reach prop. Without a Player (an orbit camera, the editor) everything stays clickable. @runek/core exports the check as useReach for your own clickable meshes.

Fixed

  • runek add no longer installs components that need a newer @runek/core than npm has. The registry at /r served source from the unreleased main branch, so runek add bookshelf with @runek/core 0.15.0 failed to typecheck (useReach and DEFAULT_REACH missing). /r now serves the latest release, which also fixes older CLIs. (#21)
  • <WorldEditor>’s props panel keeps up with the world: it shows where a node is after a gizmo drag, a nudge, or an undo instead of the numbers it opened with, and editing one field no longer snaps the node’s other fields (or earlier edits) back to those stale values.
  • The workshop inspector’s number fields keep a value stepped with the arrow keys (it reverted on blur), show up to three decimals instead of rounding to the step (so a y of 0.002 no longer reads as 0), and no longer rewrite a value just because you clicked in and out.
  • The workshop’s wall sign no longer draws its “build · tune · share” line over the WORKSHOP lettering.
  • The camera no longer shakes while walking, most visibly in first person and on high refresh rate displays. <World> now steps physics once per rendered frame (splitting long frames into short sub-steps), in step with the avatar controller, instead of on a fixed 1/60 s clock.
  • A custom Player body (a Person from the workshop’s “use as play avatar”, say) stands on the ground instead of hovering about 26 cm above it. The controller floats its capsule on a spring, and Player now lowers the body (and the default capsule) by that float, at any frame rate. Bodies placed as documented, feet at y = -0.65, need no change. If you lowered yours by hand to make up for it, put it back.

0.15.0 - 2026-10-18

Added

  • The workshop’s avatar studio: a room behind the hall’s west door for customising a Person. Open it from the inspector’s customize, the doorway, the Studio mode, or 4. The figure stands on a turntable while the Avatar panel edits it tab by tab (looks, body, face, hair, clothes, hat and extras, pose) with picture tiles, color swatches, and sliders, and the camera frames the part you are on. Clothes are a drag-to-reorder layer stack that shows what tucks in. Each trait shows whether you set it or it came from the skin, the kind, the seed, or a default, with reset, pin, and a per-tab shuffle. A 3 × 3 seed grid, look export and import (.look.json), and “use as play avatar” round it out.
  • resolvePerson(props) reports what each of a Person’s traits resolves to and where it came from (prop, skin, kind, seed, or default), without building any geometry. PERSON_COLORS holds the curated skin, hair, and eye colors a seed rolls from.
  • Lights: TikiTorch (a bamboo torch with a flickering flame), CeilingLight (flush, pendant, or spot), Chandelier (one or two rings of arms, instanced, one shared light), DeskLamp (a banker’s lamp), and PictureLight (over a picture or on a bookshelf). Each takes on and nightOnly, which lights it only while the world’s sun is down.
  • Tableware: a plate, bowl, glass, cup, or water jug, or a whole place setting, with a drink fill line and stacking plates and bowls.
  • GameConsole with up to four controllers, TableTennis at regulation size, and ChessBoard, set from a FEN position or played out from the seed, on a table or its own pedestal (chessPieces gives the placement without rendering).
  • Screen, a display of any size on a wall, a stand, a desk foot, or in a frame, showing a seeded picture or desktop, a text banner, animated static, or a color. Poster, a wall print of seeded flat art with an optional caption and frame.
  • A code-only texture prop on Screen, Poster, Tv, and Monitor: hand it a texture your app owns (a video, a canvas, a render target) to show instead of the built-in content. The components still load nothing themselves, and the prop schema lists it as code only.
  • ArcadeCabinet game: invaders (the default, unchanged), blocks, maze, snake, or paddle, each laid out from the seed.
  • useSun() in @runek/core: the sun for the world’s time of day, for components that react to day and night. Sky and LightRig now use it.
  • isCodeOnly(prop) in @runek/core/data, and an instance prop type in the prop schema for live three.js objects such as a Texture.
  • Staircase form: solid (the default, unchanged) or floating, thin treads on two sloped stringers, open underneath, for a flight that spans a void such as an atrium, a loft, or a mezzanine.
  • The docs site’s navbar shows the current Runek version in a pill next to the title.

Changed

  • The library world’s component gallery is now a three-floor hall around an open atrium instead of one long corridor. Each floor holds a category (Grounds, Structures, Interiors), exhibits run alphabetically along the walls with a label where each section starts, and two floating flights cross the atrium between floors. A directory on the ground floor shows what each floor holds. The hall widens on its own as the catalog grows.
  • Gallery miniatures live in their own paused physics world, so an exhibit’s colliders can no longer block or push visitors (a Terrain or Shore miniature used to leave an invisible full-size floor over the walkway).

0.14.0 - 2026-10-07

Added

  • Agent-friendly docs: /llms.txt indexes every guide and component, /llms-full.txt is the whole docs set in one file, every docs page is also served as Markdown at /docs/<slug>.md, and a new “Runek for agents” guide walks through discover, install, compose, validate, and preview. Component pages now list every prop with its type, default, and description.
  • A machine-readable catalog: /r/props.json (every component’s props, types, defaults, and docs, generated from the TypeScript by just registry) and /r/world.schema.json, a JSON Schema for world files. Add "$schema": "https://runek.nullorder.org/r/world.schema.json" to a world for completion and checks in your editor. The $schema URLs that registry.json and runek.config.json already point at (/registry/schema.json, /registry/config-schema.json) now resolve.
  • runek validate <world.json>: checks a world against the catalog (unknown components, misspelled props, wrong value types, bad anchors, duplicate ids) with did-you-mean suggestions. Components you wrote yourself are recognized from your install directory.
  • runek info <name>: a component’s props (type, default, docs), dependencies, and a world-node and JSX example.
  • runek preview <world.json>: prints a workshop link that opens the world in the browser.
  • runek skill: installs a Runek agent skill into your project (.claude/skills/runek/SKILL.md by default).
  • --json output for runek list, runek check-world, and runek validate.
  • runek --version (-v, or runek version) prints the CLI version: a Pixelspace banner in a terminal, the bare version when piped.
  • validateWorld, suggest, matches, describeType, and the prop schema types (SchemaMap, ComponentSchema, PropSchema) are exported from @runek/core and @runek/core/data, so the same world checks run in the workshop, the CLI, and your own tooling.
  • A world file may carry a $schema key; serializeWorld keeps it.
  • Every component now accepts seed, as the contract requires. The 25 that had none (Bench, Chair, Table, Wall, Door, Window, Roof, Floor, Staircase, Arch, Pillar, Barrel, Bed, Shelf, Clock, Lamp, Sign, Well, Fountain, Lake, Shore, Ocean, Sky, LightRig, Player) accept it with no seeded variation, so nothing renders differently. The prop schema marks each component seeded or not, and the workshop only offers seed controls where a seed changes something.
  • Component pages show the component’s source in a collapsible panel.
  • The workshop: an in-browser workbench at /workshop on the docs site, and a door from the library to it. Your world renders live in a room, with the tools floating over it. An inspector generated from each component’s TypeScript edits every prop (sliders, palette-aware colors, enums, vectors, nested lists like clothes or openings). world.json edits two ways, with completion and checks. A live App.tsx runs in a sandboxed iframe and keeps its world.json import in sync. A component lab has bounds, scale, wireframe, normals, colliders, stats, a seed grid, an A/B compare and a determinism check. There are templates, snippets, play mode, a ⌘K palette, undo, autosave, share links that carry the world in the URL, and exports (world.json, App.tsx, PNG, the prop schema). Gallery cards and component docs open their component in the lab.
  • Person is configurable part by part, all as plain JSON: body (build, tone, height), face (eye color and size, brow color and weight, nose shape, lip color and fullness, blush), hair as { style, color }, and clothes, an ordered list of garment layers (tshirt, shirt, sweater, vest, tunic, coat, dress, robe, apron, trousers, jeans, shorts, skirt, shoes, boots, belt, scarf, cape), each with its own color and, for tops, sleeves and neck. Layers go inner to outer and the order decides the fit: a shirt listed before the jeans is tucked in. clothes={[]} is the bare base body. The flat props (outfit, topColor, skinTone, hairColor, …) still work as shorthands. New swept hairstyle and preppy skin.
  • useWorld().player: a ref to the mounted Player’s avatar (at eye height), or null when there’s no Player. Components can react to where the player stands instead of where the camera is.
  • The ground is queryable: groundAt(x, z) returns the highest walkable top at a point, computed from the same functions that build the meshes (no raycasts, no physics). createGroundIndex(world, registry) indexes a world once; useGround() gives the query inside a world; checkWorld reports problems. All of it also ships from the React-free @runek/core/data entry, for Node scripts and tests.
  • Anchored nodes: a world node’s anchor ("ground" or "surface") makes its position[1] an offset above the ground at its (x, z), so props stay on uneven terrain. The file keeps the offset, so moving or reseeding the terrain re-seats everything anchored to it. Works for any node, including groups, composites, and Player.
  • Surfaces: Terrain and Shore (terrain), Dock, Floor, and Slab (decks), and Lake (water) publish their tops as Component.surface, with the math in a plain surfaces/*.ts module that runek add copies alongside the component. People, trees, furniture, and buildings (including the house and room composites) are marked groundSitting.
  • Editor: Drop to ground / Drop to surface for the selection, an Anchor switch that keeps the node in place, and anchored nodes slide along the ground when dragged.
  • runek check-world <file>: flags buried and floating nodes and open water standing above its shore, with a suggested Y for each. --fix writes them into the file; --strict exits non-zero for CI.
  • Terrain accepts rotation, like every other component.
  • Guide: “Placing things on uneven ground”.
  • Person walks. patrol takes waypoints relative to its position (they turn with rotation); wander takes a radius and walks a seeded loop inside it. speed (by age: 1.3 m/s for an adult), pause at each waypoint, and loop ("loop" or "pingpong") shape the route. Where a figure is on its route depends only on the clock and the seed, so everyone viewing the world sees it in the same place. It follows the ground under it, eases in and out of each stop, turns into its corners, and swings its arms and legs in a walk cycle keyed to the distance walked, so feet don’t slide. The authored pose plays at each pause (a merchant walks to the stall, then works). Walkers step aside for the player and for each other (passing on the right when head-on), and wait when a wall blocks both sides. A walking figure’s collider is kinematic; a standing one costs what it did.
  • Person gait: the walk-cycle speed for a figure something else moves. As a Player’s body it now walks in step with the avatar instead of sliding.
  • useWorld().walkers (the figures walking right now, for components that want to steer around them) and PlayerMotionContext (the enclosing Player’s live speed).
  • World input: set false to ignore the keyboard (e.g. while a modal is open). Keys held at that moment are released.
  • World paused: stops the frame loop and physics, so a world that isn’t on screen costs nothing. Animations resume where they stopped.
  • isEditableTarget(target): whether a key event belongs to a text field rather than the world.
  • Person takes trips: route is a one-way walk through waypoints (relative to position, like patrol) that sets off at departAt (epoch ms), waits at its first point before then, and stays at its last point after, in its pose. Where it is depends only on the clock, so every viewer agrees and a reopened page needs no replay. onArrive fires once per trip, even when the arrival happened while the world was paused or unmounted. tripAt() gives where a trip had got to, for starting the next one from there.
  • Person actions, actionRadius, and onAction: walk near a figure and a prompt over its head shows what you can do (T Talk, I Info) with the keys the world’s controls bind; press one and onAction gets its id. Only the nearest figure in range shows a prompt and takes the key, and keys yield to text fields.
  • Interactable: wrap anything in it to offer the player the same prompt and actions (sit on a sofa, use a desk).
  • useInteraction(anchor, { actions, radius, onAction }) in core, the hook behind both, for components that draw their own prompt; keyLabel(code) turns KeyT into T for display.
  • useWorld().keyboard: the key events the world reads (none aimed at text fields, none while input is off or the world is paused).
  • Sign outline: a crisp edge in a color, so text reads against any background.
  • Desk: a work desk on slim legs, a drawer pedestal, or side panels, with a modesty panel at the back. Base and drawer count are seeded.
  • Monitor: a desk monitor on a foot, plate, or legs. Switched on, its screen glows in its screen color with a few seeded windows of text on it; on={false} is dark glass.
  • Sofa: one to four seats, with square, rolled, or slim arms, split or bench cushions in soft shades of the fabric, pegs or a plinth, and throw pillows, all seeded and each pinnable.
  • Guide: “Embedding a world in an app” (content security policy, desktop webviews, lazy loading, input, pausing, and figures driven by app state).
  • Person poses type (seated, fingers on the keys), play (hands on controls), drink (a mug held at the chest, sipped now and then), and lie (on its back, centered on position, head toward -Z, so it takes a Bed’s position and rotation at mattress height).
  • Person bubble: a short line in a speech bubble over the head. emote: a small animated sign instead (sleep rising z’s, alert a bouncing !, think pulsing dots, happy a heart, coffee a steaming cup), drawn from shapes and the world font.
  • Office and casual clothes: hoodie and blazer garment layers, outfit shorthands tshirt, sweater, hoodie, and blazer, a chef hat, and the office, casual, hoodie, and chef skins.
  • Player view="overhead" (and avatar: "overhead" in a world): the camera high above at a fixed tilt, following the avatar, with scroll to zoom and WASD walking relative to the screen. A view control in the world’s controls cycles first, third, and overhead at runtime, keeping the avatar where it stands.
  • OfficeChair, Tv (on a stand, a console, or a wall, with a seeded picture), Whiteboard (seeded marker diagrams and optional text), Fridge, CoffeeMachine (espresso or drip, with steam), ArcadeCabinet (a seeded pixel game on its screen), and PoolTable (balls racked or mid-game).
  • Table base="pedestal" and ends="round", for a conference table; Counter hob for a kitchen stove (a glass cooktop and an oven).
  • Sign onBounds: the laid-out text’s size, for fitting a backdrop.

Changed

  • Breaking: WorldEditor moved to its own entry point. Import it from @runek/core/editor instead of @runek/core. leva is now an optional peer dependency, needed only by apps that use the editor.
  • The world no longer reads keys typed into a text field (input, textarea, select, or contenteditable), so typing “wasd” into a form beside the canvas doesn’t walk the avatar. Keys held when the window loses focus are released instead of staying stuck down.
  • @runek/cli declares Node 24 or newer (engines.node was >=20), matching the version the repo develops and tests on.
  • Person has a new look: one continuous sculpted body (no seams at the joints) with a sculpted face, eyelids that blink, eyes that follow you, and hands with fingers, skinned to an 18-bone skeleton so it bends as one. Clothes, hair, and hats are layers fitted to the body, so any garment fits any build, age, or style. The default style is now stylized (an animated-film look); realistic and anime remain. Meshes build in the background a few milliseconds at a time, coarse first; full detail builds only within ~10 units of the camera, and identical figures share one build.
  • Person turns its head toward the player’s avatar, not the camera. In third person the camera trails behind, so figures used to look past you; now they meet your avatar’s eyes, and lookRadius is measured from the avatar. Without a Player (e.g. an orbit view) it still watches the camera. A Person used as the Player’s own body no longer tries to track anything.
  • Player’s capsule no longer tips over: its rotation is locked and only the visible body turns to face the way it walks. At low frame rates the old balancing could topple the avatar. The avatar also starts facing the way the camera looks.
  • A seated Person’s name, bubble, and prompt sit over its head at seat height instead of floating at standing height.
  • Player holds still until there’s ground under it (or half a second passes) before gravity takes over, so a spawn can’t drop through a collider that hasn’t arrived yet.

Fixed

  • The library’s gallery fits every exhibit to its pedestal: each miniature is measured once, then scaled and centered, so large pieces (CurvedWall, Cliff, Clouds, Pool, Roof, Palm, Flag, …) no longer overhang the corridor or stand in front of their pedestal, Ocean shows again, and the empty Interactable exhibit is gone.
  • CurvedWall previews (gallery cards and its docs page) show a smaller arc centered on the frame instead of a wall standing in front of the camera.
  • Setting one Person trait (a skin tone, a build) no longer reshuffles the seeded rolls of the others.
  • A Player spawned over displaced Terrain could fall through it: the trimesh collider was derived one render after the mesh. Terrain now builds it in the same render.

0.13.0 - 2026-09-18

Added

  • Person component: a procedural human figure, so worlds can have people in them. The body is built from smooth lathe surfaces (tapered limbs, one hip-to-shoulder torso profile) on an articulated joint rig that breathes, shifts its weight, blinks, and turns its head to watch you; detail drops to a cheap silhouette past ~18 units. A style template sets the whole look: anime (default; bigger head, large lit eyes, tapered chin, longer legs, chunky fringe) or realistic (canonical 7.5-head proportions). Traits resolve explicit prop first, then the skin cosmetic bundle, then the kind role preset (villager, merchant, guard, sailor, farmer, noble, scholar, traveler), then the seed, so <Person seed={3} /> is a complete villager and every prop narrows it: gender, age, height, build, skinTone, hair, facialHair, outfit, clothing colors, hat, accessories, and pose. skin (a PersonSkin) bundles clothes, colors, headwear, hair, and accessories as one plain-JSON prop, so an editor or in-world UI can swap a figure’s whole look in a single write; nine named presets ship as PERSON_SKINS (festival, harvest, winter, voyage, ceremony, atelier, pirate, bartender, sailor — wardrobe only, never hair), and skin accepts a preset name ("winter") or a bundle. position is the spawn point; one capsule collider makes the figure solid, and physics={false} yields a bare visual you can hand to Player as a third-person body. Movement and dialogue come later.

  • Slab component: a rounded structural plate (pill or disc) for curved-modernist floor decks, cantilevered roofs, terraces, or a lawn disc. Walkable, one convex-hull collider; top surface at the origin like Floor.

  • CurvedWall component: an arc of wall around the origin — solid concrete or a floor-to-ceiling glass curtain wall with mullions and rails. Instanced chords, each carrying a cuboid collider; compose several arcs to leave door gaps.

  • Pool component: a built swimming pool flush with the deck — coping rim, walkable plastered basin with corner exit steps, translucent water just under the lip.

  • Palm component: a coconut palm with a leaning bowed trunk and a crown of drooping instanced fronds; seed drives lean, spread, and the coconut cluster.

  • Docs library: a librarian and a traveler now stand in the reading room, and the player’s third-person body is a Person.

Fixed

  • Contribute modal: “Suggest changes upstream” downloads world.json and snapshot.png with one button instead of two, so the snapshot that step 4 asks for is no longer skipped. The PNG is now named snapshot.png to match the instructions (was world-snapshot.png).

0.12.0 - 2026-07-19

Added

  • Docs library: the reading-room shelves are labeled category sections (Intro / Guides / Reference) instead of an unlabeled even deal, so a new doc only joins its own section rather than reshuffling every shelf. Reference docs (CLI, changelog) now shelve too, and the changelog book reads the root CHANGELOG.md in-world. Books spread across each case’s rows (the CLI reference sits on the Reference shelf’s top row, the changelog on its bottom). The painted RUNEK mark carries the current version beneath it.
  • BookSpec.shelf: pin a Bookshelf book to a specific row (0 = top, clamped); books without a row keep auto-packing bottom-up around the placed ones.
  • Book component: a single procedural book — standing, lying, or open — with a seeded cloth cover and optional href/onSelect interaction (hover pop + title label). The standalone, placeable sibling of Bookshelf’s instanced spines; decorative, no collider. The gallery wing’s guide books now use it (open, on their stands).
  • controls: input bindings are a world setting. A partial action → KeyboardEvent.code[] map merged over the defaults, on WorldData/<World> and resolved at useWorld().controls — remap for AZERTY ({ "forward": ["KeyW", "KeyZ"] }), disable an action with [], or add custom action names your own components read via useKeyboardControls. Orthogonal to avatar; the JSX keyboardMap prop remains the verbatim escape hatch and wins when given.
  • Composites: a new registry kind holding a named arrangement of component nodes as JSON instead of code. A world places one node ({ "type": "House" }); the renderer expands the arrangement in place, and an instance seed deterministically re-rolls children that don’t pin their own. The editor lists composites in its add menu and gains an Unpack action that replaces an instance with an editable Group subtree. runek add <composite> copies the arrangement JSON plus the source of every part it references. (CONTRACT §11.)
  • Level component: a stackable wall ring plus optional slab, with per-side wall configs (openings, color, or an omitted side) and a stairwell hole — the unit buildings are composed from.
  • Floor gains a rectangular opening (a stairwell hole); the slab splits into strips around it so collision matches the visual.
  • Built-in Group node type in worlds-as-data: a plain transform container for children.
  • Gable Roofs cap their open end triangles (ends, default on) with wall-colored prisms, so the attic no longer shows the sky (Roof migration guide for keeping the old open look).
  • scripts/migrate-buildings.mjs: bakes pre-composite House/Room nodes (with non-default props) into equivalent part arrangements.
  • Docs: “The building kit” guide; the library world’s gallery wing ends at a walkable two-level show home built from the house composite.
  • Docs: component pages gain a Migrate section — a side-by-side before/after panel per version bump. Four components carry a 0.11.0 → 0.12.0 guide: Wall, House, Room, and Roof. Entries live in apps/docs/scripts/migrations.mjs and accumulate across versions.

Changed

  • Arrow-key camera look now works in first person too (it was third-person only): Left/Right turn, Up/Down look, in both views, composing with mouse-drag as before.
  • Bookshelf defaults to 3 rows (shelves was 4); pass shelves={1} or shelves={2} for a shorter case, or any other count.
  • Breaking: House and Room are no longer coded components — the registry names now resolve to composites (the default house is a two-level dwelling with a working staircase). Worlds that placed them with default props keep rendering; worlds that relied on non-default props can bake the old look with scripts/migrate-buildings.mjs. HouseProps/RoomProps are gone from @runek/components. Migration guides: House, Room.
  • Breaking: Wall’s single opening prop is replaced by openings: WallOpening[] — one wall can now carry a door and windows together. Migration (opening: X → openings: [X]): Wall migration guide.

0.11.0 - 2026-07-10

Added

  • Hut, Tent, Counter, Stool, Road, and Cliff components.
  • Compass HUD component: a screen-fixed dial that tracks the camera heading, with an optional wind and bearing readout.
  • Bookshelf gains a label prop (plus labelColor and labelSize) that draws a Sign floating above the shelf frame.
  • Path gains a heights elevation profile so trails can follow the terrain they climb.
  • Player gains a children slot for a custom avatar that replaces the default capsule and hides in first-person view.
  • Keyboard camera control: Left/Right arrows turn (yaw) and Up/Down arrows look (pitch), composing with mouse drag.

Changed

  • Breaking: the default keyboardMap in @runek/core no longer binds the arrow keys to movement. WASD moves the character; the arrow keys steer the camera.
  • Docs site: search moved to Pagefind’s Component UI, and the walkable library world gained a gallery wing.

Fixed

  • The brand font bundled in @runek/core is embedded as woff (was woff2, which troika-three-text cannot parse), so text components render the brand face instead of falling back to a fetched default.
  • Docs generator: props tables were missing for any component whose props interface uses an extends clause.

0.10.4 - 2026-06-30

Added

  • Portal component: a glowing travel gate with a physics sensor that fires onEnter (or navigates to to) when the avatar or a vehicle passes through.
  • Signpost component: a post-and-plank signboard that renders a name in the world’s display font.

Fixed

  • WorldNodes no longer passes an explicit null child to leaf nodes, which clobbered a component’s own children default; text authored in world JSON (a Sign’s label, for example) now round-trips correctly.

0.10.2 - 2026-06-29

Added

  • Dock, Flag, Sailboat, and Windmill components.
  • Sailboat gains a physics prop: set it to false to render the boat as a bare visual so a parent controller (a steerable vehicle) can own the physics body.
  • Prerelease publish channels: just publish alpha|beta|rc ships to the matching npm dist-tag and marks the GitHub release a pre-release.

0.10.1 - 2026-06-27

Added

  • Ocean component: a procedural animated-shader sea that follows the camera and blends into the distance fog, defaulting to the palette’s water colors and the world ground baseline.
  • Terrain gains falloff (radial island falloff with a shoreline at the rim) and collider (set false for backdrop terrain the player never walks).

Fixed

  • WorldEditor and WorldRenderer forward ground and fonts to the world context instead of silently dropping them.

0.10.0 - 2026-06-26

Folds in the 0.7.0 through 0.9.0 milestones, which were versioned in the repository but never published to npm.

Added

  • World identity and git-native contribution: WorldData.meta (title, description, authors, license, source), stable node ids with assignNodeIds(), a canonical key order in serializeWorld so unchanged nodes never churn a diff, and a WorldAbout panel.
  • World settings and rules: <World time> pins a reproducible time-of-day and <World timezone> tracks a live clock, driving a day/night cycle in Sky and LightRig; <World avatar> sets the default camera view for Player.
  • World fonts and the Sign text component: a world declares fonts by role and Sign renders from them, with a default face bundled in @runek/core. Text is the single sanctioned exception to the asset-free rule.
  • 18 components rounding out the catalog: Fence, Bridge, Path, Arch, Pillar, Bush, Flowers, Hedge, Well, Fountain, Bench, Bed, Crate, Barrel, Plant, Clouds, Campfire, and Birds.
  • ground world baseline (a Y value, default 0) that Floor and Lake place themselves against, plus the open-water rule in CONTRACT.md.
  • Docs site search (Pagefind).

Changed

  • All workspace packages aligned to a single 0.10.0 version line.
  • The release flow regenerates the served registry manifests before publishing and refuses to ship from a dirty tree, so manifests can no longer drift from the released version.

0.6.0 - 2026-06-18

Added

  • Clickable Bookshelf books: a serializable books array ({ id, title?, color?, href? }) with hover feedback and an onBookSelect callback; without a callback, a click navigates the book’s href.
  • Clock component: a procedural analog clock tracking the local system clock or a given IANA timezone.

Changed

  • Breaking: components import the runtime from the published @runek/core npm package instead of a copy vendored by the CLI, and coreImport is removed from runek.config.json. Core improvements now reach existing projects via a normal dependency update.
  • Breaking: the CLI is published as the scoped @runek/cli (previously the unscoped runek).
  • Breaking: Bookshelf’s fill now defaults to 0, rendering an empty shelf; pass fill explicitly for the old decorative look.
  • The editor exports the world as a world.json file download instead of copying JSON to the clipboard.

0.5.0 - 2026-06-18

Initial public release.

Added

  • @runek/core: the <World> provider (canvas, physics, keyboard controls, lighting), useWorld, deterministic seeded rng helpers, world palette and fog, and the shared component contract.
  • Data-driven worlds: the WorldData/WorldNode JSON model with serializeWorld/parseWorld, a WorldRenderer that mounts world data against a component registry, and an in-browser WorldEditor with selection gizmos and JSON export.
  • The runek CLI (init, add, list) and the source registry: add copies editable component source into your project along with its dependencies.
  • Starter catalog: Player, Terrain, Room, and Bookshelf, plus the structure, furnishing, nature, and lighting component sets.
  • Docs site with flat pages, a live component gallery, and a walkable 3D library world.