@runek/core 0.5.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +245 -28
  2. package/dist/index.js +706 -83
  3. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -1,7 +1,32 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ComponentType } from 'react';
2
+ import { ComponentType, ReactNode } from 'react';
3
3
  import { KeyboardControlsEntry } from '@react-three/drei';
4
4
 
5
+ /**
6
+ * The single default font bundled with Runek, for the in-world text carve-out
7
+ * (CONTRACT §4). It travels as a base64 `data:` URI so it ships inside the JS
8
+ * bundle: no separate font file to resolve, no CDN fetch, nothing for the host
9
+ * app to serve. A world can override it per role via `WorldData.fonts`; a text
10
+ * component falls back to this whenever a role is undeclared, so it always
11
+ * renders.
12
+ *
13
+ * Face: Pixelspace, the Runek brand pixel display font (woff2, ~8KB).
14
+ */
15
+ declare const DEFAULT_FONT = "data:font/woff2;base64,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";
16
+ /**
17
+ * Named font roles a world declares and text components draw from. Values are
18
+ * font URLs (or `data:` URIs). Mirrors {@link WorldPalette}: a world overrides
19
+ * the roles it cares about; the rest fall back to {@link DEFAULT_FONT}.
20
+ */
21
+ interface WorldFonts {
22
+ /** Titles, signage, headings. */
23
+ display: string;
24
+ /** Labels, paragraphs, body copy. */
25
+ body: string;
26
+ }
27
+ /** Every role resolved to the bundled default. A world's `fonts` layers on top. */
28
+ declare const DEFAULT_FONTS: WorldFonts;
29
+
5
30
  /**
6
31
  * Named color slots shared by the whole world. Components default their colors
7
32
  * to these slots, so swapping the palette re-themes every component at once —
@@ -38,7 +63,63 @@ interface WorldPalette {
38
63
  }
39
64
  declare const DEFAULT_PALETTE: WorldPalette;
40
65
 
66
+ /**
67
+ * A world's resolved time-of-day, the value day/night-aware components (`Sky`,
68
+ * `LightRig`, `Clock`) read from `useWorld()`. A world pins a fixed `time`
69
+ * ("HH:MM", reproducible) or tracks a live `timezone`; either way it lands here.
70
+ */
71
+ interface WorldTime {
72
+ /** Fractional hours in [0, 24). For a live world this is a snapshot at resolve
73
+ * time; call `currentHours()` for the up-to-date value each frame. */
74
+ hours: number;
75
+ /** True when the time tracks a live clock (`timezone` or system), false when pinned. */
76
+ live: boolean;
77
+ /** IANA zone in effect, if any. Drives live reads and `Clock`'s default timezone. */
78
+ timezone?: string;
79
+ }
80
+ /** A neutral, pinned midday — the default when a world declares no time. Renders
81
+ * as bright day, so a world that opts out of day/night looks unchanged. */
82
+ declare const DEFAULT_WORLD_TIME: WorldTime;
83
+ /** Parse "HH:MM" (24-hour) into fractional hours in [0, 24), or null if malformed. */
84
+ declare function parseClockTime(value: string): number | null;
85
+ /** Read the current fractional hours-of-day in `timezone` (or the local system
86
+ * clock when omitted), falling back to UTC if the zone is unusable. */
87
+ declare function clockHours(timezone?: string): number;
88
+ /**
89
+ * Resolve a world's `time`/`timezone` fields into a `WorldTime`. Precedence: a
90
+ * valid pinned `time` wins (deterministic); else a live clock in `timezone`; else
91
+ * the local system clock; else UTC. A malformed `time` is ignored, not fatal.
92
+ */
93
+ declare function resolveWorldTime(opts: {
94
+ time?: string;
95
+ timezone?: string;
96
+ }): WorldTime;
97
+ /** The fractional hours for a `WorldTime` right now: constant when pinned, the
98
+ * live clock when not. Components call this each frame for a live world. */
99
+ declare function currentHours(time: WorldTime): number;
100
+ /** Where the sun sits for a given time-of-day, shared by `Sky` and `LightRig` so
101
+ * the sky's bright spot and the cast shadows always agree. */
102
+ interface SunState {
103
+ /** Direction to the sun, usable directly as a `sunPosition`. Dips below the
104
+ * ground plane (`position[1] < 0`) at night. */
105
+ position: Vec3;
106
+ /** 0 at or below the horizon, rising to 1 at the zenith. */
107
+ elevation: number;
108
+ /** Whether the sun is above the horizon. */
109
+ day: boolean;
110
+ }
111
+ /**
112
+ * A simple time-of-day → sun position curve. The sun rises at 06:00, peaks at
113
+ * noon, sets at 18:00, and sits below the horizon overnight. This is a
114
+ * latitude/date-free model (azimuth is a plain east→west sweep); accurate solar
115
+ * position is deliberately out of scope (see the v0.8.0 spec).
116
+ */
117
+ declare function sunState(hours: number, radius?: number): SunState;
118
+
41
119
  type Vec3 = [number, number, number];
120
+ /** How the player camera frames the avatar. A world default `Player` reads when
121
+ * its own `view` is unset; an explicit component `view` still wins. */
122
+ type AvatarView = 'first' | 'third';
42
123
  /** The contract every Runek component implements. */
43
124
  interface WorldComponentProps {
44
125
  position?: Vec3;
@@ -57,12 +138,130 @@ interface WorldContextValue {
57
138
  /** Meters per unit. Components scale their geometry by this. */
58
139
  unit: number;
59
140
  gravity: Vec3;
141
+ /** Baseline ground level (Y, in world units). Floor-sitting and water components
142
+ * default their placement to it; an explicit `position` wins. Default 0. */
143
+ ground: number;
60
144
  /** Resolved color slots components default their materials to. */
61
145
  palette: WorldPalette;
146
+ /** Resolved font roles (display, body) text components draw from. Every role
147
+ * is filled (the world's overrides over the bundled default), so a text
148
+ * component can read `fonts[role]` unconditionally. */
149
+ fonts: WorldFonts;
150
+ /** Resolved time-of-day. Day/night-aware components (`Sky`, `LightRig`,
151
+ * `Clock`) read this; defaults to a pinned midday. */
152
+ time: WorldTime;
153
+ /** World default camera view for the player, if the world declares one. */
154
+ avatar?: AvatarView;
62
155
  }
63
156
 
64
157
  declare const WorldContext: react.Context<WorldContextValue>;
65
158
 
159
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
160
+ [key: string]: JsonValue;
161
+ };
162
+ /** One contributor to a world. */
163
+ interface WorldAuthor {
164
+ name: string;
165
+ url?: string;
166
+ }
167
+ /** Where a world lives, so it can be linked, forked, and contributed back to. */
168
+ interface WorldSource {
169
+ /** Full web URL of the canonical repo, e.g. `https://github.com/owner/repo`.
170
+ * Stored whole (not `owner/repo`) so the host is detectable for the fork flow. */
171
+ url: string;
172
+ /** Path to the world file within the repo, e.g. `public/helicon.world.json`. */
173
+ path?: string;
174
+ branch?: string;
175
+ }
176
+ /** A world's descriptive identity: attribution, license, and where it lives.
177
+ * Everything is optional; a world with no `meta` still renders. Travels in the
178
+ * world file (not tooling config) so it survives a fork. */
179
+ interface WorldMeta {
180
+ title?: string;
181
+ description?: string;
182
+ /** Worlds accrue contributors, hence an array. */
183
+ authors?: WorldAuthor[];
184
+ /** Free SPDX-ish string. Components are MIT code; a world is content, so authors
185
+ * often prefer a Creative Commons license for the world itself. */
186
+ license?: string;
187
+ source?: WorldSource;
188
+ }
189
+ /** One placed component: a registry key, its props, and optional nested children. */
190
+ interface WorldNode {
191
+ /** Registry key — the component's name, e.g. "Bookshelf". */
192
+ type: string;
193
+ /** Stable identity, durable across edits and reorders. Optional in hand-authored
194
+ * worlds; the editor fills it in (see `assignNodeIds`). Drives React keys,
195
+ * selection, and minimal PR diffs. */
196
+ id?: string;
197
+ props?: Record<string, JsonValue>;
198
+ children?: WorldNode[];
199
+ }
200
+ /** A whole world as plain data — diffable, forkable, version-controlled like any file. */
201
+ interface WorldData {
202
+ version: 1;
203
+ /** The world's identity (title, authors, license, source). Optional. */
204
+ meta?: WorldMeta;
205
+ unit?: number;
206
+ gravity?: Vec3;
207
+ /** Baseline ground level (Y, in units). Floor-sitting and water components default
208
+ * their placement to it; an explicit `position` wins. Default 0. */
209
+ ground?: number;
210
+ /** Pinned time-of-day ("HH:MM", 24h) for a reproducible world. Drives day/night. */
211
+ time?: string;
212
+ /** IANA timezone for a live, clock-driven day/night (used when `time` is unset). */
213
+ timezone?: string;
214
+ /** World default camera view; `Player` reads it when its own `view` is unset. */
215
+ avatar?: AvatarView;
216
+ /** Color-slot overrides applied to every component in the world. */
217
+ palette?: Partial<WorldPalette>;
218
+ /** Fonts the world ships, by role (`display`, `body`). Text components draw from
219
+ * these; unset roles fall back to the bundled default. Values are font URLs. */
220
+ fonts?: Partial<WorldFonts>;
221
+ fog?: WorldFog;
222
+ nodes: WorldNode[];
223
+ }
224
+ type ComponentRegistry = Record<string, ComponentType<any>>;
225
+ /**
226
+ * Serialize a world to pretty JSON text with a canonical, stable key order
227
+ * (`version, meta, unit, gravity, ground, time, timezone, avatar, palette, fonts,
228
+ * fog, nodes`; each node `type, id, props, children`). Stable output means an
229
+ * unchanged node never churns the diff, so PR reviews show only the real change.
230
+ */
231
+ declare function serializeWorld(data: WorldData): string;
232
+ /** Parse and lightly validate world JSON text. Throws on an unsupported shape. */
233
+ declare function parseWorld(json: string): WorldData;
234
+ /**
235
+ * Return a copy of the world where every node has a stable `id`. Existing ids are
236
+ * preserved; missing ones are generated (unique within the world). The editor calls
237
+ * this on load, so edits and serialized output carry durable node identity even when
238
+ * the source world was hand-authored without ids.
239
+ */
240
+ declare function assignNodeIds(data: WorldData): WorldData;
241
+
242
+ /** A GitHub-hosted world source, parsed into the pieces the contribute URLs need. */
243
+ interface GitHubSource {
244
+ owner: string;
245
+ repo: string;
246
+ branch: string;
247
+ path?: string;
248
+ }
249
+ /**
250
+ * Parse a `WorldSource` into its GitHub owner/repo (plus branch + path), or `null`
251
+ * if the source is not a github.com URL. `meta.source` stores the full web URL
252
+ * precisely so the host is detectable here; non-GitHub hosts fall back to "open the
253
+ * repo" in the UI.
254
+ */
255
+ declare function parseGitHubSource(source: WorldSource | undefined): GitHubSource | null;
256
+ /** URL that forks the world's repo on GitHub. */
257
+ declare function forkUrl(gh: GitHubSource): string;
258
+ /**
259
+ * GitHub web edit-file URL. Opening it as a non-collaborator makes GitHub auto-fork
260
+ * the repo and open its web editor on the fork — the zero-auth contribution path.
261
+ * Returns `null` when the world's file path is unknown (nothing to deep-link).
262
+ */
263
+ declare function editFileUrl(gh: GitHubSource): string | null;
264
+
66
265
  /** Default movement bindings, matching the action names ecctrl reads. */
67
266
  declare const keyboardMap: KeyboardControlsEntry[];
68
267
 
@@ -80,47 +279,65 @@ declare const useWorld: () => WorldContextValue;
80
279
  interface WorldProps {
81
280
  unit?: number;
82
281
  gravity?: Vec3;
282
+ /** Baseline ground level (Y). Floor-sitting and water components default to it;
283
+ * an explicit `position` still wins. Default 0. */
284
+ ground?: number;
83
285
  keyboardMap?: KeyboardControlsEntry[];
84
286
  /** Render the default light rig. Set false to supply your own (e.g. <LightRig>). */
85
287
  lights?: boolean;
86
288
  /** Override color slots; unset slots keep their defaults. Components read these via `useWorld()`. */
87
289
  palette?: Partial<WorldPalette>;
290
+ /** Fonts the world ships, by role (`display`, `body`). Text components draw from
291
+ * these; unset roles fall back to the bundled default. Values are font URLs. */
292
+ fonts?: Partial<WorldFonts>;
88
293
  /** Linear distance fog. Pair the color with your sky's horizon. */
89
294
  fog?: WorldFog;
295
+ /** Pin a fixed time-of-day ("HH:MM", 24h) so the world is reproducible. Drives
296
+ * day/night-aware components. A `timezone` instead makes it track a live clock. */
297
+ time?: string;
298
+ /** IANA timezone (e.g. "Asia/Kolkata") for a live, clock-driven day/night. Ignored
299
+ * when `time` is set (a pin wins); used as the live source otherwise. */
300
+ timezone?: string;
301
+ /** World default camera view. `Player` reads it when its own `view` is unset. */
302
+ avatar?: AvatarView;
90
303
  /** Fired when a pointer click misses every object (used to deselect in the editor). */
91
304
  onPointerMissed?: () => void;
305
+ /** Keep the WebGL backbuffer so the canvas can be snapshotted via `toDataURL`
306
+ * (the editor enables this for the "suggest changes" PNG). Off by default — it can
307
+ * cost a little performance. */
308
+ preserveDrawingBuffer?: boolean;
92
309
  debug?: boolean;
93
310
  children?: ReactNode;
94
311
  }
95
- declare function World({ unit, gravity, keyboardMap, lights, palette, fog, onPointerMissed, debug, children, }: WorldProps): react.JSX.Element;
312
+ declare function World({ unit, gravity, ground, keyboardMap, lights, palette, fonts, fog, time, timezone, avatar, onPointerMissed, preserveDrawingBuffer, debug, children, }: WorldProps): react.JSX.Element;
96
313
 
97
- type JsonValue = string | number | boolean | null | JsonValue[] | {
98
- [key: string]: JsonValue;
99
- };
100
- /** One placed component: a registry key, its props, and optional nested children. */
101
- interface WorldNode {
102
- /** Registry key — the component's name, e.g. "Bookshelf". */
103
- type: string;
104
- props?: Record<string, JsonValue>;
105
- children?: WorldNode[];
314
+ interface WorldAboutProps {
315
+ /** The world's identity. When absent, the panel shows a minimal "untitled world". */
316
+ meta?: WorldMeta;
106
317
  }
107
- /** A whole world as plain data — diffable, forkable, version-controlled like any file. */
108
- interface WorldData {
109
- version: 1;
110
- unit?: number;
111
- gravity?: Vec3;
112
- /** Color-slot overrides applied to every component in the world. */
113
- palette?: Partial<WorldPalette>;
114
- fog?: WorldFog;
115
- nodes: WorldNode[];
318
+ /**
319
+ * A small `ⓘ` affordance that opens a read-only "About this world" panel over the
320
+ * canvas. A pure view of `meta` (title, description, authors, license, source repo)
321
+ * plus a "built with Runek" tag. Surfaced in both walk (`WorldRenderer`) and edit
322
+ * (`WorldEditor`) modes — a visitor wants to know whose world it is and how it is
323
+ * licensed, not just an editor.
324
+ */
325
+ declare function WorldAbout({ meta }: WorldAboutProps): react.JSX.Element;
326
+
327
+ interface WorldContributeProps {
328
+ /** The edited world. Its `meta.source` drives the GitHub URLs; its content is the download. */
329
+ data: WorldData;
330
+ onClose: () => void;
116
331
  }
117
- type ComponentRegistry = Record<string, ComponentType<any>>;
118
- /** Serialize a world to pretty JSON text. */
119
- declare function serializeWorld(data: WorldData): string;
120
- /** Parse and lightly validate world JSON text. Throws on an unsupported shape. */
121
- declare function parseWorld(json: string): WorldData;
332
+ /**
333
+ * "Suggest changes upstream": a modal that couriers the edited world to GitHub's own
334
+ * UI with no account, token, or backend. Runek downloads the edited JSON + a snapshot,
335
+ * then opens GitHub's edit-file URL (which auto-forks for non-collaborators); the modal
336
+ * copy walks paste → commit → PR. Non-GitHub hosts fall back to opening the repo.
337
+ */
338
+ declare function WorldContribute({ data, onClose }: WorldContributeProps): react.JSX.Element;
122
339
 
123
- interface WorldEditorProps extends Omit<WorldProps, 'children' | 'unit' | 'gravity' | 'palette' | 'fog'> {
340
+ interface WorldEditorProps extends Omit<WorldProps, 'children' | 'unit' | 'gravity' | 'palette' | 'fog' | 'time' | 'timezone' | 'avatar'> {
124
341
  data: WorldData;
125
342
  registry: ComponentRegistry;
126
343
  onChange: (next: WorldData) => void;
@@ -138,11 +355,11 @@ interface WorldNodesProps {
138
355
  /** Render a list of world nodes by looking each `type` up in the registry. Recurses into children. */
139
356
  declare function WorldNodes({ nodes, registry }: WorldNodesProps): react.JSX.Element;
140
357
 
141
- interface WorldRendererProps extends Omit<WorldProps, 'children' | 'unit' | 'gravity' | 'palette' | 'fog'> {
358
+ interface WorldRendererProps extends Omit<WorldProps, 'children' | 'unit' | 'gravity' | 'palette' | 'fog' | 'time' | 'timezone' | 'avatar'> {
142
359
  data: WorldData;
143
360
  registry: ComponentRegistry;
144
361
  }
145
362
  /** Render a `WorldData` object inside a `<World>`, resolving each node via the registry. */
146
363
  declare function WorldRenderer({ data, registry, ...worldProps }: WorldRendererProps): react.JSX.Element;
147
364
 
148
- export { type ComponentRegistry, DEFAULT_PALETTE, type JsonValue, type Rng, type Vec3, World, type WorldComponentProps, WorldContext, type WorldContextValue, type WorldData, WorldEditor, type WorldEditorProps, type WorldFog, type WorldNode, WorldNodes, type WorldNodesProps, type WorldPalette, type WorldProps, WorldRenderer, type WorldRendererProps, int, keyboardMap, parseWorld, pick, range, rng, serializeWorld, sub, useWorld };
365
+ export { type AvatarView, type ComponentRegistry, DEFAULT_FONT, DEFAULT_FONTS, DEFAULT_PALETTE, DEFAULT_WORLD_TIME, type GitHubSource, type JsonValue, type Rng, type SunState, type Vec3, World, WorldAbout, type WorldAboutProps, type WorldAuthor, type WorldComponentProps, WorldContext, type WorldContextValue, WorldContribute, type WorldContributeProps, type WorldData, WorldEditor, type WorldEditorProps, type WorldFog, type WorldFonts, type WorldMeta, type WorldNode, WorldNodes, type WorldNodesProps, type WorldPalette, type WorldProps, WorldRenderer, type WorldRendererProps, type WorldSource, type WorldTime, assignNodeIds, clockHours, currentHours, editFileUrl, forkUrl, int, keyboardMap, parseClockTime, parseGitHubSource, parseWorld, pick, range, resolveWorldTime, rng, serializeWorld, sub, sunState, useWorld };