@origingame/engine 0.0.0-stage → 0.1.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.
- 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/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 +203 -0
- package/knowledge/scripts/mount.ts +210 -0
- package/knowledge/scripts/pack.ts +24 -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,220 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: og-i18n
|
|
3
|
+
description: Localizing a game on Origin Engine with og.i18n — string tables per locale as .ogt, messages with arguments, plurals and select (ICU), the host's locale and the player's choice, font chains for Chinese, Japanese, Korean and Arabic, CJK line breaking (kinsoku), shaping and right-to-left layout in og.ui, the cook-time check for missing keys, and how to prove each locale with frames. Use when a game shows text to players in more than one language, adds a language, or shows boxes or broken Arabic.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Every string a player reads comes from a table, never from a literal in a
|
|
7
|
+
system. `og.i18n` picks the table for the locale the host asks for (the
|
|
8
|
+
browser's language, `og run --locale`) or the player chooses, formats the
|
|
9
|
+
message, and hands og.ui the fonts and the direction for it. The API is in
|
|
10
|
+
`reference/i18n.md`; the platform's `localization` skill covers store
|
|
11
|
+
listings and the catalog's languages.
|
|
12
|
+
|
|
13
|
+
## What works
|
|
14
|
+
|
|
15
|
+
Two games written from this package alone (`examples/lantern-vale`,
|
|
16
|
+
`examples/hollowbell`) ship English and Simplified Chinese from string
|
|
17
|
+
tables: `og polish` found no locale gaps in either, the results template
|
|
18
|
+
showed its localised Best and New best, a name was typed partly through an
|
|
19
|
+
input method, and a skin copied without its CJK `fallback` was refused by
|
|
20
|
+
the cook with the line named. The Kestrel Stair (`examples/kestrel-stair`)
|
|
21
|
+
ships English and Japanese: its Noto Sans JP subset cut by the ui-kit's
|
|
22
|
+
`tools/subset-font.py` needed no other step, and its locales playtest
|
|
23
|
+
finds no English word of a template left in Japanese on eight screens.
|
|
24
|
+
|
|
25
|
+
## 1. One table per locale
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
type strings
|
|
29
|
+
version 1
|
|
30
|
+
---
|
|
31
|
+
locale "ja"
|
|
32
|
+
fonts "fonts/NotoSansJP-game.otf" # this locale's font chain, first first
|
|
33
|
+
string "menu.play" "プレイ"
|
|
34
|
+
string "hud.coins" "{n, plural, other {コイン#枚}}"
|
|
35
|
+
string "results.time" "タイム {time}"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- One file per locale under `assets/strings/` (`en.ogt`, `zh-CN.ogt`,
|
|
39
|
+
`ja.ogt`, `ko.ogt`, `ar.ogt`); keys are dotted by screen
|
|
40
|
+
(`menu.play`, `pause.resume`, `shop.buy`). Mark the base table `base
|
|
41
|
+
true` (else `en` is the base).
|
|
42
|
+
- Load every table in `start`: `og.i18n.load(world, "strings/ja.ogt")`;
|
|
43
|
+
the active locale is negotiated again after each load (`ja-JP` takes
|
|
44
|
+
`ja`, `zh-TW` takes `zh-Hant` or `zh-TW`, else the base).
|
|
45
|
+
- A key a table lacks falls back to its `fallback` table, then the base, and
|
|
46
|
+
`og cook` warns about it. Treat a cook warning as a bug: `og polish`
|
|
47
|
+
scores missing keys (og-build §5).
|
|
48
|
+
|
|
49
|
+
## 2. Messages
|
|
50
|
+
|
|
51
|
+
`og.i18n.t(world, "hud.coins", { n: coins })`. The forms:
|
|
52
|
+
|
|
53
|
+
| Form | Example |
|
|
54
|
+
|---|---|
|
|
55
|
+
| an argument | `"Hello, {name}"` |
|
|
56
|
+
| a number, grouped for the locale | `"{score, number} pts"` |
|
|
57
|
+
| plural (CLDR categories `zero one two few many other`, `=N` exact, `#` the number) | `"{n, plural, =0 {No coins} one {# coin} other {# coins}}"` |
|
|
58
|
+
| select | `"{g, select, f {She won} m {He won} other {They won}}"` |
|
|
59
|
+
| a literal brace or quote | `'{'`, `''` |
|
|
60
|
+
|
|
61
|
+
- Big numbers in a locale's own units (K, M, B in English; 万, 亿 in
|
|
62
|
+
Chinese) are table data too: a key for how many digits a unit spans and
|
|
63
|
+
a key per unit, read by one formatter (`templates/archetypes/idle`'s
|
|
64
|
+
`big` in `src/numbers.ts`).
|
|
65
|
+
- Whole sentences per key, never concatenated pieces: word order differs
|
|
66
|
+
(Japanese puts the verb last, Arabic reads right to left).
|
|
67
|
+
- Plurals in every table, even where the language has one form
|
|
68
|
+
(`{n, plural, other {コイン#枚}}`): the argument names stay the same in
|
|
69
|
+
every locale, and `og cook` checks they do.
|
|
70
|
+
- Numbers, times and prices go through arguments, not string building.
|
|
71
|
+
- og.ui's templates have words of their own (Retry, Share, "Best 1,200",
|
|
72
|
+
"New best", Cancel, Settings, Buy, Skip, "1 of 3", a racing HUD's
|
|
73
|
+
"lap", "Best 1:36.233", Pause and Boost): each is a field of
|
|
74
|
+
the template's desc with the English default, so a localised game passes
|
|
75
|
+
its table's: `ResultsDesc.bestLabel`, `newBestLabel`, `shareLabel`,
|
|
76
|
+
`actions`; `ConfirmDesc.confirmLabel`, `cancelLabel`; `SettingsDesc.title`,
|
|
77
|
+
`audioLabel`, `graphicsLabel`, `controlsLabel`, `languageLabel`,
|
|
78
|
+
`masterLabel`, `musicLabel`, `effectsLabel`, `muteLabel`, `qualityLabel`,
|
|
79
|
+
`lowLabel`, `mediumLabel`, `highLabel`, `fpsLabel`, `motionLabel`,
|
|
80
|
+
`contrastLabel`, `sensitivityLabel`, `invertLabel`, `vibrationLabel`,
|
|
81
|
+
`doneLabel`; `ShopDesc.title`, `currency`, `priceLabel`, `buyLabel`,
|
|
82
|
+
`shortLabel` (Buy while the balance is short: "Not enough"),
|
|
83
|
+
`ownedLabel`, `emptyLabel`; `InventoryDesc.title`, `useLabel`,
|
|
84
|
+
`hintLabel` (the detail panel with nothing selected), `countLabel`;
|
|
85
|
+
`LeaderboardDesc.title`, `empty`, `retryLabel` (under `error`, the
|
|
86
|
+
game's own message); `TutorialDesc.skipLabel`, `nextLabel`, `doneLabel`
|
|
87
|
+
(Next on the last step), `stepLabel`; `CutsceneDesc.skipLabel`;
|
|
88
|
+
`LevelSelectDesc.title`; `ResultsDesc.scoreLabel` (over the score:
|
|
89
|
+
"Total time"); `RacingHudDesc.lapLabel` (under the lap), `bestLabel`
|
|
90
|
+
("Best {best}", the best lap as m:ss.mmm), `pauseLabel` (the pause
|
|
91
|
+
button's name), `boostLabel`, `brakeLabel`, `leftLabel`, `rightLabel`
|
|
92
|
+
(the touch pedals and arrows) and `unit`, and its `splits`' labels
|
|
93
|
+
(`S1`) are the game's. `bestLabel: og.i18n.t(world, "results.best")`
|
|
94
|
+
with `"Best {best}"` / `"最佳 {best}"` in the tables (the template fills
|
|
95
|
+
`{best}`, formatted); `stepLabel` the same with `"{step} of {count}"` /
|
|
96
|
+
`"第 {step} 步,共 {count} 步"`. templates/ui-kit passes every one of
|
|
97
|
+
these words from its five tables (`settings.*`, `shop.*`, `inventory.*`,
|
|
98
|
+
`leaderboard.*`, `tutorial.*`, `cutscene.*`).
|
|
99
|
+
- Emphasis inside a sentence is rich-text markup in the message itself
|
|
100
|
+
(`"Your progress in slot [b]{slot}[/b] will be lost."`, shown with
|
|
101
|
+
`og.ui.richText`): each translation bolds or colours its own words, in
|
|
102
|
+
its own order, instead of the code splitting the sentence into styled
|
|
103
|
+
pieces.
|
|
104
|
+
|
|
105
|
+
## 3. Fonts for every script
|
|
106
|
+
|
|
107
|
+
- What a screen reader hears is words too, and the same rule holds: a
|
|
108
|
+
group or a picture the kit names for a reader takes its name from the
|
|
109
|
+
desc, with the English default. `LevelSelectDesc.backLabel` (its Back
|
|
110
|
+
button), `starsLabel` (`"{stars} of {max} stars"`, a card's stars and
|
|
111
|
+
the total's: `"{stars} de {max} estrellas"`), `lockedLabel` (`"{name},
|
|
112
|
+
locked"`, a locked card); `SettingsDesc.sectionLabel` (the tab row, the
|
|
113
|
+
select on a phone: "Section") and `languagesLabel` (the language list:
|
|
114
|
+
"Languages"), and `qualityLabel` names the presets as well as heading
|
|
115
|
+
them. Below the templates, `TabsDesc`, `RadioDesc` and `SelectDesc`
|
|
116
|
+
take a `label` (else the reader hears the id) and `IconDesc` and
|
|
117
|
+
`ImageDesc` a `label` (else an icon is its atlas name, `star`; `""` is
|
|
118
|
+
decoration): og-ui §4.
|
|
119
|
+
- Latin and Cyrillic: the skin's font. Every other script: a subset of a
|
|
120
|
+
Noto face for that script holding exactly the characters your tables
|
|
121
|
+
use, named first in that table's `fonts` line.
|
|
122
|
+
- What the engine ships: `fonts/NotoSansSC-Regular-subset.ttf`, in every
|
|
123
|
+
project (Simplified Chinese: GB2312's 6,763 hanzi, kana and Latin-1,
|
|
124
|
+
2.5 MB), the source for a Chinese subset and the skins' CJK `fallback`.
|
|
125
|
+
The ui-kit's `NotoSansJP-subset.otf`, `NotoSansKR-subset.otf` and its
|
|
126
|
+
Arabic, Hebrew and Devanagari subsets hold only the ui-kit's own strings
|
|
127
|
+
(the Japanese one 246 characters, 74 of them kanji): they are no source
|
|
128
|
+
for a game's text. A Japanese, Korean, Traditional Chinese or Arabic
|
|
129
|
+
game brings the whole Noto face (notofonts, SIL OFL; Noto Sans CJK's
|
|
130
|
+
Regular `.ttc` holds Japanese as face 0, Korean 1, Simplified 2,
|
|
131
|
+
Traditional 3) into a folder outside `assets/` (`tools/fonts/`), so the
|
|
132
|
+
cook never sees it, and records its licence.
|
|
133
|
+
- Make the subset with `templates/ui-kit/tools/subset-font.py`, copied into
|
|
134
|
+
the project's `tools/`: it reads every `string` of `assets/strings/`
|
|
135
|
+
(`--locale ja` for one table, `--extra` for what players or playtests
|
|
136
|
+
type), writes the subset and its import settings (`chars` listing every
|
|
137
|
+
character) and exits 1 naming each character the source lacks. Run it
|
|
138
|
+
again after a string changes, then `og cook`:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
python3 tools/subset-font.py tools/fonts/NotoSansCJK-Regular.ttc assets/fonts/NotoSansJP-game.otf --face 0 --locale ja
|
|
142
|
+
python3 tools/subset-font.py assets/fonts/NotoSansSC-Regular-subset.ttf assets/fonts/NotoSansSC-game.ttf --locale zh-CN --cjk-only
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`--cjk-only` leaves Latin out, so a label mixing scripts takes its Latin
|
|
146
|
+
from the skin's face run by run; with Latin kept, the whole label is
|
|
147
|
+
drawn in the subset (og-ui §7 has the rule).
|
|
148
|
+
- Never take Japanese from the Chinese face: it lacks the Japanese forms of
|
|
149
|
+
common kanji (鐘 鈴 険 続 設 語 楽 効 are not in it) and draws the kanji
|
|
150
|
+
both scripts share in their Chinese shapes. The fallback chain below is a
|
|
151
|
+
safety net for a character a subset missed, not a font for a language:
|
|
152
|
+
a Japanese string that reaches the skin's Chinese `fallback` is drawn in
|
|
153
|
+
Chinese forms, and one no font has is a box with a warning on the first
|
|
154
|
+
frame that draws it.
|
|
155
|
+
- Each font's import settings (`<font>.ogt`) list its characters (`chars`)
|
|
156
|
+
or a charset: `ascii`, `latin`, `kana`, `hangul`, `arabic`. A font
|
|
157
|
+
cooked with the `arabic` charset also carries its joining forms and its
|
|
158
|
+
outlines, which og-ui's shaper needs.
|
|
159
|
+
- og-ui looks a glyph up in the active locale's fonts first, then every
|
|
160
|
+
other loaded table's, then `UiDesc.fallbackFonts` (fonts for scripts no
|
|
161
|
+
table names: a Hebrew or Devanagari face), then the skin's. A box (tofu)
|
|
162
|
+
in a frame means a font lacks the character: add it to the subset and
|
|
163
|
+
to `chars`. The engine's log says so on the tick a character is first
|
|
164
|
+
drawn, once per character, naming it, the font's asset and what its atlas
|
|
165
|
+
holds (`WARN tick 2603 a frame drew "−" U+2212, which font
|
|
166
|
+
skins/neon/ChakraPetch-BoldItalic.ttf lacks (its atlas holds charset
|
|
167
|
+
latin punct)`): the packs cook `latin punct`, which has the hyphen-minus
|
|
168
|
+
`-` but not the minus sign U+2212 nor the arrows U+2190 to U+2193. One
|
|
169
|
+
label is drawn in one font, so the font that covers a
|
|
170
|
+
Hebrew word must also have the digits beside it.
|
|
171
|
+
- Player-typed text (a name, a chat line) is not in any table: cook the
|
|
172
|
+
CJK face with `runtime true` in its import settings and the characters
|
|
173
|
+
outside its atlas are drawn at run time (about 1 to 2 ms each, once per
|
|
174
|
+
character), with a warning the first time. Keep `chars` for everything
|
|
175
|
+
the tables hold, so a frame never waits on them; `charset all` for CJK
|
|
176
|
+
is about 20 MB and never the answer.
|
|
177
|
+
- Complex scripts are shaped from the font file the cook keeps: Arabic
|
|
178
|
+
joins, Devanagari conjuncts and pre-base vowel signs, marks over their
|
|
179
|
+
bases. Cook such a font with the script's charset (`arabic`) or its
|
|
180
|
+
`chars`; check a frame of every shaped string (a conjunct drawn as
|
|
181
|
+
separate letters means the font or its file is missing). Kerning pairs
|
|
182
|
+
apply in every font that has them: nothing to set.
|
|
183
|
+
- Chinese, Japanese and Korean break between characters under kinsoku:
|
|
184
|
+
closing brackets and quotes, 、。,?!, small kana, ー and the iteration
|
|
185
|
+
marks never start a line, opening brackets never end one, and a katakana
|
|
186
|
+
word stays whole (オリジン・ / クエスト, not オリジン・クエス / ト);
|
|
187
|
+
Latin breaks at spaces. Nothing to configure: write the string as it
|
|
188
|
+
reads and check the frame.
|
|
189
|
+
|
|
190
|
+
## 4. Right to left
|
|
191
|
+
|
|
192
|
+
Arabic, Hebrew, Persian and Urdu locales set the UI's direction to
|
|
193
|
+
right-to-left: og.ui mirrors the whole layout about the surface's centre
|
|
194
|
+
(a corner anchored top-left moves top-right, rows run right to left),
|
|
195
|
+
flips start and end alignment, and orders words by direction while numbers
|
|
196
|
+
and Latin words inside stay left to right (by the Unicode bidirectional
|
|
197
|
+
algorithm, over each whole line of `og.ui.richText`; a label takes the
|
|
198
|
+
direction of its first strong character). Do not mirror the play field or
|
|
199
|
+
the camera. Icons are drawn as they are: give a direction icon (back, next)
|
|
200
|
+
its mirrored twin for right to left, and leave icons of objects alone.
|
|
201
|
+
|
|
202
|
+
## 5. The player's choice
|
|
203
|
+
|
|
204
|
+
The settings screen's language list calls `og.i18n.setLocale(world, tag)`
|
|
205
|
+
and saves the choice with the other settings (`og.platform.saveSettings`).
|
|
206
|
+
Restore it: `og.platform.loadSettings` on the first tick, and
|
|
207
|
+
`og.i18n.setLocale` with the saved tag when it answers (og-platform-sdk §3);
|
|
208
|
+
a game that only saves comes back in the host's language after a reload. `og.i18n.locale(world)`
|
|
209
|
+
answers the requested and active locale, the direction and the font chain.
|
|
210
|
+
|
|
211
|
+
## 6. Prove it
|
|
212
|
+
|
|
213
|
+
- A playtest per locale that matters (`templates/ui-kit/tests/locales.og.ts`):
|
|
214
|
+
`locale:<tag>` in the command script switches the locale, then
|
|
215
|
+
`screenshot` each main screen and compare with references; read each
|
|
216
|
+
frame for boxes, clipped words and wrong direction.
|
|
217
|
+
- Check text fits: the longest translation (German, Russian) of each
|
|
218
|
+
button in `dump_ui` stays inside its element.
|
|
219
|
+
- `og cook` prints `warning:` lines for missing or mismatched keys; the
|
|
220
|
+
build is not done while any remain.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: og-platform-sdk
|
|
3
|
+
description: The OriginGame platform from inside an Origin Engine game — og.platform's store (saves with revisions and conflicts), leaderboards, telemetry, Origin Point, AI-content marks and the settings helpers, as requests answered at a later tick; how they work in the Player, on the stage and under og run; how to test them with dump_platform and --platform-store; and what to ask the platform for when a method is missing. Use when a game saves progress or settings, shows scores, files events or asks for points (the platform's og-platform skill covers the Player protocol around it).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Game code never talks to the platform. It asks the engine, `og.platform.*`,
|
|
7
|
+
by method name and arguments; the host answers: the web shell over the
|
|
8
|
+
Player's protocol 2 (to the Player framing the page, or, when the platform
|
|
9
|
+
opens the published page itself at the top level, to its play origin over
|
|
10
|
+
same-origin HTTP), falling back to the browser's own storage; `og run`
|
|
11
|
+
from a local JSON store. No credential, URL or cookie reaches game code,
|
|
12
|
+
and every call works (or answers `unavailable`) everywhere, so the game
|
|
13
|
+
never branches on where it runs. The API is in `reference/platform.md`;
|
|
14
|
+
the platform's `og-platform` skill is the Player and readiness side.
|
|
15
|
+
|
|
16
|
+
## What works
|
|
17
|
+
|
|
18
|
+
Hollowbell (`examples/hollowbell`, written from this package alone) keeps
|
|
19
|
+
its run in the store (`og.platform.storeSet` at the revision it read, a
|
|
20
|
+
conflict read again and written back) and proves Continue across a
|
|
21
|
+
relaunch with a `store: "keep"` playtest; Lantern Vale
|
|
22
|
+
(`examples/lantern-vale`) keeps its settings through
|
|
23
|
+
`og.platform.saveSettings`. The relaunch worked the first time it was
|
|
24
|
+
asked for as §4 says. The Kestrel Stair (`examples/kestrel-stair`) keeps
|
|
25
|
+
its whole world at a waystone (`world.snapshot()`, about 78 KB of text, in
|
|
26
|
+
parts under the store's 64 KiB a key) and continues it after a
|
|
27
|
+
`store: "keep"` relaunch, its progress at the revision read with a
|
|
28
|
+
conflict merged, and a won climb on an ascending leaderboard. The store's
|
|
29
|
+
limits are the platform's (64 KiB a key, 64 keys and 1 MiB per player and
|
|
30
|
+
game), so the world goes through `og.platform.saveSlot`: its parts
|
|
31
|
+
(`run.a0`, `run.a1`) in whichever of the slot's two banks of keys
|
|
32
|
+
(`<slot>.a0..`, `<slot>.b0..`) the last save did not use, then the
|
|
33
|
+
manifest `run` at the revision read, so a save cut short leaves the
|
|
34
|
+
previous one whole and two devices saving at once get a conflict, not a
|
|
35
|
+
mix; one request a call. Its title reads `og.platform.loadSlot` with
|
|
36
|
+
`metaOnly` for Continue's place and hour, and Continue reads the parts,
|
|
37
|
+
checked against the manifest's length and checksum.
|
|
38
|
+
|
|
39
|
+
## 1. Requests, answered later
|
|
40
|
+
|
|
41
|
+
Every call returns a request number at once; the answer arrives at the
|
|
42
|
+
start of a later tick, when `og.platform` sends `og.PlatformAnswered`.
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// templates/archetypes/runner's Records system.
|
|
46
|
+
if (time.tick === 0) {
|
|
47
|
+
const asked = og.platform.storeGet(world, "best"); // the request, at once
|
|
48
|
+
run.bestRequest[i] = asked.ok ? asked.value : 0;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const request = run.bestRequest[i]!;
|
|
52
|
+
if (request === 0) return;
|
|
53
|
+
const answer = og.platform.result(world, request); // null while in flight
|
|
54
|
+
if (answer === null) return;
|
|
55
|
+
run.bestRequest[i] = 0;
|
|
56
|
+
if (answer.ok && typeof answer.data.value === "number") run.best[i] = Math.max(run.best[i]!, answer.data.value);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- Keep the request number in a component or resource (snapshots carry it),
|
|
60
|
+
never in a closure.
|
|
61
|
+
- Answers are host input, like key presses: a replay records them, so the
|
|
62
|
+
same answers give the same world.
|
|
63
|
+
- Every answer carries `time` (UTC ms): the host's clock when it was
|
|
64
|
+
answered. `og.platform.now(world)` is the host's clock at the last
|
|
65
|
+
answer (or when the platform attached) advanced by the ticks since, so
|
|
66
|
+
it is the one wall clock a game may read: the time away is a store
|
|
67
|
+
answer's `time` less the time the save wrote down
|
|
68
|
+
(`templates/archetypes/idle`, `tests/away.og.ts`). `info(world).clock`
|
|
69
|
+
says whose it is: `player`, `browser` (the web host's own, until the
|
|
70
|
+
Player sends its time), `system` (native), `fixed` (`og run --clock`,
|
|
71
|
+
playtests), `none` before the platform attaches (`now` answers 0).
|
|
72
|
+
- An error has a stable `code` (`offline`, `timeout`, `invalid`,
|
|
73
|
+
`unauthorized`, `not_found`, `conflict`, `too_large`, `rate_limited`,
|
|
74
|
+
`unavailable`, `failed`) and `retryable`. Handle `unavailable` as
|
|
75
|
+
"this feature is off here" and keep playing.
|
|
76
|
+
|
|
77
|
+
## 2. What to use
|
|
78
|
+
|
|
79
|
+
| Need | Call | Notes |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| Progress, unlocks, a best score | `storeGet(world, key)`, `storeSet(world, { key, value, revision })`; the whole world: `world.snapshot()` text through `saveSlot(world, { slot, text, meta })` (as many keys as it needs, the manifest last, one request) and `loadSlot(world, { slot, metaOnly })`, back with `world.restore` (og-game §17) | one key per independent thing; values ≤ 64 KiB, at most 64 keys and 1 MiB per player and game; pass the revision you read to refuse a stale write (`conflict`, whose `error.revision` is where the key is now: read again and merge), `create: true` to write only a key never written, no revision for "the last write wins" |
|
|
82
|
+
| Volume, controls, language | `loadSettings(world)`, `saveSettings(world, settings)` | the key `og.settings`, last save wins, local fallback when offline |
|
|
83
|
+
| A leaderboard | `leaderboardSubmit(world, { board, score })`, `leaderboardTop`, `leaderboardAround` | the board keeps the player's best; show "around me" as well as the top |
|
|
84
|
+
| Analytics | `telemetry(world, "round.over", { score, coins })` | one-way; a name of 1–64 letters, digits, `.` and `_` (the Player files it as `game.custom.<name>`), at most 16 scalar attributes; never personal data |
|
|
85
|
+
| Origin Point | `point(world, { reason, amount })` | the platform decides what it grants (today nothing under a Player: `{ granted: 0, balance: null }` until an approved point policy; show nothing that depends on it) |
|
|
86
|
+
| AI-generated content shown | `aiMark(world, { kind, label })` | disclosure, not permission |
|
|
87
|
+
|
|
88
|
+
`og.platform.info(world)` says what answers here (`player`, `local` or
|
|
89
|
+
`none`) and the methods it answers (`capabilities`): check it rather than
|
|
90
|
+
trying.
|
|
91
|
+
|
|
92
|
+
## 3. Rules
|
|
93
|
+
|
|
94
|
+
- Read saves on the first tick and show defaults until they answer;
|
|
95
|
+
never block the title on the platform. On the web the title behind the
|
|
96
|
+
play gate is not tick 1's defaults: the shell takes the first tick, then
|
|
97
|
+
one more tick each time the store answers what that tick asked (the
|
|
98
|
+
settings, a slot's manifest, then its parts), up to 8 ticks, before the
|
|
99
|
+
first frame while the answers come within 300 ms and drawn as they land
|
|
100
|
+
for 5 s after (docs/web-export.md §The shell). So apply an answer the
|
|
101
|
+
tick it arrives (the language, Continue's label), and apply it before
|
|
102
|
+
the title is built that tick: systems run in `GameDesc.systems` order,
|
|
103
|
+
so the system that reads the answers comes before (or is) the one that
|
|
104
|
+
builds the title. A title built by a `Ui` system listed before an
|
|
105
|
+
`Answers` system draws that tick in the defaults even though the answer
|
|
106
|
+
landed, and the frame behind the gate is that tick's: the saved Spanish
|
|
107
|
+
shows only after the tap. The fix is the order: list the system that
|
|
108
|
+
reads every answer first (`systems: [Answers, ..., Ui, Saves]`, writes
|
|
109
|
+
last), or read the answers at the top of the system that builds the
|
|
110
|
+
title. A title that waits for a later tick, or a screen transition that
|
|
111
|
+
only starts after the tap, still shows its defaults behind the gate.
|
|
112
|
+
- The frame behind the gate is a still of the opening title a few ticks
|
|
113
|
+
in, held until the tap, so anything on it that moves into place over
|
|
114
|
+
its first second stands part-way there: the skin's panel entrance and
|
|
115
|
+
its controls' stagger (about half a second), a transition, a fade the
|
|
116
|
+
game plays itself. Build the screen the game opens on settled while it
|
|
117
|
+
is that screen, and let every later screen enter as usual:
|
|
118
|
+
`og.ui.mainMenu("Title", { ..., instant: !g.leftTitle })` with
|
|
119
|
+
`g.leftTitle` set once the shown screen is another (examples/kestrel-stair
|
|
120
|
+
`src/ui.ts`); `og.ui.anchor` and `og.ui.transition` take the same
|
|
121
|
+
`instant`. A game's own intro on the title (a fade from black, a logo)
|
|
122
|
+
starts complete on the first tick, or plays after the player's first
|
|
123
|
+
press.
|
|
124
|
+
- Settings are a pair: a game that calls `saveSettings` also calls
|
|
125
|
+
`loadSettings` on its first tick and, when it answers, applies every
|
|
126
|
+
field it saves, through the same code its settings screen uses. Ask
|
|
127
|
+
for the saved language with `og.i18n.setLocale` even when it matches
|
|
128
|
+
the default (the host's own locale may be another). Saving alone
|
|
129
|
+
passes every test that never relaunches, and every reload comes back
|
|
130
|
+
in the defaults. A `store: "keep"` playtest whose relaunch reads the
|
|
131
|
+
setting back proves it (§4; `templates/ui-kit/tests/settings-saved.og.ts`).
|
|
132
|
+
- Save on meaningful moments (a new best, a level done, settings closed),
|
|
133
|
+
not every tick: writes are rate limited.
|
|
134
|
+
- Never store anything the game can recompute, and never a secret: the
|
|
135
|
+
store belongs to the player, not to the game.
|
|
136
|
+
- The engine exposes no identity (no player id, name or account): a game
|
|
137
|
+
that needs a display name asks for it (§5).
|
|
138
|
+
|
|
139
|
+
The settings read, as the UI kit does it (`followSettings` in
|
|
140
|
+
`templates/ui-kit/src/main.ts`, called by its system every tick):
|
|
141
|
+
|
|
142
|
+
```ts
|
|
143
|
+
if (time.tick === 0) {
|
|
144
|
+
d.settingsRequest = og.platform.loadSettings(world); // og.settings, answered later
|
|
145
|
+
} else if (d.settingsRequest !== 0) {
|
|
146
|
+
const answer = og.platform.result(world, d.settingsRequest);
|
|
147
|
+
if (answer !== null) {
|
|
148
|
+
d.settingsRequest = 0;
|
|
149
|
+
const saved = answer.ok ? (answer.data as { value?: unknown } | null)?.value : null;
|
|
150
|
+
if (saved && typeof saved === "object") takeSettings(world, d, saved as Partial<GameSettings>, true); // volumes, language, contrast, motion
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## 4. Test it
|
|
156
|
+
|
|
157
|
+
- `dump_platform[:file]` lists every call with its method, arguments, tick,
|
|
158
|
+
state and answer, and what answers them: assert on it in a playtest
|
|
159
|
+
(`templates/archetypes/runner/tests/records.og.ts`; a stale write read
|
|
160
|
+
again and merged: `templates/archetypes/idle/tests/offline.og.ts` and
|
|
161
|
+
`templates/archetypes/roguelike/tests/win.og.ts`).
|
|
162
|
+
- `og run --platform-store save.json` keeps the local store in a file, so
|
|
163
|
+
a second run sees the first's save; without it the store is in memory
|
|
164
|
+
for the run. `og run --clock <UTC ms>` fixes the host clock at tick 0
|
|
165
|
+
(it then moves with the ticks); a playtest runs at 2026-01-01 00:00 UTC
|
|
166
|
+
unless its `clock` says otherwise.
|
|
167
|
+
- A relaunch across processes: a playtest with `store: "keep"` keeps one
|
|
168
|
+
local store (`<out>/.store/platform.json`, a dot name no step's output
|
|
169
|
+
may take, so `dump_platform:platform.json` is safe) for its run and its `then` steps (`{ commands, clock }`,
|
|
170
|
+
each a fresh `og run`), and `check` reads each step's run in
|
|
171
|
+
`run.then[n]` (`templates/archetypes/idle/tests/away.og.ts`: saved at
|
|
172
|
+
`clock`, launched two hours later). A command that relaunches in the
|
|
173
|
+
same process stays for tests that need no second process.
|
|
174
|
+
- In the browser, `packages/engine/tests/web/player.ts` shows the Player
|
|
175
|
+
side: a framed game's first calls wait for the Player's `hello`, then go
|
|
176
|
+
as protocol 2 `call`s (`store.get`, `store.set`, `leaderboard.around`,
|
|
177
|
+
`point.award`, `ai.mark`, as the hello lists them; `docs/web-export.md`
|
|
178
|
+
§Platform services has the mapping).
|
|
179
|
+
- The same file then opens the build as a top-level page with a stand-in
|
|
180
|
+
play origin: the same calls go to `/__player/v2/<game>/<release>/call`
|
|
181
|
+
with the answers unchanged, so a game behaves the same framed or not
|
|
182
|
+
(`docs/web-export.md` §The top-level page). Nothing in game code changes
|
|
183
|
+
between the two, and nothing in it can tell them apart.
|
|
184
|
+
|
|
185
|
+
## 5. When a method is missing: ask
|
|
186
|
+
|
|
187
|
+
The engine maps what the Player's protocol 2 offers and answers the rest
|
|
188
|
+
`unavailable`. Every ask the engine made (the engine repository's
|
|
189
|
+
`docs/platform-asks.md`: the keyed store with per-key revisions,
|
|
190
|
+
`leaderboard.around`, the best on submit, `point.award`, `ai.mark`,
|
|
191
|
+
capabilities for a verification run, telemetry names) landed on
|
|
192
|
+
2026-10-19; an older Player answers what its hello lists. One is open:
|
|
193
|
+
the Player's `hello` and answers carrying its `time` (until then the web
|
|
194
|
+
host's answers carry the browser's clock and `info(world).clock` says
|
|
195
|
+
`browser`). When a game
|
|
196
|
+
needs something none of them gives, do not work around it with a server of
|
|
197
|
+
your own or by reaching the page: write the ask (the method, its arguments,
|
|
198
|
+
its answer, why a game needs it) for the platform owners and keep the
|
|
199
|
+
feature behind `info(world)` until it lands.
|