@origingame/engine 0.0.0-stage → 0.1.1
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.
- package/bin/og.mjs +25 -0
- package/dist/web/origin-engine.d.ts +1233 -0
- package/dist/web/origin-engine.js +8628 -0
- package/dist/web/origin-engine_bg.wasm +0 -0
- package/dist/web/origin-engine_bg.wasm.d.ts +366 -0
- package/dist/web-publish/origin-engine-lanes.js +8509 -0
- package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish/origin-engine.d.ts +1176 -0
- package/dist/web-publish/origin-engine.js +8313 -0
- package/dist/web-publish/origin-engine_bg.wasm +0 -0
- package/dist/web-publish/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/dist/web-publish-2d/origin-engine-lanes.js +8509 -0
- package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine.d.ts +1176 -0
- package/dist/web-publish-2d/origin-engine.js +8313 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish-2d/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/knowledge/README.md +31 -0
- package/knowledge/knowledge.json +13 -0
- package/knowledge/looks/catalogue.json +1871 -0
- package/knowledge/package.json +33 -0
- package/knowledge/rules/platform.json +329 -0
- package/knowledge/scaffold/README.md +27 -0
- package/knowledge/scaffold/distribution.json +48 -0
- package/knowledge/scaffold/origin-engine.json +19 -0
- package/knowledge/scaffold/template/.origin/setup +14 -0
- package/knowledge/scaffold/template/LICENSES.md +23 -0
- package/knowledge/scaffold/template/README.md +37 -0
- package/knowledge/scaffold/template/assets/ai/guard.ogt +48 -0
- package/knowledge/scaffold/template/assets/audio/click.ogt +9 -0
- package/knowledge/scaffold/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
- package/knowledge/scaffold/template/assets/fonts/OFL.txt +93 -0
- package/knowledge/scaffold/template/assets/looks/origin-game.ogt +12 -0
- package/knowledge/scaffold/template/assets/nav/yard.ogt +27 -0
- package/knowledge/scaffold/template/assets/scenes/main.ogt +34 -0
- package/knowledge/scaffold/template/assets/strings/en.ogt +14 -0
- package/knowledge/scaffold/template/assets/textures/stone_n.png.ogt +7 -0
- package/knowledge/scaffold/template/gitattributes +22 -0
- package/knowledge/scaffold/template/gitignore +8 -0
- package/knowledge/scaffold/template/package.json +18 -0
- package/knowledge/scaffold/template/project.ogt +25 -0
- package/knowledge/scaffold/template/src/main.ts +246 -0
- package/knowledge/scaffold/template/tests/guard.og.ts +67 -0
- package/knowledge/scaffold/template/tests/smoke.og.ts +43 -0
- package/knowledge/scaffold/template/tsconfig.json +17 -0
- package/knowledge/scripts/lib.ts +207 -0
- package/knowledge/scripts/mount.ts +210 -0
- package/knowledge/scripts/pack.ts +25 -0
- package/knowledge/scripts/scaffold.ts +72 -0
- package/knowledge/scripts/validate.ts +749 -0
- package/knowledge/skills/og-ai/SKILL.md +386 -0
- package/knowledge/skills/og-audio/SKILL.md +191 -0
- package/knowledge/skills/og-build/SKILL.md +330 -0
- package/knowledge/skills/og-core/SKILL.md +285 -0
- package/knowledge/skills/og-feel/SKILL.md +385 -0
- package/knowledge/skills/og-game/SKILL.md +1479 -0
- package/knowledge/skills/og-i18n/SKILL.md +220 -0
- package/knowledge/skills/og-platform-sdk/SKILL.md +199 -0
- package/knowledge/skills/og-taste/SKILL.md +537 -0
- package/knowledge/skills/og-ui/SKILL.md +444 -0
- package/knowledge/wiki/ai.md +627 -0
- package/knowledge/wiki/anim.md +1187 -0
- package/knowledge/wiki/atmosphere.md +636 -0
- package/knowledge/wiki/audio.md +732 -0
- package/knowledge/wiki/camera.md +711 -0
- package/knowledge/wiki/commands.md +357 -0
- package/knowledge/wiki/game.md +220 -0
- package/knowledge/wiki/i18n.md +213 -0
- package/knowledge/wiki/index.md +1027 -0
- package/knowledge/wiki/input.md +1714 -0
- package/knowledge/wiki/load.md +1280 -0
- package/knowledge/wiki/math.md +166 -0
- package/knowledge/wiki/nav.md +1154 -0
- package/knowledge/wiki/physics.md +3142 -0
- package/knowledge/wiki/platform.md +857 -0
- package/knowledge/wiki/probe.md +1692 -0
- package/knowledge/wiki/reflect.md +285 -0
- package/knowledge/wiki/render.md +3511 -0
- package/knowledge/wiki/rng.md +393 -0
- package/knowledge/wiki/stream.md +376 -0
- package/knowledge/wiki/system.md +197 -0
- package/knowledge/wiki/systems.md +43 -0
- package/knowledge/wiki/terrain.md +727 -0
- package/knowledge/wiki/tilemap.md +422 -0
- package/knowledge/wiki/time.md +150 -0
- package/knowledge/wiki/transform.md +230 -0
- package/knowledge/wiki/tween.md +580 -0
- package/knowledge/wiki/ui.md +8000 -0
- package/knowledge/wiki/world.md +909 -0
- package/package.json +68 -4
- package/schema/api.schema.json +29675 -0
- package/shell/README.md +22 -0
- package/shell/brand/README.md +23 -0
- package/shell/brand/icon-192.png +0 -0
- package/shell/brand/icon-512.png +0 -0
- package/shell/brand/mark-on-paper.svg +6 -0
- package/shell/brand/origin-mark.svg +5 -0
- package/shell/brand/origin-motion.css +251 -0
- package/shell/brand/tokens.css +20 -0
- package/shell/brand.ts +132 -0
- package/shell/page.ts +715 -0
- package/shell/platform.ts +203 -0
- package/shell/player.ts +238 -0
- package/shell/shell.css +185 -0
- package/shell/sw.ts +177 -0
- package/shell/transport.ts +515 -0
- package/shell/wire.ts +369 -0
- package/src/e6.ts +69 -0
- package/src/env.d.ts +100 -0
- package/src/generated/abi.ts +980 -0
- package/src/index.ts +193 -0
- package/src/node/audio.ts +86 -0
- package/src/node/build.ts +109 -0
- package/src/node/device.ts +981 -0
- package/src/node/doctor.ts +60 -0
- package/src/node/index.ts +102 -0
- package/src/node/platform.ts +24 -0
- package/src/node/playtest-run.ts +249 -0
- package/src/node/png.ts +130 -0
- package/src/node/polish.ts +976 -0
- package/src/node/run.ts +373 -0
- package/src/node/web-build.ts +808 -0
- package/src/p2.ts +35 -0
- package/src/platform-local.ts +211 -0
- package/src/playtest.ts +131 -0
- package/src/runtime.ts +1509 -0
- package/src/systems/cameras.ts +247 -0
- package/src/ui-kit/bind.ts +141 -0
- package/src/ui-kit/huds.ts +303 -0
- package/src/ui-kit/index.ts +36 -0
- package/src/ui-kit/menus.ts +187 -0
- package/src/ui-kit/settings.ts +131 -0
- package/src/ui-kit/store.ts +222 -0
- package/src/ui-kit/story.ts +89 -0
- package/src/ui-kit/util.ts +106 -0
- package/src/web/a11y.ts +429 -0
- package/src/web/audio-worklet.ts +193 -0
- package/src/web/audio.ts +307 -0
- package/src/web/basis.ts +239 -0
- package/src/web/gamepad.ts +123 -0
- package/src/web/host.ts +166 -0
- package/src/web/input.ts +237 -0
- package/src/web/lanes.ts +21 -0
- package/src/web/play.ts +20 -0
- package/src/web/rpc-ring.ts +110 -0
- package/src/web/settle.ts +122 -0
- package/src/web/shell.ts +643 -0
- package/src/web/text-input.ts +248 -0
- package/src/web/worker.ts +1002 -0
- package/types/bindings.json +13654 -0
- package/types/index.d.ts +20090 -0
- package/vendor/basis/LICENSE +201 -0
- package/vendor/basis/NOTICE +15 -0
- package/vendor/basis/README.md +18 -0
- package/vendor/basis/basis_transcoder.js +19 -0
- package/vendor/basis/basis_transcoder.wasm +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// Text entry on the page (og-input's typed-text stream, crates/og-ui/README.md
|
|
2
|
+
// §Input): the canvas cannot take text, so while the engine's UI has a text
|
|
3
|
+
// control focused the shell keeps a hidden <textarea> over it, focused. What
|
|
4
|
+
// the page inserts there becomes `{ kind: "text" }` input: `beforeinput`'s
|
|
5
|
+
// typed characters and pastes are committed text, an input method's
|
|
6
|
+
// `compositionupdate` is its composition so far (the field shows it
|
|
7
|
+
// underlined) and `compositionend` commits it. The textarea itself stays
|
|
8
|
+
// empty. Its keys still reach the engine as key events (the shell's keydown
|
|
9
|
+
// listener lets this element through), except while an input method is
|
|
10
|
+
// composing, when they are the method's. Copy and cut put the engine's
|
|
11
|
+
// selection on the clipboard (the Worker reports it every frame the target
|
|
12
|
+
// changes); the engine deletes the cut text from the Ctrl/Cmd+X it sees.
|
|
13
|
+
//
|
|
14
|
+
// The engine side: the frame answer's `text` (`{ path, rect, multiline,
|
|
15
|
+
// selection, composing }` or null) and `copy` (text a copy took), which the
|
|
16
|
+
// Worker forwards as `{ type: "text", target }` and `{ type: "copy", text }`.
|
|
17
|
+
//
|
|
18
|
+
// For assistive technology the textarea is the field while it has the
|
|
19
|
+
// focus: it is named after the field's node in the accessibility tree
|
|
20
|
+
// (a11y.ts: its name, its placeholder as aria-placeholder, its text as the
|
|
21
|
+
// description), and the mirror's own element for the field steps aside
|
|
22
|
+
// (aria-hidden) while its parent owns the textarea (aria-owns), so a screen
|
|
23
|
+
// reader meets one text box, inside the dialog it belongs to. While no field
|
|
24
|
+
// is focused the textarea is aria-hidden; it never holds the focus hidden
|
|
25
|
+
// (Chrome blocks that and a reader lands on an unnamed box:
|
|
26
|
+
// examples/WEB-FINDINGS.md #7).
|
|
27
|
+
|
|
28
|
+
import type { InjectedInput } from "./input.ts";
|
|
29
|
+
|
|
30
|
+
/** What the textarea is named from: the accessibility mirror (a11y.ts A11yMirror). */
|
|
31
|
+
export interface TextLabels {
|
|
32
|
+
/** The mirrored node at `path`: its name, placeholder (description) and text (value). */
|
|
33
|
+
node(path: string): { readonly name: string; readonly description: string; readonly value: string } | undefined;
|
|
34
|
+
/** The textarea stands in for the field at `path` (null: for none). */
|
|
35
|
+
standIn(path: string | null, element: HTMLElement | null): void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let entries = 0;
|
|
39
|
+
|
|
40
|
+
/** The engine's focused text control, as the Worker reports it. */
|
|
41
|
+
export interface TextTarget {
|
|
42
|
+
/** The element's path in the UI. */
|
|
43
|
+
path: string;
|
|
44
|
+
/** Its rectangle in CSS pixels of the play surface: x, y, width, height. */
|
|
45
|
+
rect: [number, number, number, number];
|
|
46
|
+
multiline: boolean;
|
|
47
|
+
/** The selected text (empty: none). */
|
|
48
|
+
selection: string;
|
|
49
|
+
/** An input method is composing in it. */
|
|
50
|
+
composing: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The hidden textarea that takes the page's text for the engine's focused field. */
|
|
54
|
+
export class TextEntry {
|
|
55
|
+
readonly element: HTMLTextAreaElement;
|
|
56
|
+
#canvas: HTMLCanvasElement;
|
|
57
|
+
#send: (event: InjectedInput) => void;
|
|
58
|
+
#target: TextTarget | null = null;
|
|
59
|
+
#labels: TextLabels | undefined;
|
|
60
|
+
#composing = false;
|
|
61
|
+
/** The editing key pressed in this task, so a virtual keyboard's delete without one still deletes. */
|
|
62
|
+
#keyed = "";
|
|
63
|
+
/** When the page last copied through the copy event (the engine's own copy is then already on the clipboard). */
|
|
64
|
+
#copiedAt = 0;
|
|
65
|
+
#off: Array<() => void> = [];
|
|
66
|
+
|
|
67
|
+
constructor(canvas: HTMLCanvasElement, send: (event: InjectedInput) => void, labels?: TextLabels) {
|
|
68
|
+
this.#canvas = canvas;
|
|
69
|
+
this.#send = send;
|
|
70
|
+
this.#labels = labels;
|
|
71
|
+
const area = canvas.ownerDocument.createElement("textarea");
|
|
72
|
+
area.id = `og-text-entry${entries++ ? `-${entries}` : ""}`;
|
|
73
|
+
area.setAttribute("aria-hidden", "true");
|
|
74
|
+
area.setAttribute("autocomplete", "off");
|
|
75
|
+
area.setAttribute("autocapitalize", "off");
|
|
76
|
+
area.setAttribute("spellcheck", "false");
|
|
77
|
+
area.tabIndex = -1;
|
|
78
|
+
// Invisible but laid out where the field is, so an input method's
|
|
79
|
+
// candidate window opens beside it; 16px keeps mobile browsers from zooming.
|
|
80
|
+
Object.assign(area.style, {
|
|
81
|
+
position: "fixed",
|
|
82
|
+
opacity: "0",
|
|
83
|
+
pointerEvents: "none",
|
|
84
|
+
resize: "none",
|
|
85
|
+
border: "0",
|
|
86
|
+
padding: "0",
|
|
87
|
+
margin: "0",
|
|
88
|
+
overflow: "hidden",
|
|
89
|
+
whiteSpace: "pre",
|
|
90
|
+
fontSize: "16px",
|
|
91
|
+
caretColor: "transparent",
|
|
92
|
+
background: "transparent",
|
|
93
|
+
color: "transparent",
|
|
94
|
+
left: "0px",
|
|
95
|
+
top: "0px",
|
|
96
|
+
width: "1px",
|
|
97
|
+
height: "1px",
|
|
98
|
+
} satisfies Partial<CSSStyleDeclaration>);
|
|
99
|
+
canvas.ownerDocument.body.appendChild(area);
|
|
100
|
+
this.element = area;
|
|
101
|
+
const on = <K extends keyof HTMLElementEventMap>(type: K, fn: (e: HTMLElementEventMap[K]) => void) => {
|
|
102
|
+
area.addEventListener(type, fn as EventListener);
|
|
103
|
+
this.#off.push(() => area.removeEventListener(type, fn as EventListener));
|
|
104
|
+
};
|
|
105
|
+
on("keydown", (e) => {
|
|
106
|
+
this.#keyed = e.code;
|
|
107
|
+
queueMicrotask(() => (this.#keyed = ""));
|
|
108
|
+
});
|
|
109
|
+
on("beforeinput", (e) => this.#beforeInput(e));
|
|
110
|
+
on("compositionstart", () => {
|
|
111
|
+
this.#composing = true;
|
|
112
|
+
});
|
|
113
|
+
on("compositionupdate", (e) => {
|
|
114
|
+
this.#send({ kind: "text", text: e.data ?? "", composing: true });
|
|
115
|
+
});
|
|
116
|
+
on("compositionend", (e) => {
|
|
117
|
+
this.#composing = false;
|
|
118
|
+
if (e.data) this.#send({ kind: "text", text: e.data });
|
|
119
|
+
else this.#send({ kind: "text", text: "", composing: true });
|
|
120
|
+
area.value = "";
|
|
121
|
+
});
|
|
122
|
+
on("paste", (e) => {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
const text = e.clipboardData?.getData("text/plain") ?? "";
|
|
125
|
+
if (text) this.#send({ kind: "text", text: this.#target?.multiline ? text : text.replace(/\r?\n/g, " ") });
|
|
126
|
+
});
|
|
127
|
+
const copy = (e: ClipboardEvent) => {
|
|
128
|
+
const text = this.#target?.selection ?? "";
|
|
129
|
+
if (!text) return;
|
|
130
|
+
e.preventDefault();
|
|
131
|
+
e.clipboardData?.setData("text/plain", text);
|
|
132
|
+
this.#copiedAt = performance.now();
|
|
133
|
+
};
|
|
134
|
+
on("copy", copy);
|
|
135
|
+
on("cut", copy);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Whether a key event's target is this textarea (the shell forwards its keys). */
|
|
139
|
+
owns(target: EventTarget | null): boolean {
|
|
140
|
+
return target === this.element;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** An input method is composing: its keys are not the game's. */
|
|
144
|
+
get composing(): boolean {
|
|
145
|
+
return this.#composing;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The engine's focused text control changed: follow it, focus or let go. */
|
|
149
|
+
update(target: TextTarget | null): void {
|
|
150
|
+
const had = this.#target !== null;
|
|
151
|
+
this.#target = target;
|
|
152
|
+
const area = this.element;
|
|
153
|
+
if (!target) {
|
|
154
|
+
if (had && area.ownerDocument.activeElement === area) area.blur();
|
|
155
|
+
this.relabel();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Named and visible to assistive technology before it takes the focus.
|
|
159
|
+
this.relabel();
|
|
160
|
+
const box = this.#canvas.getBoundingClientRect();
|
|
161
|
+
const sx = box.width / (this.#canvas.clientWidth || box.width || 1);
|
|
162
|
+
const sy = box.height / (this.#canvas.clientHeight || box.height || 1);
|
|
163
|
+
const [x, y, w, h] = target.rect;
|
|
164
|
+
Object.assign(area.style, {
|
|
165
|
+
left: `${box.left + x * sx}px`,
|
|
166
|
+
top: `${box.top + y * sy}px`,
|
|
167
|
+
width: `${Math.max(1, w * sx)}px`,
|
|
168
|
+
height: `${Math.max(1, h * sy)}px`,
|
|
169
|
+
});
|
|
170
|
+
if (!had || area.ownerDocument.activeElement !== area) {
|
|
171
|
+
area.focus({ preventScroll: true });
|
|
172
|
+
// An empty commit tells the engine the page has a text stream now, so
|
|
173
|
+
// a key's character and its typed text never both type.
|
|
174
|
+
this.#send({ kind: "text", text: "" });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Name the textarea after the focused field's node (call when the
|
|
180
|
+
* mirrored tree changed): or hide it from assistive technology while no
|
|
181
|
+
* field is focused.
|
|
182
|
+
*/
|
|
183
|
+
relabel(): void {
|
|
184
|
+
const area = this.element;
|
|
185
|
+
const target = this.#target;
|
|
186
|
+
const set = (name: string, value: string | null) => {
|
|
187
|
+
if (value === null) area.removeAttribute(name);
|
|
188
|
+
else if (area.getAttribute(name) !== value) area.setAttribute(name, value);
|
|
189
|
+
};
|
|
190
|
+
if (!target) {
|
|
191
|
+
for (const a of ["aria-label", "aria-placeholder", "aria-description", "aria-multiline"]) set(a, null);
|
|
192
|
+
set("aria-hidden", "true");
|
|
193
|
+
this.#labels?.standIn(null, null);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const node = this.#labels?.node(target.path);
|
|
197
|
+
set("aria-hidden", null);
|
|
198
|
+
// Unmirrored (no tree yet): the field's own id stands for its name.
|
|
199
|
+
set("aria-label", node?.name || target.path.split("/").pop() || null);
|
|
200
|
+
set("aria-placeholder", node?.description || null);
|
|
201
|
+
set("aria-description", node?.value || null);
|
|
202
|
+
set("aria-multiline", target.multiline ? "true" : "false");
|
|
203
|
+
this.#labels?.standIn(target.path, area);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The engine copied without a copy event (a game's own copy): best effort. */
|
|
207
|
+
copied(text: string): void {
|
|
208
|
+
if (performance.now() - this.#copiedAt < 500) return;
|
|
209
|
+
void globalThis.navigator?.clipboard?.writeText(text).catch(() => {});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
#beforeInput(e: InputEvent): void {
|
|
213
|
+
switch (e.inputType) {
|
|
214
|
+
// The composition: compositionupdate and compositionend carry it.
|
|
215
|
+
case "insertCompositionText":
|
|
216
|
+
case "insertFromComposition":
|
|
217
|
+
case "deleteByComposition":
|
|
218
|
+
case "deleteCompositionText":
|
|
219
|
+
return;
|
|
220
|
+
case "insertText":
|
|
221
|
+
case "insertReplacementText":
|
|
222
|
+
case "insertFromDrop":
|
|
223
|
+
case "insertFromPaste": {
|
|
224
|
+
e.preventDefault();
|
|
225
|
+
const text = e.data ?? e.dataTransfer?.getData("text/plain") ?? "";
|
|
226
|
+
if (text) this.#send({ kind: "text", text });
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
case "deleteContentBackward":
|
|
230
|
+
case "deleteContentForward": {
|
|
231
|
+
e.preventDefault();
|
|
232
|
+
// A virtual keyboard deletes without a key event the engine saw.
|
|
233
|
+
const key = e.inputType === "deleteContentBackward" ? "Backspace" : "Delete";
|
|
234
|
+
if (this.#keyed !== key) this.#send({ kind: "key", key, action: "press" });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
default:
|
|
238
|
+
// Line breaks arrive as Enter, everything else is the engine's.
|
|
239
|
+
e.preventDefault();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
dispose(): void {
|
|
244
|
+
for (const off of this.#off.splice(0)) off();
|
|
245
|
+
this.#labels?.standIn(null, null);
|
|
246
|
+
this.element.remove();
|
|
247
|
+
}
|
|
248
|
+
}
|