narraleaf-studio 1.4.1 → 1.4.3

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 (2) hide show
  1. package/dist/_api.d.ts +1591 -1415
  2. package/package.json +1 -1
package/dist/_api.d.ts CHANGED
@@ -1736,1336 +1736,6 @@ type BlueprintAssetPinRef = {
1736
1736
  */
1737
1737
  origin?: "stored" | "published";
1738
1738
  };
1739
- type AudioClipRegion = {
1740
- /** Offset from the start of the clip, in milliseconds. Where playback starts. */
1741
- inMs?: number;
1742
- /** Offset from the start of the clip, in milliseconds. Where playback stops, or where a loop turns around. */
1743
- outMs?: number;
1744
- /**
1745
- * Offset from the start of the clip, in milliseconds. Where each repeat returns to.
1746
- *
1747
- * Absent means "return to {@link inMs}" - a plain loop, and exactly what every record written
1748
- * before this field existed means. Set past the in point it describes the standard VN
1749
- * **intro→loop**: `inMs..loopStartMs` plays once, then `loopStartMs..outMs` repeats forever.
1750
- *
1751
- * Constrained to `inMs <= loopStartMs < outMs`. A value outside that window is dropped rather
1752
- * than clamped: clamping would invent a loop point the author never marked, while dropping
1753
- * degrades to the plain loop the two other markers already describe.
1754
- */
1755
- loopStartMs?: number;
1756
- /**
1757
- * Playback gain in decibels, never above 0: the engine cannot raise a clip past its own level, so
1758
- * balancing loudness means bringing the loud clips down. Absent is unity. Folded into a volume
1759
- * only by {@link clipVolume}.
1760
- */
1761
- gainDb?: number;
1762
- };
1763
- type StoredAudioClipRegion = Pick<AudioClipRegion, "inMs" | "outMs" | "loopStartMs">;
1764
- type StoredAudioGain = {
1765
- db: number;
1766
- };
1767
- type BlueprintOpenExternalRequest = {
1768
- url: string;
1769
- };
1770
- type BlueprintOpenExternalOutcome = "opened" | "refused" | "failed";
1771
- type BlueprintOpenExternalResult = {
1772
- outcome: BlueprintOpenExternalOutcome;
1773
- /** Human-readable reason, null when the page was handed over. */
1774
- error: string | null;
1775
- };
1776
- type BlueprintGraphKind = "event" | "function" | "macro";
1777
- type GameBuildPlatform = "windows" | "macos" | "linux" | "web" | "android" | "ios";
1778
- declare const UI_STRUCT_FIELD_TYPES: readonly [
1779
- "string",
1780
- "number",
1781
- "boolean",
1782
- "image",
1783
- "color",
1784
- "json"
1785
- ];
1786
- type UIStructFieldType = (typeof UI_STRUCT_FIELD_TYPES)[number];
1787
- type UIStructField = {
1788
- /**
1789
- * Stable identity. Value bindings, node params and generated pins all address a field by this,
1790
- * so renaming `key` costs nothing and reshuffling the list costs nothing.
1791
- */
1792
- id: string;
1793
- /** The property name this field occupies inside an item object. */
1794
- key: string;
1795
- /**
1796
- * What the author called it, shown in the item table header and in every field picker.
1797
- *
1798
- * Optional because `key` is usually already the readable name; a label is what lets a field
1799
- * whose key has to stay ASCII (it travels in JSON to a plugin, a save file, a story variable)
1800
- * still read as words in the editor.
1801
- */
1802
- label?: string;
1803
- type: UIStructFieldType;
1804
- };
1805
- type UIStructId = string;
1806
- type UIStructDef = {
1807
- id: UIStructId;
1808
- fields: UIStructField[];
1809
- };
1810
- type PluginPermissionPersistence = "temporary" | "permanent";
1811
- type PluginFileSystemPermissionMode = "read" | "write" | "readwrite";
1812
- /**
1813
- * Install-time permissions split into two families, and the split is the whole
1814
- * point:
1815
- *
1816
- * - **Author-declared** (`filesystem`, `api`) — privileged Studio controls the
1817
- * plugin asks for explicitly in `permissions[]`.
1818
- * - **Derived** (`runtime`, `sidecar`, `buildDependency`, `externalLink`,
1819
- * `network`) — computed from `contributes` by {@link validatePluginManifest}
1820
- * and *rejected* if written by
1821
- * hand. A capability is declared in exactly one place, so what the prompt shows
1822
- * and what the plugin can actually reach cannot drift apart. Adding a
1823
- * capability widens the permission set, which makes the update re-prompt for
1824
- * free (see `isPermissionSubset`).
1825
- */
1826
- export type PluginInstallPermission = {
1827
- kind: "filesystem";
1828
- path: string;
1829
- mode: PluginFileSystemPermissionMode;
1830
- recursive: boolean;
1831
- } | {
1832
- kind: "api";
1833
- capability: string;
1834
- } | {
1835
- /** Derived from `contributes.runtimeCapabilities`. */
1836
- kind: "runtime";
1837
- capability: PluginRuntimeCapability;
1838
- } | {
1839
- /** Derived from `contributes.sidecars`: a native child process shipped inside the author's game. */
1840
- kind: "sidecar";
1841
- id: string;
1842
- /**
1843
- * Which of the two sidecar shapes this is, carried through from the manifest because the
1844
- * two are different promises: `executable` starts a separate binary, `node` runs the
1845
- * plugin's own JavaScript under the game's Electron as Node. An author told only "this
1846
- * ships a helper program" has not been told which one they are approving.
1847
- *
1848
- * Named apart from `kind` above on purpose - that one says which permission this is, this
1849
- * one says what the sidecar runs.
1850
- */
1851
- sidecarKind: PluginSidecarKind;
1852
- /** `<platform>-<arch>` keys the sidecar ships binaries for. */
1853
- platforms: string[];
1854
- } | {
1855
- /** Derived from `contributes.buildDependencies`: binaries downloaded at build time. */
1856
- kind: "buildDependency";
1857
- id: string;
1858
- /** Distinct hostnames the binaries are fetched from. */
1859
- hosts: string[];
1860
- } | {
1861
- /**
1862
- * Derived from `contributes.externalLinks`: addresses outside the game this plugin may open
1863
- * in the player's browser or platform handler.
1864
- *
1865
- * One permission carrying every pattern rather than one permission each, because what the
1866
- * author is deciding is a single question - "may this plugin send the player out to these
1867
- * places" - and a prompt that asked it four times would be four chances to stop reading.
1868
- * `isPermissionSubset` still compares the patterns one by one, so adding one to a later
1869
- * version widens the set and re-prompts.
1870
- *
1871
- * The patterns are the author's own strings, unrewritten: this list is what the prompt
1872
- * shows, and normalizing a permission before showing it to the person approving it would
1873
- * make the prompt and the manifest two slightly different documents.
1874
- */
1875
- kind: "externalLink";
1876
- patterns: string[];
1877
- } | {
1878
- /**
1879
- * Derived from `contributes.network`: hosts this plugin's runtime code requests bytes from.
1880
- *
1881
- * A separate question from `externalLink` and never folded into it. Opening a page hands an
1882
- * address to the browser the player already uses and nothing comes back; this fetches, and
1883
- * what comes back runs inside the game. An author deciding about one has not decided about
1884
- * the other.
1885
- *
1886
- * The patterns are also what a project's network allowlist shows attributed to this plugin,
1887
- * which is the point of declaring them: a build narrowed to a list still reaches what the
1888
- * author approved here, and the list can say so rather than silently making room.
1889
- */
1890
- kind: "network";
1891
- patterns: string[];
1892
- };
1893
- type PluginSidecarKind = "executable" | "node";
1894
- declare const PluginRuntimeCapability: {
1895
- /** `app.game.store` — plugin-scoped persistent key/value storage. */
1896
- readonly Store: "store";
1897
- /** `app.game.events` — game lifecycle and engine event subscription. */
1898
- readonly Events: "events";
1899
- /** `app.game.state.get` — read story variables. */
1900
- readonly StateRead: "state.read";
1901
- /** `app.game.state.set` — write story variables. */
1902
- readonly StateWrite: "state.write";
1903
- /** `app.game.saves` — list and read save metadata. */
1904
- readonly SavesRead: "saves.read";
1905
- /**
1906
- * `app.game.saves.write` / `.load` — overwrite a save slot and load one.
1907
- *
1908
- * Separate from (and heavier than) `saves.read`: this can destroy a
1909
- * playthrough. It exists because quick-save/quick-load is an ordinary thing
1910
- * for a visual novel to ship — the built-in Quick Save plugin is exactly
1911
- * this — and a capability model that cannot express a feature the product
1912
- * already has would just push authors back around it.
1913
- */
1914
- readonly SavesWrite: "saves.write";
1915
- /** `app.game.ui.overlay` — draw on top of the game. */
1916
- readonly UiOverlay: "ui.overlay";
1917
- /** `app.game.assets` — resolve packaged asset URLs. */
1918
- readonly Assets: "assets";
1919
- /**
1920
- * `app.game.locale` — read and observe the game language, and read the project's own
1921
- * localized text through the same table and fallback chain the `Get Text` node uses.
1922
- */
1923
- readonly Locale: "locale";
1924
- /**
1925
- * `app.game.menu` — own the menu bar above the game.
1926
- *
1927
- * Its own capability rather than part of anything else, because what it grants is *the chrome
1928
- * the player reads the game through*: every row on that bar, and what each one does, is this
1929
- * plugin's to decide. The rows still act through the game's own vocabulary (see
1930
- * `@shared/types/gameMenu`) - nothing here reaches past what a blueprint could already do - but
1931
- * an author approving this is handing over a surface their players will read as the game's own,
1932
- * which is worth a name at install rather than arriving with something else.
1933
- *
1934
- * Absent on shells with no bar to give (the web export, Dev Mode), which is reported the way
1935
- * every environment difference is: the member is not there.
1936
- */
1937
- readonly Menu: "menu";
1938
- /**
1939
- * `app.game.story` — register a compile pass that observes each scene and injects engine
1940
- * actions around its rows.
1941
- *
1942
- * The heaviest of the ten to read, and the one whose weight is easiest to miss: nothing here
1943
- * touches the player's files, and the author still sees only their own document — but a pass
1944
- * runs over *every* scene of the project, sees who speaks in each, and can put actions around
1945
- * lines it did not write. That is a plugin editing the story, at the last moment before it
1946
- * plays, and it is worth a name the author approves at install.
1947
- *
1948
- * It is not `state.write` in disguise and does not imply it: a pass builds actions, it does not
1949
- * run them, and the one piece of runtime state it can touch is a scene-local flag the compiler
1950
- * creates and owns (`SceneCompileContext.runtimeFlag`), never a story variable the author
1951
- * declared.
1952
- */
1953
- readonly StoryCompile: "story.compile";
1954
- /**
1955
- * `app.game.diagnostics` — read what the player's caches are holding.
1956
- *
1957
- * The lightest of the list to grant and the easiest to misread as heavier than it is. Nothing
1958
- * here is authored data: it reports how many images the engine is holding, how many bytes those
1959
- * cost, and what budgets they are being held against. It reads no story, no save and no
1960
- * variable, and every member is a read - there is nothing to set.
1961
- *
1962
- * It is its own capability rather than free like `data` and `config` because a plugin that
1963
- * watches memory is a plugin that runs continuously and reports numbers about the player's
1964
- * machine, and an author should see that named at install. The performance inspector is the
1965
- * plugin this exists for.
1966
- *
1967
- * Absent until a game session is live, which is the same rule every other engine-backed member
1968
- * follows: a plugin's `setup()` runs during boot, and asking then answers null rather than
1969
- * throwing.
1970
- */
1971
- readonly Diagnostics: "diagnostics";
1972
- /**
1973
- * `app.game.process.memory()` — read how much memory the game's processes hold, as the
1974
- * operating system counts it.
1975
- *
1976
- * Its own capability rather than a member of `diagnostics`, because what it reports is not the
1977
- * game's: `diagnostics` describes the engine's caches, which are the game's own bookkeeping,
1978
- * while this is the player's machine - how much of its memory every process of the game is
1979
- * holding, the main process and the GPU process included. An author who approved the first did
1980
- * not approve the second, and a plugin that already holds `diagnostics` must not gain this by
1981
- * an update without being asked again - which is what a capability of its own guarantees.
1982
- *
1983
- * Read-only, and numbers only: sizes and what each process is for, never a path, an id or a
1984
- * command line. Nothing is sent anywhere by the host; what a plugin does with a reading is
1985
- * visible in the code the author installed.
1986
- *
1987
- * Absent where there are no processes to count - the web export - and narrowed in Dev Mode to
1988
- * the window's own renderer, since everything around it is Studio's.
1989
- */
1990
- readonly ProcessMemory: "process.memory";
1991
- };
1992
- type PluginRuntimeCapability = typeof PluginRuntimeCapability[keyof typeof PluginRuntimeCapability];
1993
- /**
1994
- * `app.game.sidecar` has no entry here on purpose: it exists exactly when
1995
- * `contributes.sidecars` is non-empty. Declaring the sidecar *is* the request.
1996
- */
1997
- export interface PluginIdentity {
1998
- id: string;
1999
- name?: string;
2000
- version?: string;
2001
- publisher?: string;
2002
- }
2003
- interface PluginPermissionRequestBase {
2004
- requestId: string;
2005
- plugin: PluginIdentity;
2006
- reason?: string;
2007
- requestedAt?: number;
2008
- }
2009
- export type PluginPermissionRequest = (PluginPermissionRequestBase & {
2010
- kind: "trust";
2011
- persistence?: PluginPermissionPersistence;
2012
- }) | (PluginPermissionRequestBase & {
2013
- kind: "filesystem";
2014
- path: string;
2015
- mode: PluginFileSystemPermissionMode;
2016
- recursive: boolean;
2017
- persistence: PluginPermissionPersistence;
2018
- }) | (PluginPermissionRequestBase & {
2019
- kind: "install";
2020
- source: string;
2021
- permissions?: PluginInstallPermission[];
2022
- persistence?: PluginPermissionPersistence;
2023
- }) | (PluginPermissionRequestBase & {
2024
- kind: "api";
2025
- capability: string;
2026
- persistence?: PluginPermissionPersistence;
2027
- });
2028
- interface PluginPermissionGrantResult {
2029
- requestId: string;
2030
- pluginId: string;
2031
- kind: PluginPermissionRequest["kind"];
2032
- approved: boolean;
2033
- persistence: PluginPermissionPersistence;
2034
- grantedAt?: number;
2035
- }
2036
- export type PluginPermissionPromptResult = PluginPermissionGrantResult | null;
2037
- declare const PluginManifestVersion = 2;
2038
- /**
2039
- * Per-target plugin entry files. Each entry is a prebundled ESM file relative
2040
- * to the plugin package root. At least one target must be declared.
2041
- *
2042
- * - `studio`: loaded in the workspace window only; talks to the
2043
- * `narraleaf-studio/plugin` host API (editor extensions).
2044
- * - `runtime`: loaded in every game execution environment (Dev Mode window,
2045
- * Preview, Production); talks to the `narraleaf-studio/runtime` host API
2046
- * (game logic such as blueprint node execute bindings).
2047
- */
2048
- export type PluginManifestEntries = {
2049
- studio?: string;
2050
- runtime?: string;
2051
- };
2052
- type PluginLocaleContribution = {
2053
- code: string;
2054
- nativeName?: string;
2055
- englishName?: string;
2056
- intl?: string;
2057
- dir?: "ltr" | "rtl";
2058
- messages: string;
2059
- };
2060
- type PluginBinaryPlatformKey = string;
2061
- type PluginSidecarTargetContribution = {
2062
- /** Executable (or, for `kind: "node"`, the .js file) to run. Must appear in `include`. */
2063
- entry: string;
2064
- /**
2065
- * Everything shipped for this platform. Entries are package-relative paths,
2066
- * or `dep:<buildDependencyId>/<path>` to pull an artifact produced by a
2067
- * declared build dependency (that is how third-party redistributables that
2068
- * we may not vendor ourselves reach the pack).
2069
- */
2070
- include: string[];
2071
- /**
2072
- * `sha256` of every package-relative entry in `include`, hex. Verified at
2073
- * install and again at pack time, so a tampered package fails to install
2074
- * instead of silently shipping a different binary. `dep:` entries are covered
2075
- * by the build dependency's own digest instead.
2076
- */
2077
- sha256: Record<string, string>;
2078
- };
2079
- type PluginSidecarContribution = {
2080
- /** Prefixed with the plugin id, like every other contributed identifier. */
2081
- id: string;
2082
- /**
2083
- * `executable` spawns the binary directly and talks to it over stdin/stdout; `node` runs the
2084
- * .js as an Electron utility process and talks to it over `process.parentPort`, which is the
2085
- * one channel a utility process has - it has no stdin.
2086
- */
2087
- kind: PluginSidecarKind;
2088
- /**
2089
- * Newline-delimited JSON, which is the framing and not the channel: where the frames travel is
2090
- * {@link kind}'s business, and only an executable sidecar's travel over stdio. Either way
2091
- * stderr stays a plain log channel.
2092
- *
2093
- * `stdio-jsonl` is the older spelling of the same thing, from when there was only one channel
2094
- * it could mean. Manifests that say it keep working and always will - a published plugin is a
2095
- * file somebody already shipped - but a new one should say `jsonl`.
2096
- */
2097
- transport: "jsonl" | "stdio-jsonl";
2098
- /** `onGameStart` spawns with the window; `onRequest` waits for the first call. */
2099
- autostart: "onGameStart" | "onRequest";
2100
- /** How long the handshake may take before the sidecar counts as unavailable. */
2101
- startupTimeoutMs: number;
2102
- /** Grace period between the shutdown message and SIGTERM. */
2103
- shutdownTimeoutMs: number;
2104
- restart: {
2105
- maxRetries: number;
2106
- backoffMs: number;
2107
- };
2108
- targets: Record<PluginBinaryPlatformKey, PluginSidecarTargetContribution>;
2109
- };
2110
- type PluginBuildDependencyTargetContribution = {
2111
- url: string;
2112
- /** Mandatory. Doubles as the cache key, so re-pointing the URL at identical bytes never re-downloads. */
2113
- sha256: string;
2114
- archive: "zip";
2115
- /** Archive-internal path -> path inside the produced dependency directory. */
2116
- files: Record<string, string>;
2117
- } | {
2118
- url: string;
2119
- sha256: string;
2120
- archive: "none";
2121
- /** Name the downloaded file takes inside the produced dependency directory. */
2122
- fileName: string;
2123
- };
2124
- type PluginBuildDependencyContribution = {
2125
- /** Prefixed with the plugin id. Referenced from sidecar `include` as `dep:<id>/<path>`. */
2126
- id: string;
2127
- /** Shown to the author at install and in build logs; say what the binaries are. */
2128
- description?: string;
2129
- targets: Record<PluginBinaryPlatformKey, PluginBuildDependencyTargetContribution>;
2130
- };
2131
- declare const PLUGIN_BUILD_CONFIG_TYPES: readonly [
2132
- "text",
2133
- "secret"
2134
- ];
2135
- type PluginBuildConfigType = (typeof PLUGIN_BUILD_CONFIG_TYPES)[number];
2136
- declare const PLUGIN_BUILD_CONFIG_SCOPES: readonly [
2137
- "global",
2138
- "variant",
2139
- "platform",
2140
- "variant-platform"
2141
- ];
2142
- type PluginBuildConfigScope = (typeof PLUGIN_BUILD_CONFIG_SCOPES)[number];
2143
- type PluginBuildConfigFieldContribution = {
2144
- /** Unique within the plugin. Keys the stored value; never displayed. */
2145
- key: string;
2146
- /** Author-facing name of the field. */
2147
- label: string;
2148
- /** One line saying what the value is for. */
2149
- description?: string;
2150
- /** `text` is stored in the project; `secret` is not - the project stores a handle. */
2151
- type: PluginBuildConfigType;
2152
- /** Which builds share one value. */
2153
- scope: PluginBuildConfigScope;
2154
- /** Platforms the field applies to. Absent means every platform. */
2155
- platforms?: GameBuildPlatform[];
2156
- /** A build that would ship without a value for this field is refused. */
2157
- required?: boolean;
2158
- };
2159
- export type PluginContributes = {
2160
- /** Blueprint node types this plugin provides (editor def + runtime execute). */
2161
- blueprintNodes?: string[];
2162
- /** Widget element types this plugin provides (editor module + runtime renderer). */
2163
- widgets?: string[];
2164
- /** Studio language packs: locales this plugin adds or fills. */
2165
- locales?: PluginLocaleContribution[];
2166
- /**
2167
- * Plugin storage namespaces to publish with the game, readable at runtime
2168
- * through `app.game.data.readJson(namespace)`.
2169
- *
2170
- * Plugin stores live under the project's `editor/` directory, which is never
2171
- * packaged. A plugin whose runtime needs authored data (catalogs, tables)
2172
- * must list those namespaces here. The list is an explicit allowlist so
2173
- * editor-only plugin state cannot leak into a shipped game by accident.
2174
- */
2175
- runtimeData?: string[];
2176
- /**
2177
- * Test ids this plugin registers with `app.services.tests` (see
2178
- * `docs/plugin-test-protocol.md`). Declaring them lets the Launcher say what
2179
- * a plugin checks before any of its code runs, and registering an
2180
- * undeclared id throws at load.
2181
- *
2182
- * Unlike every other code-backed contribution here, this one derives no
2183
- * install permission — a test only ever executes because the author picked
2184
- * it out of the Run > Test dialog and pressed Start, so there is no ambient
2185
- * capability to consent to at install time.
2186
- */
2187
- tests?: string[];
2188
- /**
2189
- * Save ids the plugin keeps for its own bookkeeping - a quick-save slot, a checkpoint - rather
2190
- * than slots a player chose.
2191
- *
2192
- * The game leaves them out of `List Saves`, and so out of every save screen built on it, the way
2193
- * it leaves out its own autosave ring. Nothing else changes for them: the plugin still writes,
2194
- * loads and finds them through `app.game.saves` (whose `listIds` is the raw store listing and
2195
- * still includes them), and the explicit-id save nodes - `Load Save`, `Delete Save`, the
2196
- * `Get Save` readers - take them like any other id.
2197
- *
2198
- * Prefixed with the plugin id like the type lists above, so a plugin can only reserve ids in its
2199
- * own namespace and can never hide a slot the author's own `Save Game` wrote. That is also why it
2200
- * derives no install permission: it reaches nothing the plugin did not already own.
2201
- */
2202
- reservedSaveIds?: string[];
2203
- /**
2204
- * Capability domains the `runtime` entry may use. Each maps 1:1 onto a
2205
- * namespace on `app.game`, and an undeclared domain is absent from that
2206
- * object rather than present-and-throwing — so what the install prompt
2207
- * listed and what the plugin can reach are the same set by construction.
2208
- */
2209
- runtimeCapabilities?: PluginRuntimeCapability[];
2210
- /** Native child processes shipped inside the author's game. */
2211
- sidecars?: PluginSidecarContribution[];
2212
- /** External binaries fetched (and cached) at build time. */
2213
- buildDependencies?: PluginBuildDependencyContribution[];
2214
- /**
2215
- * Values the author supplies before a build can ship. Declaration only - see
2216
- * {@link PluginBuildConfigFieldContribution} for why there is no registration API and why this
2217
- * grants the plugin nothing.
2218
- */
2219
- buildConfig?: PluginBuildConfigFieldContribution[];
2220
- /**
2221
- * Address patterns this plugin may hand to the player's browser or platform handler, through
2222
- * `app.game.navigation.openExternal`.
2223
- *
2224
- * Unlike everything else here, this one is *not* a list of identifiers the plugin owns - it is a
2225
- * list of places outside the game it may reach, so it derives an install permission the author
2226
- * approves by name (see `PluginInstallPermission` `kind: "externalLink"`) and adding one to a
2227
- * later version re-prompts.
2228
- *
2229
- * Patterns are matched structurally, never as strings: see `isExternalLinkPatternDeclared` in
2230
- * `@shared/types/externalLinkPattern` for the exact semantics. Wildcards are allowed
2231
- * (`https://*.example.com/app/*`) and the scheme is not restricted to `http(s)`, because the
2232
- * storefronts a plugin integrates with speak their own (`steam://run/480`). A handful of schemes
2233
- * are refused at validation whatever a manifest says; that list and its reasoning live in the
2234
- * same module.
2235
- *
2236
- * This is separate from, and grants nothing towards, {@link network}: opening a page and
2237
- * fetching bytes are different acts, and the Open Link node the author writes is a third thing
2238
- * again, governed only by its scheme.
2239
- */
2240
- externalLinks?: string[];
2241
- /**
2242
- * Address patterns this plugin's runtime code requests bytes from.
2243
- *
2244
- * Like {@link externalLinks} and unlike everything else here, this is a list of places outside
2245
- * the game rather than identifiers the plugin owns, so it derives an install permission the
2246
- * author approves by name (`PluginInstallPermission` `kind: "network"`) and adding one to a
2247
- * later version re-prompts.
2248
- *
2249
- * Two rules narrower than the external-link patterns, both for the same reason - what comes
2250
- * back from a fetch runs inside the game, while an opened page does not:
2251
- *
2252
- * - **`http(s)` only.** There is no storefront-scheme case here; a plugin that fetches speaks
2253
- * HTTP.
2254
- * - **The path is written out.** `https://api.example.com/*` is a declaration and
2255
- * `https://api.example.com` is not, because the pattern language reads the second as the
2256
- * single path `/`. It is refused rather than rewritten: this string is what the install
2257
- * prompt shows, and a manifest whose text differs from what was approved is the one thing
2258
- * this whole mechanism exists to prevent.
2259
- *
2260
- * What this reaches is *not* widened by the project's own network allowlist and does not widen
2261
- * it. A build narrowed to a list still reaches these hosts - the author approved them at
2262
- * install - and the list shows them attributed to this plugin rather than quietly making room.
2263
- */
2264
- network?: string[];
2265
- /**
2266
- * The props of a contributed widget that hold words a player reads, by widget type.
2267
- *
2268
- * Each type named here must also be in {@link widgets}. A declared prop is treated as Studio's own
2269
- * text and button labels are: the properties panel offers its words written directly or read
2270
- * from a translation key, the canvas draws a key's words, the words go into the project's
2271
- * translation table and exports, the project checks report a missing translation or key, and
2272
- * the glyph check reads them. In the game the widget's renderer is handed the words already in
2273
- * the player's language, in the prop it declared - it never resolves a key itself.
2274
- *
2275
- * Declared in the manifest rather than at registration because both entries need it - the
2276
- * studio entry's widget is edited by it and the runtime entry's is drawn by it - and a
2277
- * declaration only one of them made would be the half that drifts.
2278
- */
2279
- widgetText?: Record<string, PluginWidgetTextContribution[]>;
2280
- /**
2281
- * The shapes of the rows this plugin's nodes hand out, so a list can be given one and a field
2282
- * read from one by name.
2283
- *
2284
- * A node pin carrying them says `struct:<id>` or `array<struct:<id>>` - `Get Gallery` answers
2285
- * `array<struct:narraleaf.gallery.entry>` - and from then on the plugin's rows are what the
2286
- * engine's own are: a list picks the shape under Edit content and its fields are the plugin's
2287
- * and locked, Get Field offers them, and a list handed rows that lack them is reported.
2288
- *
2289
- * Declared in the manifest rather than at registration because the editor, the running game and
2290
- * the command-line checker all need the shape, and only the manifest is read by all three before
2291
- * any plugin code runs. Ids must start with the plugin id.
2292
- */
2293
- structs?: PluginStructContribution[];
2294
- };
2295
- type PluginStructContribution = {
2296
- /** Prefixed with the plugin id: `narraleaf.gallery.entry`. */
2297
- id: string;
2298
- /** What the editor calls the shape - "Gallery entry". */
2299
- name: string;
2300
- /** `name` per editor locale, matched exactly as `localized` names are. */
2301
- localized?: Record<string, string>;
2302
- /** In the order the rows are read: the list's content editor shows them in this order. */
2303
- fields: {
2304
- key: string;
2305
- type: UIStructFieldType;
2306
- }[];
2307
- };
2308
- /**
2309
- * One prop of a contributed widget whose value is words a player reads.
2310
- *
2311
- * `keyProp` is the prop the widget's translation key is kept in when the author reads the words from
2312
- * one. It defaults to `<prop>LocalizationKey`, and it is the host's to write: the widget never reads it
2313
- * to draw, because the words in `prop` already come from the key when there is one.
2314
- */
2315
- export type PluginWidgetTextContribution = {
2316
- /** The prop holding the words. */
2317
- prop: string;
2318
- /** The prop the translation key is kept in; `<prop>LocalizationKey` when not declared. */
2319
- keyProp?: string;
2320
- /** What the properties panel calls the words. The prop name when not declared. */
2321
- label?: string;
2322
- /** `label` per editor locale, matched exactly as `localized` names are. */
2323
- localized?: Record<string, string>;
2324
- /** Whether the words may run over several lines; one line when not declared. */
2325
- multiline?: boolean;
2326
- };
2327
- /**
2328
- * The plugin's name and description in one editor language. Either may be left out, and the plain
2329
- * manifest field is shown in its place.
2330
- */
2331
- export type PluginManifestLocalizedText = {
2332
- name?: string;
2333
- description?: string;
2334
- };
2335
- /**
2336
- * `name` and `description` per editor locale, keyed by the exact code Studio's language setting
2337
- * stores (`zh`, `ja`, or a code a language pack adds). There is no region matching: an entry under
2338
- * `zh-CN` is never shown, because no Studio locale is called that.
2339
- */
2340
- export type PluginManifestLocalized = Record<string, PluginManifestLocalizedText>;
2341
- export type PluginManifestV2 = Omit<PluginIdentity, "id" | "name" | "version"> & Required<Pick<PluginIdentity, "id" | "name" | "version">> & {
2342
- manifestVersion: typeof PluginManifestVersion;
2343
- description?: string;
2344
- /**
2345
- * Translations of `name` and `description`, shown wherever Studio names the plugin to an author
2346
- * whose editor is set to that language. The plain fields stay the plugin's identity: command
2347
- * lines, project files and built games name it by them.
2348
- */
2349
- localized?: PluginManifestLocalized;
2350
- /**
2351
- * Package-relative path to the thumbnail shown beside the plugin's name in
2352
- * the Launcher list. Square, at most 512x512, and one of the extensions in
2353
- * `PLUGIN_ICON_EXTENSIONS`. Plugins without one keep the name monogram.
2354
- */
2355
- icon?: string;
2356
- entries: PluginManifestEntries;
2357
- contributes?: PluginContributes;
2358
- permissions?: PluginInstallPermission[];
2359
- };
2360
- export type NormalizedPluginManifestV2 = PluginManifestV2 & {
2361
- contributes: Required<PluginContributes>;
2362
- permissions: PluginInstallPermission[];
2363
- };
2364
- type PluginInstallSource = {
2365
- kind: "local-directory";
2366
- path: string;
2367
- } | {
2368
- kind: "builtin";
2369
- path: string;
2370
- } | {
2371
- kind: "registry";
2372
- url: string;
2373
- };
2374
- export type PluginInstallRecord = {
2375
- pluginId: string;
2376
- installPath: string;
2377
- enabled: boolean;
2378
- builtIn: boolean;
2379
- manifest: NormalizedPluginManifestV2;
2380
- installSource: PluginInstallSource;
2381
- installedAt: number;
2382
- updatedAt: number;
2383
- grantedManifestVersion?: string | null;
2384
- /**
2385
- * The permission set the user actually approved, recorded so a later version
2386
- * that does not widen it can inherit the grant instead of re-prompting.
2387
- * Absent on records written before this was tracked — the manifest at
2388
- * `grantedManifestVersion` is the fallback source of truth for those.
2389
- */
2390
- grantedPermissions?: PluginInstallPermission[] | null;
2391
- lastError?: string | null;
2392
- };
2393
- type PluginStatus = "enabled" | "disabled" | "needsAuthorization" | "error";
2394
- export type PluginListItem = {
2395
- pluginId: string;
2396
- manifest: NormalizedPluginManifestV2;
2397
- /**
2398
- * `app://` address of the declared icon, absent when the plugin ships none.
2399
- * Resolved here rather than in the renderer because only the main process
2400
- * knows where the package landed on disk.
2401
- */
2402
- iconUrl?: string;
2403
- installPath: string;
2404
- enabled: boolean;
2405
- builtIn: boolean;
2406
- status: PluginStatus;
2407
- installSource: PluginInstallSource;
2408
- installedAt: number;
2409
- updatedAt: number;
2410
- grantedManifestVersion?: string | null;
2411
- lastError?: string | null;
2412
- };
2413
- /** Descriptor handed to the workspace window: plugins with a studio entry. */
2414
- export type WorkspacePluginDescriptor = {
2415
- plugin: PluginIdentity;
2416
- manifest: NormalizedPluginManifestV2;
2417
- entryUrl: string;
2418
- };
2419
- /** Descriptor handed to game execution environments: plugins with a runtime entry. */
2420
- export type RuntimePluginDescriptor = {
2421
- plugin: PluginIdentity;
2422
- manifest: NormalizedPluginManifestV2;
2423
- entryUrl: string;
2424
- /**
2425
- * Published plugin storage, keyed by the namespaces declared in
2426
- * `contributes.runtimeData`. Absent namespaces simply have no entry - a
2427
- * plugin must tolerate missing data (the project may never have written it).
2428
- */
2429
- data?: Record<string, unknown>;
2430
- /**
2431
- * This plugin's own `contributes.buildConfig` values, as the build that produced the pack
2432
- * resolved them, keyed by field key. Only ever the declaring plugin's own values: the pack
2433
- * holds one record per plugin and a descriptor is built from one entry.
2434
- *
2435
- * Absent outside a compiled pack - the editor and Dev Mode hand out descriptors that were never
2436
- * built for a variant, so there is nothing resolved to carry.
2437
- */
2438
- buildConfig?: Record<string, string>;
2439
- };
2440
- type GameMenuLabel = {
2441
- readonly key?: string | null;
2442
- readonly text: string;
2443
- readonly words?: string | null;
2444
- };
2445
- declare const GAME_MENU_DYNAMIC_SOURCES: readonly [
2446
- "textLanguage",
2447
- "voiceLanguage",
2448
- "windowScale"
2449
- ];
2450
- type GameMenuDynamicSource = typeof GAME_MENU_DYNAMIC_SOURCES[number];
2451
- type GameMenuAction =
2452
- /** Open an app surface as a page. */
2453
- {
2454
- readonly type: "openPage";
2455
- readonly surfaceId: string;
2456
- }
2457
- /** Open an app surface as a layer over whatever is on screen. */
2458
- | {
2459
- readonly type: "openLayer";
2460
- readonly surfaceId: string;
2461
- readonly modal?: boolean;
2462
- readonly dismissible?: boolean;
2463
- readonly group?: string | null;
2464
- }
2465
- /** End the playthrough and hand the screen to a page - the same act as the story's quit row. */
2466
- | {
2467
- readonly type: "quitToPage";
2468
- readonly surfaceId: string;
2469
- }
2470
- /**
2471
- * Close the game.
2472
- *
2473
- * The same act as the Quit Application node, which means the same terms: a graceful quit that
2474
- * does NOT run the author's close handler (`On Window Close Requested` answers the window
2475
- * frame's own close, and the node deliberately stands the guard aside so a quit the game asked
2476
- * for is not asked back about). An author who wants their own "really quit?" screen binds a
2477
- * function instead and calls the node at the end of it - which is the same answer this menu
2478
- * gives for every other confirmation.
2479
- */
2480
- | {
2481
- readonly type: "quitApp";
2482
- }
2483
- /** Advance the line on screen. */
2484
- | {
2485
- readonly type: "next";
2486
- }
2487
- /** Auto-forward on/off. Ticked while on. */
2488
- | {
2489
- readonly type: "toggleAutoForward";
2490
- }
2491
- /** Skipping on/off. Ticked while running. */
2492
- | {
2493
- readonly type: "toggleSkipping";
2494
- }
2495
- /** Which lines skipping stops at. The two values are a radio pair, never one switch. */
2496
- | {
2497
- readonly type: "setSkipReadText";
2498
- readonly value: boolean;
2499
- }
2500
- /** Show or hide the dialogue box. Ticked while shown. */
2501
- | {
2502
- readonly type: "toggleDialog";
2503
- }
2504
- /** Step back one line of history. Greyed out when there is none. */
2505
- | {
2506
- readonly type: "historyUndo";
2507
- }
2508
- /** Step forward again after an undo. Greyed out when there is none. */
2509
- | {
2510
- readonly type: "historyRedo";
2511
- }
2512
- /** Full-screen on/off. Ticked while full-screen, including when the OS did it. */
2513
- | {
2514
- readonly type: "toggleFullscreen";
2515
- }
2516
- /**
2517
- * Call a function declared in the global blueprint.
2518
- *
2519
- * `fnRef` is the catalogue's own reference (see `fnCatalog`), and `args` are keyed by the head's
2520
- * parameter pin ids. Everything the named actions above do not cover goes through here -
2521
- * including every confirmation, because what a game asks before losing a playthrough is the
2522
- * author's screen and not a native box in a language the game may not even be running in.
2523
- */
2524
- | {
2525
- readonly type: "fn";
2526
- readonly fnRef: string;
2527
- readonly args?: Readonly<Record<string, unknown>>;
2528
- };
2529
- type GameMenuItemSpec = {
2530
- readonly kind: "separator";
2531
- } | {
2532
- readonly kind: "submenu";
2533
- readonly label: GameMenuLabel;
2534
- readonly items: readonly GameMenuItemSpec[];
2535
- } | {
2536
- readonly kind: "action";
2537
- readonly label: GameMenuLabel;
2538
- readonly action: GameMenuAction;
2539
- } | {
2540
- readonly kind: "dynamic";
2541
- readonly source: GameMenuDynamicSource;
2542
- };
2543
- type GameMenuSpecMenu = {
2544
- readonly label: GameMenuLabel;
2545
- readonly items: readonly GameMenuItemSpec[];
2546
- };
2547
- type GameMenuSpec = {
2548
- readonly menus: readonly GameMenuSpecMenu[];
2549
- };
2550
- /**
2551
- * How much memory a running game costs the machine it runs on, process by process.
2552
- *
2553
- * Read in the main process from Electron's process metrics (`app.getAppMetrics()`), which is the
2554
- * operating system's own account: the same working set a task manager shows. Nothing a page can
2555
- * read gives this. `performance.memory` describes one JavaScript heap, and in Electron it is also
2556
- * rounded to coarse buckets - the two arms of a measured comparison both read exactly 92.9 MB -
2557
- * while the memory a visual novel actually spends is mostly outside any heap:
2558
- * decoded pictures, textures in the GPU process, audio buffers, the main process serving and
2559
- * decrypting assets. The whole game is several processes, and only the process that owns them can
2560
- * see all of them.
2561
- *
2562
- * `process.memoryUsage()` in the main process was the other candidate and answers a narrower
2563
- * question - the main process's own heap and buffers - which is the one process a game's pictures
2564
- * are not in.
2565
- *
2566
- * Comments in English per project convention.
2567
- */
2568
- /** What a process is for, in the words a reader of a memory report needs. */
2569
- type GameProcessKind = "main" | "renderer" | "gpu" | "utility" | "other";
2570
- type GameProcessMemoryEntry = {
2571
- kind: GameProcessKind;
2572
- /**
2573
- * What the platform calls it, when it says: a utility process names its service ("Network
2574
- * Service", "Audio Service", a sidecar's own name). Absent for the rest.
2575
- */
2576
- name?: string;
2577
- /** Whether this is the process the asking page runs in. */
2578
- current: boolean;
2579
- /** Memory currently resident in physical RAM, in bytes. */
2580
- workingSetBytes: number;
2581
- /** The most this process has ever had resident, in bytes. */
2582
- peakWorkingSetBytes: number;
2583
- /** Memory no other process shares, in bytes. Windows reports it; other platforms do not. */
2584
- privateBytes?: number;
2585
- };
2586
- type GameProcessMemoryReading = {
2587
- /**
2588
- * What the processes below add up to.
2589
- *
2590
- * - `game` - every process the game is made of. A packaged game and a preview.
2591
- * - `window` - only the process that draws this window. Dev Mode: the window is Studio's, and
2592
- * the processes around it (Studio's main process, its GPU process, its other windows) are
2593
- * shared with everything else Studio has open, so counting them would report Studio.
2594
- */
2595
- scope: "game" | "window";
2596
- processes: GameProcessMemoryEntry[];
2597
- /** The sum of every process's working set, in bytes. */
2598
- workingSetBytes: number;
2599
- /** The sum of every process's private bytes, or null where the platform does not report them. */
2600
- privateBytes: number | null;
2601
- };
2602
- declare const UI_INPUT_POINTER_GESTURES: readonly [
2603
- "click",
2604
- "doubleClick",
2605
- "rightClick",
2606
- "wheelUp",
2607
- "wheelDown",
2608
- "wheelLeft",
2609
- "wheelRight",
2610
- "longPress",
2611
- "middleClick"
2612
- ];
2613
- type UIInputPointerGesture = (typeof UI_INPUT_POINTER_GESTURES)[number];
2614
- type UIInputBinding = {
2615
- kind: "pointer";
2616
- gesture: UIInputPointerGesture;
2617
- } | {
2618
- kind: "key";
2619
- key: string;
2620
- } | {
2621
- kind: "gamepad";
2622
- button: string;
2623
- };
2624
- type UIInputActionDef = {
2625
- id: string;
2626
- /** Author-facing name. */
2627
- name: string;
2628
- /** The bindings a surface gets unless it overrides them. */
2629
- bindings: UIInputBinding[];
2630
- };
2631
- type UISurfaceActionEnablement = {
2632
- actionId: string;
2633
- /** Whether firing this action stops the input here. Default true. */
2634
- consume?: boolean;
2635
- };
2636
- declare const UI_PAGE_ANIMATION_PRESETS: readonly [
2637
- "none",
2638
- "fade",
2639
- "slide",
2640
- "push",
2641
- "zoom",
2642
- "pop",
2643
- "blur"
2644
- ];
2645
- declare const UI_PAGE_ANIMATION_DIRECTIONS: readonly [
2646
- "auto",
2647
- "left",
2648
- "right",
2649
- "up",
2650
- "down",
2651
- "angle"
2652
- ];
2653
- type UIPageAnimationPreset = (typeof UI_PAGE_ANIMATION_PRESETS)[number];
2654
- type UIPageAnimationDirection = (typeof UI_PAGE_ANIMATION_DIRECTIONS)[number];
2655
- type UIPageAnimationSettings = {
2656
- enter: UIPageAnimationPreset;
2657
- exit: UIPageAnimationPreset;
2658
- enterDirection: UIPageAnimationDirection;
2659
- exitDirection: UIPageAnimationDirection;
2660
- enterAngleDegrees: number;
2661
- exitAngleDegrees: number;
2662
- enterDurationSeconds: number;
2663
- exitDurationSeconds: number;
2664
- /** Held before this host's own enter starts, counted from the moment its subtree begins entering. */
2665
- enterDelaySeconds: number;
2666
- /** Held before this host's own exit starts, counted from the moment its subtree begins exiting. */
2667
- exitDelaySeconds: number;
2668
- /** Added to each successive direct child, so a row of children arrives (and leaves) one after another. */
2669
- childStaggerSeconds: number;
2670
- /** Hold this host's own exit until every child has finished leaving. Chains through nested parents. */
2671
- exitWaitsForChildren: boolean;
2672
- /** Surfaces only: hold the incoming Page until this exit has finished. */
2673
- exitBlocking: boolean;
2674
- };
2675
- type UISurfaceBackgroundFillMode = "cover" | "contain" | "stretch" | "tile";
2676
- type UISurfaceBackgroundImage = {
2677
- /** Image library asset id. Absent background = absent field, so this is never empty. */
2678
- assetId: string;
2679
- fillMode: UISurfaceBackgroundFillMode;
2680
- };
2681
- type UIDocumentVersion = number;
2682
- type UIDocumentId = string;
2683
- type UISurfaceId = string;
2684
- type UIElementId = string;
2685
- type UIDocument = {
2686
- schemaVersion: UIDocumentVersion;
2687
- id: UIDocumentId;
2688
- name: string;
2689
- surfaces: UISurface[];
2690
- components?: UIComponentDefinition[];
2691
- elements: Record<UIElementId, UIElement>;
2692
- /**
2693
- * The shapes this document's widgets describe their data with, keyed by id.
2694
- *
2695
- * No authoring surface manages this table: an author edits the fields on the widget that uses
2696
- * them, and a widget stores only the id. It is a document-level table rather than a copy on each
2697
- * widget so that a shape has one spelling for the whole project - which is what lets one list's
2698
- * content feed another's, and what a Break node's pins are generated from. The rules that keep
2699
- * an implicit library safe to edit are in `struct.ts`.
2700
- */
2701
- structs?: Record<UIStructId, UIStructDef>;
2702
- /**
2703
- * What the gestures of this project mean, keyed by id.
2704
- *
2705
- * The vocabulary half of the input model: "advance", "skip", "open the menu" - named once for
2706
- * the whole project, with the bindings a surface gets unless it says otherwise. A surface stores
2707
- * only the id, in {@link UISurface.actions}, so renaming an action here renames it everywhere
2708
- * and rebinding it rebinds every surface that took the default. The rules are in
2709
- * `inputAction.ts`.
2710
- */
2711
- actions?: Record<string, UIInputActionDef>;
2712
- /**
2713
- * The page the game starts on, by id.
2714
- *
2715
- * Absent means the page carrying `MAIN_APP_SURFACE_ID` - the id a new document gives its first
2716
- * page, and the only entry a document had before this field existed - so a document whose author
2717
- * never moved the entry stays exactly as short as it was. Read only through `resolveEntrySurface`
2718
- * (`entrySurface.ts`), which also says what a pointer that has lost its page falls back to.
2719
- *
2720
- * A pointer rather than an id swap: a page's id is what its blueprints are filed under and what
2721
- * every `Go Page` names, so making another page the entry must not change which id it has.
2722
- *
2723
- * Carries no schema bump, for the reason `actions` and `structs` did not: absence already means a
2724
- * defined answer, so a document written before this field reads correctly without one.
2725
- */
2726
- entrySurfaceId?: UISurfaceId;
2727
- meta?: UIDocumentMeta;
2728
- };
2729
- type UIDocumentMeta = {
2730
- createdAt?: string;
2731
- updatedAt?: string;
2732
- };
2733
- type UIHost = "app" | "player";
2734
- type UISurfaceKind = "appSurface" | "stageSurface";
2735
- type UIStageSlotId = "onStage" | "dialog" | "notification" | "choice" | "nvl";
2736
- type UIStageSurfaceMount = {
2737
- kind: "slot";
2738
- slotId: UIStageSlotId;
2739
- };
2740
- type UIAppSurface = {
2741
- id: UISurfaceId;
2742
- name: string;
2743
- host: "app";
2744
- kind: "appSurface";
2745
- designSize: UISurfaceDesignSize;
2746
- rootElementId: UIElementId;
2747
- settings?: UISurfaceSettings;
2748
- /**
2749
- * Which of the project's actions this surface answers. An id here names an entry of
2750
- * {@link UIDocument.actions}; absent means it answers none of them.
2751
- *
2752
- * Input that lands on this surface's content stops here whether or not an action answered it,
2753
- * unless the action that answered says otherwise. There is no surface-wide setting for that: a
2754
- * surface drawn over another is drawn over it, and an action is the only thing that knows
2755
- * whether what it just did leaves anything for the surface behind to do.
2756
- */
2757
- actions?: UISurfaceActionEnablement[];
2758
- /** The values this page is opened with, in author order. See {@link UIPageParam}. */
2759
- params?: UIPageParam[];
2760
- };
2761
- type UIPageParam = {
2762
- id: string;
2763
- name: string;
2764
- type: UIPageParamType;
2765
- /**
2766
- * For a `list`: the shape each row has, as a struct id (an engine shape such as `nl.confirmButton`,
2767
- * a plugin's, or one of the project's). It types the value on both ends - the inputs that give it
2768
- * and `Get Page Param` - and a list on the page bound to it is checked against it. Absent for
2769
- * every other type, and for a list of rows of no declared shape.
2770
- */
2771
- struct?: string;
2772
- /**
2773
- * What the page reads when whoever opened it gave nothing for this name, in the declared type.
2774
- * Absent reads as the type's empty value (`uiPageParamDefaultValue`).
2775
- */
2776
- defaultValue?: unknown;
2777
- };
2778
- type UIPageParamType = "string" | "text" | "number" | "boolean" | "list" | "json";
2779
- type UIStageSurface = {
2780
- id: UISurfaceId;
2781
- name: string;
2782
- host: "player";
2783
- kind: "stageSurface";
2784
- designSize: UISurfaceDesignSize;
2785
- rootElementId: UIElementId;
2786
- settings?: UISurfaceSettings;
2787
- mount: UIStageSurfaceMount;
2788
- slots?: Record<string, UISlotDefinition>;
2789
- /** @see UIAppSurface.actions */
2790
- actions?: UISurfaceActionEnablement[];
2791
- };
2792
- type UISurface = UIAppSurface | UIStageSurface;
2793
- type UISurfaceDesignSize = {
2794
- width: number;
2795
- height: number;
2796
- };
2797
- type UISurfaceSettings = {
2798
- backgroundColor?: string;
2799
- /**
2800
- * Painted over {@link backgroundColor}, inside the design area only - the letterbox bars around
2801
- * a scaled Surface keep showing the colour, so the two settings are not alternatives.
2802
- */
2803
- backgroundImage?: UISurfaceBackgroundImage;
2804
- pageAnimation?: UIPageAnimationSettings;
2805
- /**
2806
- * What the asset set this Surface's own background names resolves to, per locale. Build-written
2807
- * exactly as {@link UIElement.assetVariants} is, and here rather than on the Surface so the
2808
- * answer sits beside the only asset a Surface names by itself.
2809
- */
2810
- assetVariants?: AssetVariantMap;
2811
- };
2812
- type UISlotDefinition = {
2813
- id: string;
2814
- name: string;
2815
- rootElementId?: UIElementId;
2816
- };
2817
- type UIComponentId = string;
2818
- type UIComponentDefinition = {
2819
- id: UIComponentId;
2820
- name: string;
2821
- rootElementId: UIElementId;
2822
- elements: Record<UIElementId, UIElement>;
2823
- /** Declared in the component's own inspector; each instance supplies its own values. */
2824
- params?: UIComponentParam[];
2825
- previewMeta?: {
2826
- width?: number;
2827
- height?: number;
2828
- };
2829
- createdAt?: string;
2830
- updatedAt?: string;
2831
- };
2832
- type UIComponentParam = {
2833
- /** Stable identity. Blueprints and instance values reference this, so renaming `name` is free. */
2834
- id: string;
2835
- /** What the author called it, shown in the instance's inspector. */
2836
- name: string;
2837
- type: UIComponentParamType;
2838
- /**
2839
- * Used by an instance that has not overridden it, and by the definition's own preview. For a
2840
- * `text` parameter these are words written directly, translated through the definition's own unit
2841
- * (`uiComponentParamUnitId(componentId, paramId)`).
2842
- */
2843
- defaultValue: string;
2844
- };
2845
- type UIComponentParamType = "string" | "text";
2846
- type UIElement = {
2847
- id: UIElementId;
2848
- type: string;
2849
- name?: string;
2850
- parentId: UIElementId | null;
2851
- childrenIds: UIElementId[];
2852
- layout: UILayout;
2853
- style?: UIStyle;
2854
- props?: Record<string, unknown>;
2855
- valueBindings?: Record<string, UIElementValueBinding>;
2856
- /**
2857
- * How this element arrives and leaves - the same record a Surface uses for its page animation.
2858
- * Absent means it does neither. Read it through `elementAnimation.ts` rather than directly.
2859
- *
2860
- * Its own field rather than a `props` key, because it belongs to the placement and not to the
2861
- * widget: every type has one, no renderer reads it, and a linked component instance may set it
2862
- * even though its props come from the definition.
2863
- */
2864
- animation?: UIPageAnimationSettings;
2865
- extra?: Record<string, unknown>;
2866
- /**
2867
- * What each asset set THIS element names resolves to, per locale.
2868
- *
2869
- * **Never authored and never on disk under `editor/`.** It is written while a package is being
2870
- * assembled (`@shared/build/uiAssetSets`) and exists only in the bundle a game runs from, which
2871
- * is why editing a widget cannot produce one and why version control never sees it.
2872
- *
2873
- * It sits on the element rather than in a document-wide table for the reason the story row's
2874
- * copy gives: a table would enumerate every localized asset the project has, which is the one
2875
- * thing a protected pack refuses to disclose. One element discloses the variants of the sets
2876
- * that element itself uses, which is what drawing the widget already discloses.
2877
- *
2878
- * Keyed by the set id the props still name - the prop keeps the set id, so a widget whose map is
2879
- * missing fails loudly at a resolver rather than quietly fetching one language's picture.
2880
- */
2881
- assetVariants?: AssetVariantMap;
2882
- };
2883
- type UIElementValueBindingValueType = "string" | "json" | "float" | "boolean";
2884
- type UIElementValueBinding = {
2885
- kind: "blueprintValue";
2886
- blueprintId: string;
2887
- valueType: UIElementValueBindingValueType;
2888
- }
2889
- /**
2890
- * Read one declared field of the list item this element is being drawn for.
2891
- *
2892
- * The whole reason the struct model exists. Reaching a row's own data used to take a private
2893
- * blueprint per element - Get List Item Props into Get JSON Field with the key typed into a
2894
- * string pin - which is three abstractions deep for "show the title". This is the same read with
2895
- * none of them: the field is picked from a dropdown, and an element outside an item template
2896
- * simply has nothing to bind to.
2897
- *
2898
- * No `valueType` of its own. The field declares one and the bound prop declares one, and the
2899
- * conversion between them belongs to the target, not to the binding - the same reason the
2900
- * slider's binding does not restate that a slider holds a number.
2901
- */
2902
- | {
2903
- kind: "listItemField";
2904
- fieldId: string;
2905
- }
2906
- /**
2907
- * Show the value one placement gives a text parameter of the component this element is part of.
2908
- *
2909
- * The component's counterpart of `listItemField`, and the same kind of read: no graph, nothing
2910
- * kept alive - the value is taken from the placement being drawn (`resolveUIComponentTextParams`),
2911
- * so the editing canvas draws each placement with its own words, and the game translates each
2912
- * through the placement's own unit or reads the key it names. Only a text parameter answers one,
2913
- * and only a widget's words take one; outside a placement (the component's own editor) the
2914
- * element's own words are drawn, as sample text.
2915
- */
2916
- | {
2917
- kind: "componentParam";
2918
- paramId: string;
2919
- }
2920
- /**
2921
- * Show the value the page this element is on was opened with for one of its text parameters.
2922
- *
2923
- * The page's counterpart of `componentParam`, read the same way: no graph - the value is taken from
2924
- * the props the page was opened with (`resolveUIPageTextParams`), words given at the opening shown
2925
- * as given, the declared default translated through the page's own unit. Only a text parameter
2926
- * answers one, only a widget's words take one, and only on a page: a component's insides and a
2927
- * Game UI have no page parameters to show. The editing canvas, where the page has not been opened,
2928
- * draws the default, or the element's own words as sample text while the default is empty.
2929
- */
2930
- | {
2931
- kind: "pageParam";
2932
- paramId: string;
2933
- };
2934
- type UILayout = {
2935
- x: number;
2936
- y: number;
2937
- width: number;
2938
- height: number;
2939
- rotation?: number;
2940
- opacity?: number;
2941
- visible?: boolean;
2942
- /** When true, width and height stay proportional in the inspector and on-canvas resize. */
2943
- lockAspectRatio?: boolean;
2944
- };
2945
- type UIStyle = Record<string, unknown>;
2946
- type GameTestLogLevel = "verbose" | "info" | "success" | "warning" | "error";
2947
- type GameTestExitReason =
2948
- /** Closed from inside the game - the window's own close, or the engine quitting itself. */
2949
- "closed-by-user"
2950
- /** The host asked: an explicit stop, or the run being cancelled. */
2951
- | "stopped-by-host"
2952
- /** Non-zero exit code, a fatal signal, or an uncaught exception in the game's main process. */
2953
- | "crashed"
2954
- /** Never got far enough to run: compile failed, or the runner binary would not spawn. */
2955
- | "failed-to-start";
2956
- type GameTestExit = {
2957
- reason: GameTestExitReason;
2958
- code: number | null;
2959
- signal: string | null;
2960
- };
2961
- type GameTestChoiceOption = {
2962
- index: number;
2963
- text: string;
2964
- /** Offered but not selectable: the option's `disabledWhen` condition holds. */
2965
- disabled: boolean;
2966
- };
2967
- type GameTestEvent = {
2968
- kind: "console";
2969
- level: GameTestLogLevel;
2970
- source: string;
2971
- message: string;
2972
- }
2973
- /**
2974
- * An uncaught error inside the running game - `window.onerror` / `unhandledrejection` in its
2975
- * renderer, `uncaughtException` / `unhandledRejection` in its main process. The game runtime had
2976
- * no such hook before this pipeline; an uncaught renderer exception was reported nowhere.
2977
- */
2978
- | {
2979
- kind: "runtime-error";
2980
- scope: "renderer" | "main";
2981
- message: string;
2982
- stack?: string;
2983
- }
2984
- /** The engine reached an ending (`event:state.end`), forwarded out of the game's renderer. */
2985
- | {
2986
- kind: "game-end";
2987
- }
2988
- /**
2989
- * An `/ending` row ran, naming the ending it declares.
2990
- *
2991
- * Beside `game-end` rather than replacing it: `game-end` also fires for a story that simply ran
2992
- * out of rows, which has no ending to name. A test whose pass condition is "*this* ending" can
2993
- * only be written against this one.
2994
- */
2995
- | {
2996
- kind: "ending";
2997
- endingId: string;
2998
- name: string;
2999
- }
3000
- /**
3001
- * A choice menu is on screen, with every option it evaluated.
3002
- *
3003
- * Fires each time a menu mounts, so a rollback into the same choice reports it again. The list
3004
- * is what `choose` addresses; see {@link GameTestChoiceOption}.
3005
- */
3006
- | {
3007
- kind: "choice";
3008
- options: GameTestChoiceOption[];
3009
- } | {
3010
- kind: "exit";
3011
- exit: GameTestExit;
3012
- };
3013
- type GameTestCommand =
3014
- /**
3015
- * Begin a story at a scene.
3016
- *
3017
- * Necessary because a test session boots to the main app surface - the title screen - and no
3018
- * story runs until something starts one. The caller supplies the target, exactly as the
3019
- * blueprint behind a Start button does.
3020
- */
3021
- {
3022
- kind: "start";
3023
- storyId: string;
3024
- sceneId: string;
3025
- }
3026
- /** One click on the dialogue: finish the line being typed, or move to the next. */
3027
- | {
3028
- kind: "advance";
3029
- }
3030
- /** Pick an option of the choice currently on screen, by the index the `choice` event reported. */
3031
- | {
3032
- kind: "choose";
3033
- index: number;
3034
- };
3035
- type UITemplateSurfacePlacement = {
3036
- kind: UISurfaceKind;
3037
- /** Required for `stageSurface`; the game-UI slot the surface mounts into. */
3038
- slotId?: UIStageSlotId;
3039
- };
3040
- interface AssetTransferManifestEntry {
3041
- /**
3042
- * The asset's id in the source project, which the importing project reuses.
3043
- *
3044
- * Reusing it is what lets the rows that travelled alongside keep pointing at their files
3045
- * without being rewritten, and it makes a second paste of the same clipboard a no-op: the id
3046
- * is already in the library, so there is nothing to import.
3047
- */
3048
- assetId: string;
3049
- /** Author-facing file name, extension included: what the file is called once imported. */
3050
- fileName: string;
3051
- /**
3052
- * The `AssetType` value as a string.
3053
- *
3054
- * Structural for the reason `assetSet.ts` gives for the same field: the enum lives under the
3055
- * renderer and cannot be imported here.
3056
- */
3057
- type: string;
3058
- /** Size in bytes, when the offering side already knew it. */
3059
- size?: number;
3060
- /**
3061
- * Whether the payload is a directory rather than a single file.
3062
- *
3063
- * True for model bundles, the one asset type whose contents are a tree. It decides both halves
3064
- * of the transfer: the grant covering the entry reaches everything below the path, and the
3065
- * importing project copies the tree instead of writing bytes.
3066
- */
3067
- isDirectory?: boolean;
3068
- }
3069
1739
  declare const en: {
3070
1740
  readonly common: {
3071
1741
  readonly appName: "NarraLeaf Studio";
@@ -3152,6 +1822,16 @@ declare const en: {
3152
1822
  readonly other: "{count} info";
3153
1823
  };
3154
1824
  };
1825
+ readonly flowCanvas: {
1826
+ readonly nodeHelp: "Press Enter or Space to select a node. Press Delete to remove it and Escape to cancel.";
1827
+ readonly movableNodeHelp: "Press Enter or Space to select a node, then move it with the arrow keys. Press Delete to remove it and Escape to cancel.";
1828
+ readonly wireHelp: "Press Enter or Space to select a wire. Press Delete to remove it and Escape to cancel.";
1829
+ readonly nodeMoved: "Moved the selected node {direction}. Position x {x}, y {y}";
1830
+ readonly up: "up";
1831
+ readonly down: "down";
1832
+ readonly left: "left";
1833
+ readonly right: "right";
1834
+ };
3155
1835
  };
3156
1836
  readonly menu: {
3157
1837
  readonly app: {
@@ -3995,6 +2675,10 @@ declare const en: {
3995
2675
  readonly characterSpeaker: "Character";
3996
2676
  readonly scenesGroup: "Scenes";
3997
2677
  readonly sceneSpeaker: "Scene";
2678
+ readonly endingsGroup: "Endings";
2679
+ readonly endingSpeaker: "Ending";
2680
+ readonly renamesGroup: "Renames";
2681
+ readonly renameSpeaker: "Rename";
3998
2682
  readonly addKey: "Add";
3999
2683
  readonly keyNamePlaceholder: "Key (menu.start\u2026)";
4000
2684
  readonly keySourcePlaceholder: "Source text";
@@ -4037,6 +2721,24 @@ declare const en: {
4037
2721
  readonly statusReviewed: "Reviewed";
4038
2722
  readonly statusStale: "To review";
4039
2723
  readonly findPlaceholder: "Find in translations";
2724
+ readonly orphans: {
2725
+ readonly source: "Orphan translations ({count})";
2726
+ readonly summary: {
2727
+ readonly one: "{count} translation belongs to a line that is not in the game";
2728
+ readonly other: "{count} translations belong to lines that are not in the game";
2729
+ };
2730
+ readonly none: "No orphan translations";
2731
+ readonly disabledLine: "Disabled line";
2732
+ readonly deletedLine: "Deleted line";
2733
+ readonly delete: "Delete";
2734
+ readonly deleteAll: "Delete all";
2735
+ readonly deleteConfirm: {
2736
+ readonly one: "Delete {count} orphan translation?";
2737
+ readonly other: "Delete {count} orphan translations?";
2738
+ };
2739
+ readonly deleteDetail: "This cannot be undone. A line that is enabled or restored later needs translating again.";
2740
+ readonly unknown: "A story cannot be read, so orphan translations are not listed.";
2741
+ };
4040
2742
  };
4041
2743
  readonly live: {
4042
2744
  readonly entryClaimed: "{name} is translating this line";
@@ -4128,6 +2830,21 @@ declare const en: {
4128
2830
  readonly notePlaceholder: "Note\u2026";
4129
2831
  readonly dropHint: "Drop audio to assign";
4130
2832
  readonly findPlaceholder: "Find in lines";
2833
+ readonly orphans: {
2834
+ readonly source: "Orphan voice ({count})";
2835
+ readonly summary: {
2836
+ readonly one: "{count} recording is linked to a line that is not in the game";
2837
+ readonly other: "{count} recordings are linked to lines that are not in the game";
2838
+ };
2839
+ readonly none: "No orphan voice";
2840
+ readonly removeAll: "Remove all";
2841
+ readonly removeConfirm: {
2842
+ readonly one: "Remove {count} orphan recording?";
2843
+ readonly other: "Remove {count} orphan recordings?";
2844
+ };
2845
+ readonly removeDetail: "The audio files stay in the asset library. A line that is enabled or restored later needs its audio assigned again.";
2846
+ readonly unknown: "A story cannot be read, so orphan voice is not listed.";
2847
+ };
4131
2848
  };
4132
2849
  };
4133
2850
  readonly textSourceMigration: {
@@ -6086,8 +4803,12 @@ declare const en: {
6086
4803
  readonly title: "Live Preview";
6087
4804
  readonly dock: "Dock in the scene editor";
6088
4805
  readonly pip: "Picture-in-picture";
4806
+ readonly skipTyping: "Skip typing animation";
6089
4807
  readonly selectRow: "Select a story row to preview its stage state.";
6090
4808
  readonly failed: "Preview failed";
4809
+ readonly settleTimeout: "The stage did not finish setting up in time.";
4810
+ readonly gameUiBlueprintError: "Game UI blueprint error: {message}";
4811
+ readonly previousSessionError: "Previous preview session: {message}";
6091
4812
  readonly playFromHere: "Play from here";
6092
4813
  readonly restart: "Restart";
6093
4814
  readonly stop: "Stop playback";
@@ -6537,6 +5258,7 @@ declare const en: {
6537
5258
  readonly parallel: "Parallel";
6538
5259
  readonly race: "Race, first to finish";
6539
5260
  readonly sequence: "In order";
5261
+ readonly noWait: "Doesn't wait";
6540
5262
  readonly nvl: "NVL";
6541
5263
  readonly menu: "Menu";
6542
5264
  readonly option: "Option";
@@ -6623,6 +5345,7 @@ declare const en: {
6623
5345
  readonly vfx: "{operation} ambience {name}";
6624
5346
  readonly nvl: "NVL block";
6625
5347
  readonly blueprint: "Blueprint";
5348
+ readonly blueprintNamed: "Blueprint {name}";
6626
5349
  readonly pluginAction: "Plugin action";
6627
5350
  readonly cameraOp: {
6628
5351
  readonly transform: "Camera";
@@ -7222,6 +5945,7 @@ declare const en: {
7222
5945
  };
7223
5946
  readonly blueprint: {
7224
5947
  readonly storyActionTitle: "Story Action";
5948
+ readonly name: "Blueprint name";
7225
5949
  };
7226
5950
  readonly audio: {
7227
5951
  readonly track: "Track";
@@ -7327,13 +6051,9 @@ declare const en: {
7327
6051
  readonly parallel: "Parallel";
7328
6052
  readonly race: "Race";
7329
6053
  readonly repeat: "Repeat";
7330
- readonly mode: {
7331
- readonly do: "Do";
7332
- readonly doAsync: "Do async";
7333
- readonly all: "All";
7334
- readonly allAsync: "All async";
7335
- readonly any: "Any";
7336
- };
6054
+ readonly rowsAfter: "Rows after";
6055
+ readonly rowsAfterWait: "Start when the group finishes";
6056
+ readonly rowsAfterStart: "Start with the group";
7337
6057
  readonly times: "Times";
7338
6058
  readonly loopKind: "Loop";
7339
6059
  readonly loopKindTimes: "A number of times";
@@ -8082,6 +6802,34 @@ declare const en: {
8082
6802
  readonly language: "Language";
8083
6803
  readonly voiceLanguage: "Voice language";
8084
6804
  };
6805
+ readonly valueType: {
6806
+ readonly inline: "{id}";
6807
+ readonly string: "String";
6808
+ readonly integer: "Integer";
6809
+ readonly float: "Float";
6810
+ readonly number: "Number";
6811
+ readonly boolean: "Boolean";
6812
+ readonly json: "JSON";
6813
+ readonly array: "Array";
6814
+ readonly list: "List";
6815
+ readonly any: "Any";
6816
+ readonly text: "Text";
6817
+ readonly audioTrack: "Audio track";
6818
+ readonly image: "Image";
6819
+ readonly imageAsset: "ImageAsset";
6820
+ readonly imageAssetOrNull: "ImageAsset|null";
6821
+ readonly color: "Color";
6822
+ readonly rgbaColor: "RGBAColor";
6823
+ readonly vector2D: "Vector2D";
6824
+ readonly rect: "Rect";
6825
+ readonly timer: "Timer";
6826
+ readonly animationToken: "AnimationToken";
6827
+ readonly soundHandle: "SoundHandle";
6828
+ readonly responseBody: "ResponseBody";
6829
+ readonly saveSlot: "SaveSlot";
6830
+ readonly element: "Element";
6831
+ readonly elementOf: "{widget} element";
6832
+ };
8085
6833
  readonly diagnostics: {
8086
6834
  readonly empty: "No diagnostics.";
8087
6835
  readonly summary: "Messages \u00B7 {errors}E \u00B7 {warnings}W \u00B7 {infos}I";
@@ -8127,9 +6875,9 @@ declare const en: {
8127
6875
  };
8128
6876
  readonly graph: {
8129
6877
  readonly noNodes: "Graph has no nodes yet.";
8130
- readonly pinMultipleInput: "Pin {node}.{port} has multiple input connections; a pin takes one connection.";
8131
- readonly pinMultipleOutput: "Pin {node}.{port} has multiple output connections; a pin takes one connection.";
8132
- readonly entryMissingNode: "Entry points to missing node \"{node}\".";
6878
+ readonly pinMultipleInput: "Pin \"{pin}\" has multiple input connections; a pin takes one connection.";
6879
+ readonly pinMultipleOutput: "Pin \"{pin}\" has multiple output connections; a pin takes one connection.";
6880
+ readonly entryMissingNode: "The entry points to a node that does not exist.";
8133
6881
  readonly entryInvalid: "Function entry not found.";
8134
6882
  };
8135
6883
  readonly event: {
@@ -8142,21 +6890,21 @@ declare const en: {
8142
6890
  };
8143
6891
  readonly edge: {
8144
6892
  readonly selfConnection: "Node \"{node}\" cannot connect to itself.";
8145
- readonly fromUnknown: "Missing source node \"{node}\".";
8146
- readonly toUnknown: "Missing target node \"{node}\".";
6893
+ readonly fromUnknown: "A wire starts at a node that does not exist.";
6894
+ readonly toUnknown: "A wire ends at a node that does not exist.";
8147
6895
  readonly portMismatch: "Pin mismatch on {from} \u2192 {to}.";
8148
6896
  readonly connectionInvalid: "Invalid connection {from} -> {to}.{detail}";
8149
6897
  readonly connectionTypeDetail: " Type mismatch: {from} -> {to}.";
8150
6898
  };
8151
6899
  readonly node: {
8152
- readonly contextInvalid: "Node \"{name}\" is not allowed in this {ownerKind} {graphKind} graph.{hint}";
6900
+ readonly contextInvalid: "Node \"{name}\" is not allowed in {owner}.{hint}";
8153
6901
  readonly contextValueReturnHint: " Return Value only belongs in Blueprint Value graphs.";
8154
6902
  readonly contextListItemHint: " It reads the list row it runs for, and nothing here draws one.";
8155
- readonly noRuntime: "Node \"{node}\": no runtime for type \"{type}\".";
6903
+ readonly noRuntime: "Node \"{node}\" has no runtime.";
8156
6904
  readonly inputMissing: "\"{node}\" has nothing connected to \"{pin}\".";
8157
6905
  readonly stepLimit: "\"{head}\" stopped at \"{node}\" after {steps} steps without a wait.";
8158
6906
  readonly stepLimitGlobal: "\"{head}\" in App logic stopped at \"{node}\" after {steps} steps without a wait.";
8159
- readonly unknownType: "Node \"{node}\": unknown type \"{type}\". Its plugin may be uninstalled or disabled.";
6907
+ readonly unknownType: "Unknown node type \"{type}\". Its plugin may be uninstalled or disabled.";
8160
6908
  readonly variableIdInvalid: "Node \"{node}\": pick a variable.";
8161
6909
  readonly persistentVariableIdInvalid: "Node \"{node}\": pick a persistent variable.";
8162
6910
  readonly savedVariableIdInvalid: "Node \"{node}\": pick a saved variable.";
@@ -8167,11 +6915,11 @@ declare const en: {
8167
6915
  readonly pageParamMissing: "\"{node}\": the page does not declare this param.";
8168
6916
  };
8169
6917
  readonly fn: {
8170
- readonly nameMissing: "Fn \"{node}\": set a function name.";
6918
+ readonly nameMissing: "\"{node}\": set a function name.";
8171
6919
  readonly duplicateName: "Fn name \"{name}\" is used by another function in scope.";
8172
6920
  readonly returnOrphan: "Fn Return must be reachable from a Fn head.";
8173
6921
  readonly returnMultipleHeads: "Fn Return is reachable from multiple Fn heads.";
8174
- readonly returnSignatureConflict: "Fn Return \"{node}\" declares different results than the first Return of this fn.";
6922
+ readonly returnSignatureConflict: "\"{node}\" declares different results than the first Return of this fn.";
8175
6923
  readonly callUnset: "Node \"{node}\": pick a function.";
8176
6924
  readonly callTargetNotFound: "Fn \"{name}\" does not exist in this scope.";
8177
6925
  readonly callSignatureStale: "Function signature changed; re-select \"{name}\" to update its pins.";
@@ -8399,6 +7147,10 @@ declare const en: {
8399
7147
  readonly rawJson: "Raw JSON";
8400
7148
  readonly root: "Root";
8401
7149
  readonly edit: "Edit JSON";
7150
+ readonly itemCount: {
7151
+ readonly one: "{count} item";
7152
+ readonly other: "{count} items";
7153
+ };
8402
7154
  };
8403
7155
  readonly audio: {
8404
7156
  readonly select: "Select audio";
@@ -8438,6 +7190,8 @@ declare const en: {
8438
7190
  readonly removeOutput: "Remove output pin";
8439
7191
  readonly jsonFieldName: "JSON object field name";
8440
7192
  readonly readsRow: "Current row";
7193
+ readonly newParamName: "param{n}";
7194
+ readonly newResultName: "result{n}";
8441
7195
  };
8442
7196
  readonly comment: {
8443
7197
  readonly color: {
@@ -9937,8 +8691,8 @@ declare const en: {
9937
8691
  readonly namePlaceholder: "Name";
9938
8692
  readonly defaultPlaceholder: "Default";
9939
8693
  readonly type: "Type";
9940
- readonly typeString: "String";
9941
- readonly typeText: "Text";
8694
+ readonly trackMissing: "Missing track";
8695
+ readonly trackNone: "No track";
9942
8696
  readonly bindLabel: "Parameter";
9943
8697
  readonly bindNone: "No parameter";
9944
8698
  readonly bindMissing: "Missing parameter";
@@ -9957,12 +8711,6 @@ declare const en: {
9957
8711
  readonly namePlaceholder: "Name";
9958
8712
  readonly default: "Default";
9959
8713
  readonly type: "Type";
9960
- readonly typeString: "String";
9961
- readonly typeText: "Text";
9962
- readonly typeNumber: "Number";
9963
- readonly typeBoolean: "Boolean";
9964
- readonly typeList: "List";
9965
- readonly typeJson: "JSON";
9966
8714
  };
9967
8715
  readonly binding: {
9968
8716
  readonly notReady: "Blueprint not ready for this control.";
@@ -10046,6 +8794,7 @@ declare const en: {
10046
8794
  readonly unknown: "Usage could not be determined";
10047
8795
  readonly unknownDetail: "{location} could not be read";
10048
8796
  readonly unknownComputed: "{location} picks its asset by a name assembled at run time";
8797
+ readonly defaultFonts: "Default fonts";
10049
8798
  readonly count: {
10050
8799
  readonly one: "{count} reference";
10051
8800
  readonly other: "{count} references";
@@ -10343,14 +9092,6 @@ declare const en: {
10343
9092
  readonly saveSchema: {
10344
9093
  readonly title: "Save fields";
10345
9094
  readonly open: "Save fields";
10346
- readonly type: {
10347
- readonly string: "String";
10348
- readonly integer: "Integer";
10349
- readonly float: "Float";
10350
- readonly boolean: "Boolean";
10351
- readonly json: "JSON";
10352
- readonly array: "Array";
10353
- };
10354
9095
  readonly field: {
10355
9096
  readonly name: "Name";
10356
9097
  readonly type: "Type";
@@ -10362,14 +9103,6 @@ declare const en: {
10362
9103
  };
10363
9104
  };
10364
9105
  readonly struct: {
10365
- readonly type: {
10366
- readonly string: "String";
10367
- readonly number: "Number";
10368
- readonly boolean: "Boolean";
10369
- readonly image: "Image";
10370
- readonly color: "Color";
10371
- readonly json: "JSON";
10372
- };
10373
9106
  readonly field: {
10374
9107
  readonly name: "Name";
10375
9108
  readonly type: "Type";
@@ -10403,18 +9136,13 @@ declare const en: {
10403
9136
  };
10404
9137
  };
10405
9138
  readonly storyVars: {
10406
- readonly valueType: {
10407
- readonly boolean: "Boolean";
10408
- readonly number: "Number";
10409
- readonly string: "String";
10410
- readonly json: "JSON";
10411
- };
10412
9139
  readonly value: {
10413
9140
  readonly true: "True";
10414
9141
  readonly false: "False";
10415
9142
  };
10416
9143
  readonly row: {
10417
9144
  readonly nameAria: "Variable name";
9145
+ readonly defaultName: "Variable";
10418
9146
  readonly defaultPlaceholder: "default";
10419
9147
  readonly defaultAria: "Default value";
10420
9148
  readonly delete: "Delete variable";
@@ -10627,6 +9355,9 @@ declare const en: {
10627
9355
  readonly canvas: {
10628
9356
  readonly unknownWidget: "Unknown widget";
10629
9357
  readonly widgetRenderFailed: "Widget failed to draw";
9358
+ readonly componentMissing: "Missing component";
9359
+ readonly componentLoopBlocked: "Component loop blocked";
9360
+ readonly componentRootMissing: "Component root missing";
10630
9361
  readonly wordsFromRowField: "These words come from the list row field \u201C{field}\u201D.";
10631
9362
  readonly wordsFromBlueprintValue: "These words come from the blueprint value \u201C{name}\u201D.";
10632
9363
  readonly wordsFromComponentParam: "These words come from the component parameter \u201C{param}\u201D, set on each placement.";
@@ -11814,6 +10545,8 @@ declare const en: {
11814
10545
  readonly ending: {
11815
10546
  readonly title: "Page shown when the story ends";
11816
10547
  readonly none: "Show nothing";
10548
+ readonly unset: "Not chosen";
10549
+ readonly missing: "Deleted page";
11817
10550
  };
11818
10551
  readonly usedBy: {
11819
10552
  readonly one: "Used by {count} reference";
@@ -12017,6 +10750,11 @@ declare const en: {
12017
10750
  readonly one: "{count} track under it moves to {parent}.";
12018
10751
  readonly other: "{count} tracks under it move to {parent}.";
12019
10752
  };
10753
+ readonly builtinName: {
10754
+ readonly bgm: "Music";
10755
+ readonly sound: "SFX";
10756
+ readonly voice: "Voice";
10757
+ };
12020
10758
  readonly slider: {
12021
10759
  readonly bgm: "Music volume";
12022
10760
  readonly sound: "SFX volume";
@@ -13394,6 +12132,7 @@ declare const en: {
13394
12132
  readonly contentLabel: "Variant the content comes from";
13395
12133
  readonly contentHint: "The scenes, variant conditions and art of this variant go into the patch.";
13396
12134
  readonly sameVariant: "The content and the build it updates are the same variant. The patch carries no changes.";
12135
+ readonly sameProject: "The build it updates is made from the current project. The patch carries no changes. For a build already released, choose An existing build folder.";
13397
12136
  readonly outputLabel: "Save as";
13398
12137
  readonly nameLabel: "Name";
13399
12138
  readonly namePlaceholder: "Shown in the game's log";
@@ -14352,6 +13091,7 @@ declare const en: {
14352
13091
  };
14353
13092
  readonly message: {
14354
13093
  readonly ruleFailed: "{rule} could not run";
13094
+ readonly listSeparator: ", ";
14355
13095
  readonly storyLoadFailed: "{story} could not be opened";
14356
13096
  readonly storyTooOld: "{story} is in story format v{version}, and this Studio opens v{minimum} and later";
14357
13097
  readonly storyTooNew: "{story} was written by a newer NarraLeaf Studio (story format v{version}); this Studio reads up to v{supported}";
@@ -15238,13 +13978,14 @@ declare const en: {
15238
13978
  readonly finding: {
15239
13979
  readonly sceneUnreachable: "No path can satisfy the conditions leading to \"{scene}\"";
15240
13980
  readonly optionUnreachable: "No path can satisfy the condition for offering \"{option}\"";
13981
+ readonly conditionUnreachable: "No path can satisfy \"{condition}\"";
15241
13982
  readonly branchUnreachable: "No path can satisfy the condition for taking this branch";
15242
13983
  readonly endingUnreachable: "\"{name}\" is written but no path can satisfy the conditions to reach it";
15243
13984
  readonly endingUnreachableUnnamed: "This ending is written but no path can satisfy the conditions to reach it";
15244
13985
  };
15245
13986
  readonly summary: {
15246
13987
  readonly passed: "Everything the script leads to can be reached";
15247
- readonly failed: "Unreachable: scenes {scenes}, options {options}, endings {endings}";
13988
+ readonly failed: "Unreachable: scenes {scenes}, options {options}, condition branches {conditions}, endings {endings}";
15248
13989
  };
15249
13990
  };
15250
13991
  readonly reachableEndings: {
@@ -15595,15 +14336,27 @@ declare const en: {
15595
14336
  readonly checking: "Checking for updates\u2026";
15596
14337
  readonly available: "Version {version} is available.";
15597
14338
  readonly downloading: "Downloading version {version}\u2026";
14339
+ readonly preparing: "Preparing version {version}\u2026";
15598
14340
  readonly ready: "Version {version} is ready to install.";
14341
+ readonly readyFast: "Version {version} is ready.";
15599
14342
  readonly error: "Could not check for updates.";
14343
+ readonly failed: "Version {version} could not be downloaded.";
15600
14344
  readonly manual: "Version {version} is available to download.";
15601
14345
  };
14346
+ readonly errors: {
14347
+ readonly connection: "Could not reach the download server ({reason}).";
14348
+ };
15602
14349
  readonly versions: "Installed {current}";
14350
+ readonly readyHint: "Also applied when Studio quits.";
14351
+ readonly indicator: {
14352
+ readonly label: "Software update";
14353
+ };
15603
14354
  readonly actions: {
15604
14355
  readonly check: "Check for Updates";
15605
14356
  readonly download: "Download Update";
15606
14357
  readonly install: "Restart and Install";
14358
+ readonly restart: "Restart to Update";
14359
+ readonly cancel: "Cancel Update";
15607
14360
  readonly releaseNotes: "Release notes";
15608
14361
  readonly openDownloadPage: "Open download page";
15609
14362
  };
@@ -15614,17 +14367,25 @@ declare const en: {
15614
14367
  };
15615
14368
  readonly setting: {
15616
14369
  readonly checkOnLaunch: {
15617
- readonly label: "Check for updates at launch";
15618
- readonly description: "Asks GitHub once, shortly after Studio starts. Downloads never begin on their own.";
14370
+ readonly label: "Check for updates automatically";
14371
+ readonly description: "Asks GitHub shortly after Studio starts, and every few hours while it runs.";
14372
+ };
14373
+ readonly autoDownload: {
14374
+ readonly label: "Download updates automatically";
14375
+ readonly description: "A new version is downloaded and prepared in the background. Restarting Studio applies it.";
15619
14376
  };
15620
14377
  };
15621
14378
  readonly notification: {
15622
14379
  readonly message: "NarraLeaf Studio {version} is available";
15623
14380
  readonly detail: "You are running {current}.";
15624
14381
  readonly action: "View update";
14382
+ readonly readyMessage: "NarraLeaf Studio {version} is ready";
14383
+ readonly readyDetail: "Restart to finish updating.";
15625
14384
  };
15626
14385
  readonly launcher: {
15627
14386
  readonly available: "Update to {version}";
14387
+ readonly progress: "Updating to {version}\u2026";
14388
+ readonly ready: "Restart to update to {version}";
15628
14389
  };
15629
14390
  readonly quitPrompt: {
15630
14391
  readonly title: "Update in progress";
@@ -15677,6 +14438,19 @@ declare const en: {
15677
14438
  };
15678
14439
  readonly preview: "Preview in";
15679
14440
  readonly excluded: "Not used for {language}";
14441
+ readonly builtin: {
14442
+ readonly group: "Built-in fonts";
14443
+ readonly systemUi: "System UI";
14444
+ readonly sansSerif: "Sans-serif (generic)";
14445
+ readonly serif: "Serif (generic)";
14446
+ readonly monospace: "Monospace (generic)";
14447
+ readonly kind: {
14448
+ readonly system: "System font";
14449
+ readonly sansSerif: "Sans-serif";
14450
+ readonly serif: "Serif";
14451
+ readonly monospace: "Monospace";
14452
+ };
14453
+ };
15680
14454
  };
15681
14455
  readonly panel: {
15682
14456
  readonly add: "Add color";
@@ -15804,6 +14578,10 @@ declare const en: {
15804
14578
  readonly detail: "It is running in another browser tab. Close that tab, then reload this page.";
15805
14579
  readonly reload: "Reload";
15806
14580
  };
14581
+ readonly words: {
14582
+ readonly option: "Option";
14583
+ readonly unknownSpeaker: "Unknown";
14584
+ };
15807
14585
  };
15808
14586
  readonly crash: {
15809
14587
  readonly screen: {
@@ -15846,36 +14624,1366 @@ declare const en: {
15846
14624
  readonly quit: "Quit";
15847
14625
  };
15848
14626
  };
15849
- readonly documents: {
15850
- readonly tooNew: {
15851
- readonly message: "{subject} was written by a newer NarraLeaf Studio ({kind} format v{version}); this build reads up to v{supported}";
15852
- readonly kind: {
15853
- readonly story: "story";
15854
- readonly storyIndex: "story library";
15855
- readonly storyAnimation: "animation";
15856
- readonly uiDocument: "interface";
15857
- readonly uiGraphs: "interface blueprint";
15858
- readonly blueprints: "blueprint";
15859
- readonly variables: "variable";
15860
- readonly saveSchema: "save schema";
15861
- readonly localization: "translation";
15862
- readonly localizationKeys: "translation key";
15863
- readonly voice: "voice";
15864
- readonly brand: "design";
15865
- readonly appTags: "variant";
15866
- readonly dlc: "DLC";
15867
- readonly assetSets: "asset set";
15868
- readonly audioTracks: "audio track";
15869
- readonly characters: "character";
15870
- };
15871
- };
14627
+ readonly documents: {
14628
+ readonly tooNew: {
14629
+ readonly message: "{subject} was written by a newer NarraLeaf Studio ({kind} format v{version}); this build reads up to v{supported}";
14630
+ readonly kind: {
14631
+ readonly story: "story";
14632
+ readonly storyIndex: "story library";
14633
+ readonly storyAnimation: "animation";
14634
+ readonly uiDocument: "interface";
14635
+ readonly uiGraphs: "interface blueprint";
14636
+ readonly blueprints: "blueprint";
14637
+ readonly variables: "variable";
14638
+ readonly saveSchema: "save schema";
14639
+ readonly localization: "translation";
14640
+ readonly localizationKeys: "translation key";
14641
+ readonly voice: "voice";
14642
+ readonly brand: "design";
14643
+ readonly appTags: "variant";
14644
+ readonly dlc: "DLC";
14645
+ readonly assetSets: "asset set";
14646
+ readonly audioTracks: "audio track";
14647
+ readonly characters: "character";
14648
+ };
14649
+ };
14650
+ };
14651
+ };
14652
+ type Messages = typeof en;
14653
+ type Flatten<T, Prefix extends string = ""> = {
14654
+ [K in keyof T & string]: T[K] extends string ? `${Prefix}${K}` : Flatten<T[K], `${Prefix}${K}.`>;
14655
+ }[keyof T & string];
14656
+ type TranslationKey = Flatten<Messages> | (string & {});
14657
+ type AudioClipRegion = {
14658
+ /** Offset from the start of the clip, in milliseconds. Where playback starts. */
14659
+ inMs?: number;
14660
+ /** Offset from the start of the clip, in milliseconds. Where playback stops, or where a loop turns around. */
14661
+ outMs?: number;
14662
+ /**
14663
+ * Offset from the start of the clip, in milliseconds. Where each repeat returns to.
14664
+ *
14665
+ * Absent means "return to {@link inMs}" - a plain loop, and exactly what every record written
14666
+ * before this field existed means. Set past the in point it describes the standard VN
14667
+ * **intro→loop**: `inMs..loopStartMs` plays once, then `loopStartMs..outMs` repeats forever.
14668
+ *
14669
+ * Constrained to `inMs <= loopStartMs < outMs`. A value outside that window is dropped rather
14670
+ * than clamped: clamping would invent a loop point the author never marked, while dropping
14671
+ * degrades to the plain loop the two other markers already describe.
14672
+ */
14673
+ loopStartMs?: number;
14674
+ /**
14675
+ * Playback gain in decibels, never above 0: the engine cannot raise a clip past its own level, so
14676
+ * balancing loudness means bringing the loud clips down. Absent is unity. Folded into a volume
14677
+ * only by {@link clipVolume}.
14678
+ */
14679
+ gainDb?: number;
14680
+ };
14681
+ type StoredAudioClipRegion = Pick<AudioClipRegion, "inMs" | "outMs" | "loopStartMs">;
14682
+ type StoredAudioGain = {
14683
+ db: number;
14684
+ };
14685
+ type BlueprintOpenExternalRequest = {
14686
+ url: string;
14687
+ };
14688
+ type BlueprintOpenExternalOutcome = "opened" | "refused" | "failed";
14689
+ type BlueprintOpenExternalResult = {
14690
+ outcome: BlueprintOpenExternalOutcome;
14691
+ /** Human-readable reason, null when the page was handed over. */
14692
+ error: string | null;
14693
+ };
14694
+ type BlueprintGraphKind = "event" | "function" | "macro";
14695
+ type GameBuildPlatform = "windows" | "macos" | "linux" | "web" | "android" | "ios";
14696
+ declare const UI_STRUCT_FIELD_TYPES: readonly [
14697
+ "string",
14698
+ "number",
14699
+ "boolean",
14700
+ "image",
14701
+ "color",
14702
+ "json"
14703
+ ];
14704
+ type UIStructFieldType = (typeof UI_STRUCT_FIELD_TYPES)[number];
14705
+ type UIStructField = {
14706
+ /**
14707
+ * Stable identity. Value bindings, node params and generated pins all address a field by this,
14708
+ * so renaming `key` costs nothing and reshuffling the list costs nothing.
14709
+ */
14710
+ id: string;
14711
+ /** The property name this field occupies inside an item object. */
14712
+ key: string;
14713
+ /**
14714
+ * What the author called it, shown in the item table header and in every field picker.
14715
+ *
14716
+ * Optional because `key` is usually already the readable name; a label is what lets a field
14717
+ * whose key has to stay ASCII (it travels in JSON to a plugin, a save file, a story variable)
14718
+ * still read as words in the editor.
14719
+ */
14720
+ label?: string;
14721
+ type: UIStructFieldType;
14722
+ };
14723
+ type UIStructId = string;
14724
+ type UIStructDef = {
14725
+ id: UIStructId;
14726
+ fields: UIStructField[];
14727
+ };
14728
+ type PluginPermissionPersistence = "temporary" | "permanent";
14729
+ type PluginFileSystemPermissionMode = "read" | "write" | "readwrite";
14730
+ /**
14731
+ * Install-time permissions split into two families, and the split is the whole
14732
+ * point:
14733
+ *
14734
+ * - **Author-declared** (`filesystem`, `api`) — privileged Studio controls the
14735
+ * plugin asks for explicitly in `permissions[]`.
14736
+ * - **Derived** (`runtime`, `sidecar`, `buildDependency`, `externalLink`,
14737
+ * `network`) — computed from `contributes` by {@link validatePluginManifest}
14738
+ * and *rejected* if written by
14739
+ * hand. A capability is declared in exactly one place, so what the prompt shows
14740
+ * and what the plugin can actually reach cannot drift apart. Adding a
14741
+ * capability widens the permission set, which makes the update re-prompt for
14742
+ * free (see `isPermissionSubset`).
14743
+ */
14744
+ export type PluginInstallPermission = {
14745
+ kind: "filesystem";
14746
+ path: string;
14747
+ mode: PluginFileSystemPermissionMode;
14748
+ recursive: boolean;
14749
+ } | {
14750
+ kind: "api";
14751
+ capability: string;
14752
+ } | {
14753
+ /** Derived from `contributes.runtimeCapabilities`. */
14754
+ kind: "runtime";
14755
+ capability: PluginRuntimeCapability;
14756
+ } | {
14757
+ /** Derived from `contributes.sidecars`: a native child process shipped inside the author's game. */
14758
+ kind: "sidecar";
14759
+ id: string;
14760
+ /**
14761
+ * Which of the two sidecar shapes this is, carried through from the manifest because the
14762
+ * two are different promises: `executable` starts a separate binary, `node` runs the
14763
+ * plugin's own JavaScript under the game's Electron as Node. An author told only "this
14764
+ * ships a helper program" has not been told which one they are approving.
14765
+ *
14766
+ * Named apart from `kind` above on purpose - that one says which permission this is, this
14767
+ * one says what the sidecar runs.
14768
+ */
14769
+ sidecarKind: PluginSidecarKind;
14770
+ /** `<platform>-<arch>` keys the sidecar ships binaries for. */
14771
+ platforms: string[];
14772
+ } | {
14773
+ /** Derived from `contributes.buildDependencies`: binaries downloaded at build time. */
14774
+ kind: "buildDependency";
14775
+ id: string;
14776
+ /** Distinct hostnames the binaries are fetched from. */
14777
+ hosts: string[];
14778
+ } | {
14779
+ /**
14780
+ * Derived from `contributes.externalLinks`: addresses outside the game this plugin may open
14781
+ * in the player's browser or platform handler.
14782
+ *
14783
+ * One permission carrying every pattern rather than one permission each, because what the
14784
+ * author is deciding is a single question - "may this plugin send the player out to these
14785
+ * places" - and a prompt that asked it four times would be four chances to stop reading.
14786
+ * `isPermissionSubset` still compares the patterns one by one, so adding one to a later
14787
+ * version widens the set and re-prompts.
14788
+ *
14789
+ * The patterns are the author's own strings, unrewritten: this list is what the prompt
14790
+ * shows, and normalizing a permission before showing it to the person approving it would
14791
+ * make the prompt and the manifest two slightly different documents.
14792
+ */
14793
+ kind: "externalLink";
14794
+ patterns: string[];
14795
+ } | {
14796
+ /**
14797
+ * Derived from `contributes.network`: hosts this plugin's runtime code requests bytes from.
14798
+ *
14799
+ * A separate question from `externalLink` and never folded into it. Opening a page hands an
14800
+ * address to the browser the player already uses and nothing comes back; this fetches, and
14801
+ * what comes back runs inside the game. An author deciding about one has not decided about
14802
+ * the other.
14803
+ *
14804
+ * The patterns are also what a project's network allowlist shows attributed to this plugin,
14805
+ * which is the point of declaring them: a build narrowed to a list still reaches what the
14806
+ * author approved here, and the list can say so rather than silently making room.
14807
+ */
14808
+ kind: "network";
14809
+ patterns: string[];
14810
+ };
14811
+ type PluginSidecarKind = "executable" | "node";
14812
+ declare const PluginRuntimeCapability: {
14813
+ /** `app.game.store` — plugin-scoped persistent key/value storage. */
14814
+ readonly Store: "store";
14815
+ /** `app.game.events` — game lifecycle and engine event subscription. */
14816
+ readonly Events: "events";
14817
+ /** `app.game.state.get` — read story variables. */
14818
+ readonly StateRead: "state.read";
14819
+ /** `app.game.state.set` — write story variables. */
14820
+ readonly StateWrite: "state.write";
14821
+ /** `app.game.saves` — list and read save metadata. */
14822
+ readonly SavesRead: "saves.read";
14823
+ /**
14824
+ * `app.game.saves.write` / `.load` — overwrite a save slot and load one.
14825
+ *
14826
+ * Separate from (and heavier than) `saves.read`: this can destroy a
14827
+ * playthrough. It exists because quick-save/quick-load is an ordinary thing
14828
+ * for a visual novel to ship — the built-in Quick Save plugin is exactly
14829
+ * this — and a capability model that cannot express a feature the product
14830
+ * already has would just push authors back around it.
14831
+ */
14832
+ readonly SavesWrite: "saves.write";
14833
+ /** `app.game.ui.overlay` — draw on top of the game. */
14834
+ readonly UiOverlay: "ui.overlay";
14835
+ /** `app.game.assets` — resolve packaged asset URLs. */
14836
+ readonly Assets: "assets";
14837
+ /**
14838
+ * `app.game.locale` — read and observe the game language, and read the project's own
14839
+ * localized text through the same table and fallback chain the `Get Text` node uses.
14840
+ */
14841
+ readonly Locale: "locale";
14842
+ /**
14843
+ * `app.game.menu` — own the menu bar above the game.
14844
+ *
14845
+ * Its own capability rather than part of anything else, because what it grants is *the chrome
14846
+ * the player reads the game through*: every row on that bar, and what each one does, is this
14847
+ * plugin's to decide. The rows still act through the game's own vocabulary (see
14848
+ * `@shared/types/gameMenu`) - nothing here reaches past what a blueprint could already do - but
14849
+ * an author approving this is handing over a surface their players will read as the game's own,
14850
+ * which is worth a name at install rather than arriving with something else.
14851
+ *
14852
+ * Absent on shells with no bar to give (the web export, Dev Mode), which is reported the way
14853
+ * every environment difference is: the member is not there.
14854
+ */
14855
+ readonly Menu: "menu";
14856
+ /**
14857
+ * `app.game.story` — register a compile pass that observes each scene and injects engine
14858
+ * actions around its rows.
14859
+ *
14860
+ * The heaviest of the ten to read, and the one whose weight is easiest to miss: nothing here
14861
+ * touches the player's files, and the author still sees only their own document — but a pass
14862
+ * runs over *every* scene of the project, sees who speaks in each, and can put actions around
14863
+ * lines it did not write. That is a plugin editing the story, at the last moment before it
14864
+ * plays, and it is worth a name the author approves at install.
14865
+ *
14866
+ * It is not `state.write` in disguise and does not imply it: a pass builds actions, it does not
14867
+ * run them, and the one piece of runtime state it can touch is a scene-local flag the compiler
14868
+ * creates and owns (`SceneCompileContext.runtimeFlag`), never a story variable the author
14869
+ * declared.
14870
+ */
14871
+ readonly StoryCompile: "story.compile";
14872
+ /**
14873
+ * `app.game.diagnostics` — read what the player's caches are holding.
14874
+ *
14875
+ * The lightest of the list to grant and the easiest to misread as heavier than it is. Nothing
14876
+ * here is authored data: it reports how many images the engine is holding, how many bytes those
14877
+ * cost, and what budgets they are being held against. It reads no story, no save and no
14878
+ * variable, and every member is a read - there is nothing to set.
14879
+ *
14880
+ * It is its own capability rather than free like `data` and `config` because a plugin that
14881
+ * watches memory is a plugin that runs continuously and reports numbers about the player's
14882
+ * machine, and an author should see that named at install. The performance inspector is the
14883
+ * plugin this exists for.
14884
+ *
14885
+ * Absent until a game session is live, which is the same rule every other engine-backed member
14886
+ * follows: a plugin's `setup()` runs during boot, and asking then answers null rather than
14887
+ * throwing.
14888
+ */
14889
+ readonly Diagnostics: "diagnostics";
14890
+ /**
14891
+ * `app.game.process.memory()` — read how much memory the game's processes hold, as the
14892
+ * operating system counts it.
14893
+ *
14894
+ * Its own capability rather than a member of `diagnostics`, because what it reports is not the
14895
+ * game's: `diagnostics` describes the engine's caches, which are the game's own bookkeeping,
14896
+ * while this is the player's machine - how much of its memory every process of the game is
14897
+ * holding, the main process and the GPU process included. An author who approved the first did
14898
+ * not approve the second, and a plugin that already holds `diagnostics` must not gain this by
14899
+ * an update without being asked again - which is what a capability of its own guarantees.
14900
+ *
14901
+ * Read-only, and numbers only: sizes and what each process is for, never a path, an id or a
14902
+ * command line. Nothing is sent anywhere by the host; what a plugin does with a reading is
14903
+ * visible in the code the author installed.
14904
+ *
14905
+ * Absent where there are no processes to count - the web export - and narrowed in Dev Mode to
14906
+ * the window's own renderer, since everything around it is Studio's.
14907
+ */
14908
+ readonly ProcessMemory: "process.memory";
14909
+ };
14910
+ type PluginRuntimeCapability = typeof PluginRuntimeCapability[keyof typeof PluginRuntimeCapability];
14911
+ /**
14912
+ * `app.game.sidecar` has no entry here on purpose: it exists exactly when
14913
+ * `contributes.sidecars` is non-empty. Declaring the sidecar *is* the request.
14914
+ */
14915
+ export interface PluginIdentity {
14916
+ id: string;
14917
+ name?: string;
14918
+ version?: string;
14919
+ publisher?: string;
14920
+ }
14921
+ interface PluginPermissionRequestBase {
14922
+ requestId: string;
14923
+ plugin: PluginIdentity;
14924
+ reason?: string;
14925
+ requestedAt?: number;
14926
+ }
14927
+ export type PluginPermissionRequest = (PluginPermissionRequestBase & {
14928
+ kind: "trust";
14929
+ persistence?: PluginPermissionPersistence;
14930
+ }) | (PluginPermissionRequestBase & {
14931
+ kind: "filesystem";
14932
+ path: string;
14933
+ mode: PluginFileSystemPermissionMode;
14934
+ recursive: boolean;
14935
+ persistence: PluginPermissionPersistence;
14936
+ }) | (PluginPermissionRequestBase & {
14937
+ kind: "install";
14938
+ source: string;
14939
+ permissions?: PluginInstallPermission[];
14940
+ persistence?: PluginPermissionPersistence;
14941
+ }) | (PluginPermissionRequestBase & {
14942
+ kind: "api";
14943
+ capability: string;
14944
+ persistence?: PluginPermissionPersistence;
14945
+ });
14946
+ interface PluginPermissionGrantResult {
14947
+ requestId: string;
14948
+ pluginId: string;
14949
+ kind: PluginPermissionRequest["kind"];
14950
+ approved: boolean;
14951
+ persistence: PluginPermissionPersistence;
14952
+ grantedAt?: number;
14953
+ }
14954
+ export type PluginPermissionPromptResult = PluginPermissionGrantResult | null;
14955
+ declare const PluginManifestVersion = 2;
14956
+ /**
14957
+ * Per-target plugin entry files. Each entry is a prebundled ESM file relative
14958
+ * to the plugin package root. At least one target must be declared.
14959
+ *
14960
+ * - `studio`: loaded in the workspace window only; talks to the
14961
+ * `narraleaf-studio/plugin` host API (editor extensions).
14962
+ * - `runtime`: loaded in every game execution environment (Dev Mode window,
14963
+ * Preview, Production); talks to the `narraleaf-studio/runtime` host API
14964
+ * (game logic such as blueprint node execute bindings).
14965
+ */
14966
+ export type PluginManifestEntries = {
14967
+ studio?: string;
14968
+ runtime?: string;
14969
+ };
14970
+ type PluginLocaleContribution = {
14971
+ code: string;
14972
+ nativeName?: string;
14973
+ englishName?: string;
14974
+ intl?: string;
14975
+ dir?: "ltr" | "rtl";
14976
+ messages: string;
14977
+ };
14978
+ type PluginBinaryPlatformKey = string;
14979
+ type PluginSidecarTargetContribution = {
14980
+ /** Executable (or, for `kind: "node"`, the .js file) to run. Must appear in `include`. */
14981
+ entry: string;
14982
+ /**
14983
+ * Everything shipped for this platform. Entries are package-relative paths,
14984
+ * or `dep:<buildDependencyId>/<path>` to pull an artifact produced by a
14985
+ * declared build dependency (that is how third-party redistributables that
14986
+ * we may not vendor ourselves reach the pack).
14987
+ */
14988
+ include: string[];
14989
+ /**
14990
+ * `sha256` of every package-relative entry in `include`, hex. Verified at
14991
+ * install and again at pack time, so a tampered package fails to install
14992
+ * instead of silently shipping a different binary. `dep:` entries are covered
14993
+ * by the build dependency's own digest instead.
14994
+ */
14995
+ sha256: Record<string, string>;
14996
+ };
14997
+ type PluginSidecarContribution = {
14998
+ /** Prefixed with the plugin id, like every other contributed identifier. */
14999
+ id: string;
15000
+ /**
15001
+ * `executable` spawns the binary directly and talks to it over stdin/stdout; `node` runs the
15002
+ * .js as an Electron utility process and talks to it over `process.parentPort`, which is the
15003
+ * one channel a utility process has - it has no stdin.
15004
+ */
15005
+ kind: PluginSidecarKind;
15006
+ /**
15007
+ * Newline-delimited JSON, which is the framing and not the channel: where the frames travel is
15008
+ * {@link kind}'s business, and only an executable sidecar's travel over stdio. Either way
15009
+ * stderr stays a plain log channel.
15010
+ *
15011
+ * `stdio-jsonl` is the older spelling of the same thing, from when there was only one channel
15012
+ * it could mean. Manifests that say it keep working and always will - a published plugin is a
15013
+ * file somebody already shipped - but a new one should say `jsonl`.
15014
+ */
15015
+ transport: "jsonl" | "stdio-jsonl";
15016
+ /** `onGameStart` spawns with the window; `onRequest` waits for the first call. */
15017
+ autostart: "onGameStart" | "onRequest";
15018
+ /** How long the handshake may take before the sidecar counts as unavailable. */
15019
+ startupTimeoutMs: number;
15020
+ /** Grace period between the shutdown message and SIGTERM. */
15021
+ shutdownTimeoutMs: number;
15022
+ restart: {
15023
+ maxRetries: number;
15024
+ backoffMs: number;
15025
+ };
15026
+ targets: Record<PluginBinaryPlatformKey, PluginSidecarTargetContribution>;
15027
+ };
15028
+ type PluginBuildDependencyTargetContribution = {
15029
+ url: string;
15030
+ /** Mandatory. Doubles as the cache key, so re-pointing the URL at identical bytes never re-downloads. */
15031
+ sha256: string;
15032
+ archive: "zip";
15033
+ /** Archive-internal path -> path inside the produced dependency directory. */
15034
+ files: Record<string, string>;
15035
+ } | {
15036
+ url: string;
15037
+ sha256: string;
15038
+ archive: "none";
15039
+ /** Name the downloaded file takes inside the produced dependency directory. */
15040
+ fileName: string;
15041
+ };
15042
+ type PluginBuildDependencyContribution = {
15043
+ /** Prefixed with the plugin id. Referenced from sidecar `include` as `dep:<id>/<path>`. */
15044
+ id: string;
15045
+ /** Shown to the author at install and in build logs; say what the binaries are. */
15046
+ description?: string;
15047
+ targets: Record<PluginBinaryPlatformKey, PluginBuildDependencyTargetContribution>;
15048
+ };
15049
+ declare const PLUGIN_BUILD_CONFIG_TYPES: readonly [
15050
+ "text",
15051
+ "secret"
15052
+ ];
15053
+ type PluginBuildConfigType = (typeof PLUGIN_BUILD_CONFIG_TYPES)[number];
15054
+ declare const PLUGIN_BUILD_CONFIG_SCOPES: readonly [
15055
+ "global",
15056
+ "variant",
15057
+ "platform",
15058
+ "variant-platform"
15059
+ ];
15060
+ type PluginBuildConfigScope = (typeof PLUGIN_BUILD_CONFIG_SCOPES)[number];
15061
+ type PluginBuildConfigFieldContribution = {
15062
+ /** Unique within the plugin. Keys the stored value; never displayed. */
15063
+ key: string;
15064
+ /** Author-facing name of the field. */
15065
+ label: string;
15066
+ /** One line saying what the value is for. */
15067
+ description?: string;
15068
+ /** `text` is stored in the project; `secret` is not - the project stores a handle. */
15069
+ type: PluginBuildConfigType;
15070
+ /** Which builds share one value. */
15071
+ scope: PluginBuildConfigScope;
15072
+ /** Platforms the field applies to. Absent means every platform. */
15073
+ platforms?: GameBuildPlatform[];
15074
+ /** A build that would ship without a value for this field is refused. */
15075
+ required?: boolean;
15076
+ };
15077
+ export type PluginContributes = {
15078
+ /** Blueprint node types this plugin provides (editor def + runtime execute). */
15079
+ blueprintNodes?: string[];
15080
+ /** Widget element types this plugin provides (editor module + runtime renderer). */
15081
+ widgets?: string[];
15082
+ /** Studio language packs: locales this plugin adds or fills. */
15083
+ locales?: PluginLocaleContribution[];
15084
+ /**
15085
+ * Plugin storage namespaces to publish with the game, readable at runtime
15086
+ * through `app.game.data.readJson(namespace)`.
15087
+ *
15088
+ * Plugin stores live under the project's `editor/` directory, which is never
15089
+ * packaged. A plugin whose runtime needs authored data (catalogs, tables)
15090
+ * must list those namespaces here. The list is an explicit allowlist so
15091
+ * editor-only plugin state cannot leak into a shipped game by accident.
15092
+ */
15093
+ runtimeData?: string[];
15094
+ /**
15095
+ * Test ids this plugin registers with `app.services.tests` (see
15096
+ * `docs/plugin-test-protocol.md`). Declaring them lets the Launcher say what
15097
+ * a plugin checks before any of its code runs, and registering an
15098
+ * undeclared id throws at load.
15099
+ *
15100
+ * Unlike every other code-backed contribution here, this one derives no
15101
+ * install permission — a test only ever executes because the author picked
15102
+ * it out of the Run > Test dialog and pressed Start, so there is no ambient
15103
+ * capability to consent to at install time.
15104
+ */
15105
+ tests?: string[];
15106
+ /**
15107
+ * Save ids the plugin keeps for its own bookkeeping - a quick-save slot, a checkpoint - rather
15108
+ * than slots a player chose.
15109
+ *
15110
+ * The game leaves them out of `List Saves`, and so out of every save screen built on it, the way
15111
+ * it leaves out its own autosave ring. Nothing else changes for them: the plugin still writes,
15112
+ * loads and finds them through `app.game.saves` (whose `listIds` is the raw store listing and
15113
+ * still includes them), and the explicit-id save nodes - `Load Save`, `Delete Save`, the
15114
+ * `Get Save` readers - take them like any other id.
15115
+ *
15116
+ * Prefixed with the plugin id like the type lists above, so a plugin can only reserve ids in its
15117
+ * own namespace and can never hide a slot the author's own `Save Game` wrote. That is also why it
15118
+ * derives no install permission: it reaches nothing the plugin did not already own.
15119
+ */
15120
+ reservedSaveIds?: string[];
15121
+ /**
15122
+ * Capability domains the `runtime` entry may use. Each maps 1:1 onto a
15123
+ * namespace on `app.game`, and an undeclared domain is absent from that
15124
+ * object rather than present-and-throwing — so what the install prompt
15125
+ * listed and what the plugin can reach are the same set by construction.
15126
+ */
15127
+ runtimeCapabilities?: PluginRuntimeCapability[];
15128
+ /** Native child processes shipped inside the author's game. */
15129
+ sidecars?: PluginSidecarContribution[];
15130
+ /** External binaries fetched (and cached) at build time. */
15131
+ buildDependencies?: PluginBuildDependencyContribution[];
15132
+ /**
15133
+ * Values the author supplies before a build can ship. Declaration only - see
15134
+ * {@link PluginBuildConfigFieldContribution} for why there is no registration API and why this
15135
+ * grants the plugin nothing.
15136
+ */
15137
+ buildConfig?: PluginBuildConfigFieldContribution[];
15138
+ /**
15139
+ * Address patterns this plugin may hand to the player's browser or platform handler, through
15140
+ * `app.game.navigation.openExternal`.
15141
+ *
15142
+ * Unlike everything else here, this one is *not* a list of identifiers the plugin owns - it is a
15143
+ * list of places outside the game it may reach, so it derives an install permission the author
15144
+ * approves by name (see `PluginInstallPermission` `kind: "externalLink"`) and adding one to a
15145
+ * later version re-prompts.
15146
+ *
15147
+ * Patterns are matched structurally, never as strings: see `isExternalLinkPatternDeclared` in
15148
+ * `@shared/types/externalLinkPattern` for the exact semantics. Wildcards are allowed
15149
+ * (`https://*.example.com/app/*`) and the scheme is not restricted to `http(s)`, because the
15150
+ * storefronts a plugin integrates with speak their own (`steam://run/480`). A handful of schemes
15151
+ * are refused at validation whatever a manifest says; that list and its reasoning live in the
15152
+ * same module.
15153
+ *
15154
+ * This is separate from, and grants nothing towards, {@link network}: opening a page and
15155
+ * fetching bytes are different acts, and the Open Link node the author writes is a third thing
15156
+ * again, governed only by its scheme.
15157
+ */
15158
+ externalLinks?: string[];
15159
+ /**
15160
+ * Address patterns this plugin's runtime code requests bytes from.
15161
+ *
15162
+ * Like {@link externalLinks} and unlike everything else here, this is a list of places outside
15163
+ * the game rather than identifiers the plugin owns, so it derives an install permission the
15164
+ * author approves by name (`PluginInstallPermission` `kind: "network"`) and adding one to a
15165
+ * later version re-prompts.
15166
+ *
15167
+ * Two rules narrower than the external-link patterns, both for the same reason - what comes
15168
+ * back from a fetch runs inside the game, while an opened page does not:
15169
+ *
15170
+ * - **`http(s)` only.** There is no storefront-scheme case here; a plugin that fetches speaks
15171
+ * HTTP.
15172
+ * - **The path is written out.** `https://api.example.com/*` is a declaration and
15173
+ * `https://api.example.com` is not, because the pattern language reads the second as the
15174
+ * single path `/`. It is refused rather than rewritten: this string is what the install
15175
+ * prompt shows, and a manifest whose text differs from what was approved is the one thing
15176
+ * this whole mechanism exists to prevent.
15177
+ *
15178
+ * What this reaches is *not* widened by the project's own network allowlist and does not widen
15179
+ * it. A build narrowed to a list still reaches these hosts - the author approved them at
15180
+ * install - and the list shows them attributed to this plugin rather than quietly making room.
15181
+ */
15182
+ network?: string[];
15183
+ /**
15184
+ * The props of a contributed widget that hold words a player reads, by widget type.
15185
+ *
15186
+ * Each type named here must also be in {@link widgets}. A declared prop is treated as Studio's own
15187
+ * text and button labels are: the properties panel offers its words written directly or read
15188
+ * from a translation key, the canvas draws a key's words, the words go into the project's
15189
+ * translation table and exports, the project checks report a missing translation or key, and
15190
+ * the glyph check reads them. In the game the widget's renderer is handed the words already in
15191
+ * the player's language, in the prop it declared - it never resolves a key itself.
15192
+ *
15193
+ * Declared in the manifest rather than at registration because both entries need it - the
15194
+ * studio entry's widget is edited by it and the runtime entry's is drawn by it - and a
15195
+ * declaration only one of them made would be the half that drifts.
15196
+ */
15197
+ widgetText?: Record<string, PluginWidgetTextContribution[]>;
15198
+ /**
15199
+ * The shapes of the rows this plugin's nodes hand out, so a list can be given one and a field
15200
+ * read from one by name.
15201
+ *
15202
+ * A node pin carrying them says `struct:<id>` or `array<struct:<id>>` - `Get Gallery` answers
15203
+ * `array<struct:narraleaf.gallery.entry>` - and from then on the plugin's rows are what the
15204
+ * engine's own are: a list picks the shape under Edit content and its fields are the plugin's
15205
+ * and locked, Get Field offers them, and a list handed rows that lack them is reported.
15206
+ *
15207
+ * Declared in the manifest rather than at registration because the editor, the running game and
15208
+ * the command-line checker all need the shape, and only the manifest is read by all three before
15209
+ * any plugin code runs. Ids must start with the plugin id.
15210
+ */
15211
+ structs?: PluginStructContribution[];
15212
+ };
15213
+ type PluginStructContribution = {
15214
+ /** Prefixed with the plugin id: `narraleaf.gallery.entry`. */
15215
+ id: string;
15216
+ /** What the editor calls the shape - "Gallery entry". */
15217
+ name: string;
15218
+ /** `name` per editor locale, matched exactly as `localized` names are. */
15219
+ localized?: Record<string, string>;
15220
+ /** In the order the rows are read: the list's content editor shows them in this order. */
15221
+ fields: {
15222
+ key: string;
15223
+ type: UIStructFieldType;
15224
+ }[];
15225
+ };
15226
+ /**
15227
+ * One prop of a contributed widget whose value is words a player reads.
15228
+ *
15229
+ * `keyProp` is the prop the widget's translation key is kept in when the author reads the words from
15230
+ * one. It defaults to `<prop>LocalizationKey`, and it is the host's to write: the widget never reads it
15231
+ * to draw, because the words in `prop` already come from the key when there is one.
15232
+ */
15233
+ export type PluginWidgetTextContribution = {
15234
+ /** The prop holding the words. */
15235
+ prop: string;
15236
+ /** The prop the translation key is kept in; `<prop>LocalizationKey` when not declared. */
15237
+ keyProp?: string;
15238
+ /** What the properties panel calls the words. The prop name when not declared. */
15239
+ label?: string;
15240
+ /** `label` per editor locale, matched exactly as `localized` names are. */
15241
+ localized?: Record<string, string>;
15242
+ /** Whether the words may run over several lines; one line when not declared. */
15243
+ multiline?: boolean;
15244
+ };
15245
+ /**
15246
+ * The plugin's name and description in one editor language. Either may be left out, and the plain
15247
+ * manifest field is shown in its place.
15248
+ */
15249
+ export type PluginManifestLocalizedText = {
15250
+ name?: string;
15251
+ description?: string;
15252
+ };
15253
+ /**
15254
+ * `name` and `description` per editor locale, keyed by the exact code Studio's language setting
15255
+ * stores (`zh`, `ja`, or a code a language pack adds). There is no region matching: an entry under
15256
+ * `zh-CN` is never shown, because no Studio locale is called that.
15257
+ */
15258
+ export type PluginManifestLocalized = Record<string, PluginManifestLocalizedText>;
15259
+ export type PluginManifestV2 = Omit<PluginIdentity, "id" | "name" | "version"> & Required<Pick<PluginIdentity, "id" | "name" | "version">> & {
15260
+ manifestVersion: typeof PluginManifestVersion;
15261
+ description?: string;
15262
+ /**
15263
+ * Translations of `name` and `description`, shown wherever Studio names the plugin to an author
15264
+ * whose editor is set to that language. The plain fields stay the plugin's identity: command
15265
+ * lines, project files and built games name it by them.
15266
+ */
15267
+ localized?: PluginManifestLocalized;
15268
+ /**
15269
+ * Package-relative path to the thumbnail shown beside the plugin's name in
15270
+ * the Launcher list. Square, at most 512x512, and one of the extensions in
15271
+ * `PLUGIN_ICON_EXTENSIONS`. Plugins without one keep the name monogram.
15272
+ */
15273
+ icon?: string;
15274
+ entries: PluginManifestEntries;
15275
+ contributes?: PluginContributes;
15276
+ permissions?: PluginInstallPermission[];
15277
+ };
15278
+ export type NormalizedPluginManifestV2 = PluginManifestV2 & {
15279
+ contributes: Required<PluginContributes>;
15280
+ permissions: PluginInstallPermission[];
15281
+ };
15282
+ type PluginInstallSource = {
15283
+ kind: "local-directory";
15284
+ path: string;
15285
+ } | {
15286
+ kind: "builtin";
15287
+ path: string;
15288
+ } | {
15289
+ kind: "registry";
15290
+ url: string;
15291
+ };
15292
+ export type PluginInstallRecord = {
15293
+ pluginId: string;
15294
+ installPath: string;
15295
+ enabled: boolean;
15296
+ builtIn: boolean;
15297
+ manifest: NormalizedPluginManifestV2;
15298
+ installSource: PluginInstallSource;
15299
+ installedAt: number;
15300
+ updatedAt: number;
15301
+ grantedManifestVersion?: string | null;
15302
+ /**
15303
+ * The permission set the user actually approved, recorded so a later version
15304
+ * that does not widen it can inherit the grant instead of re-prompting.
15305
+ * Absent on records written before this was tracked — the manifest at
15306
+ * `grantedManifestVersion` is the fallback source of truth for those.
15307
+ */
15308
+ grantedPermissions?: PluginInstallPermission[] | null;
15309
+ lastError?: string | null;
15310
+ };
15311
+ type PluginStatus = "enabled" | "disabled" | "needsAuthorization" | "error";
15312
+ export type PluginListItem = {
15313
+ pluginId: string;
15314
+ manifest: NormalizedPluginManifestV2;
15315
+ /**
15316
+ * `app://` address of the declared icon, absent when the plugin ships none.
15317
+ * Resolved here rather than in the renderer because only the main process
15318
+ * knows where the package landed on disk.
15319
+ */
15320
+ iconUrl?: string;
15321
+ installPath: string;
15322
+ enabled: boolean;
15323
+ builtIn: boolean;
15324
+ status: PluginStatus;
15325
+ installSource: PluginInstallSource;
15326
+ installedAt: number;
15327
+ updatedAt: number;
15328
+ grantedManifestVersion?: string | null;
15329
+ lastError?: string | null;
15330
+ };
15331
+ /** Descriptor handed to the workspace window: plugins with a studio entry. */
15332
+ export type WorkspacePluginDescriptor = {
15333
+ plugin: PluginIdentity;
15334
+ manifest: NormalizedPluginManifestV2;
15335
+ entryUrl: string;
15336
+ };
15337
+ /** Descriptor handed to game execution environments: plugins with a runtime entry. */
15338
+ export type RuntimePluginDescriptor = {
15339
+ plugin: PluginIdentity;
15340
+ manifest: NormalizedPluginManifestV2;
15341
+ entryUrl: string;
15342
+ /**
15343
+ * Published plugin storage, keyed by the namespaces declared in
15344
+ * `contributes.runtimeData`. Absent namespaces simply have no entry - a
15345
+ * plugin must tolerate missing data (the project may never have written it).
15346
+ */
15347
+ data?: Record<string, unknown>;
15348
+ /**
15349
+ * This plugin's own `contributes.buildConfig` values, as the build that produced the pack
15350
+ * resolved them, keyed by field key. Only ever the declaring plugin's own values: the pack
15351
+ * holds one record per plugin and a descriptor is built from one entry.
15352
+ *
15353
+ * Absent outside a compiled pack - the editor and Dev Mode hand out descriptors that were never
15354
+ * built for a variant, so there is nothing resolved to carry.
15355
+ */
15356
+ buildConfig?: Record<string, string>;
15357
+ };
15358
+ type GameMenuLabel = {
15359
+ readonly key?: string | null;
15360
+ readonly text: string;
15361
+ readonly words?: string | null;
15362
+ };
15363
+ declare const GAME_MENU_DYNAMIC_SOURCES: readonly [
15364
+ "textLanguage",
15365
+ "voiceLanguage",
15366
+ "windowScale"
15367
+ ];
15368
+ type GameMenuDynamicSource = typeof GAME_MENU_DYNAMIC_SOURCES[number];
15369
+ type GameMenuAction =
15370
+ /** Open an app surface as a page. */
15371
+ {
15372
+ readonly type: "openPage";
15373
+ readonly surfaceId: string;
15374
+ }
15375
+ /** Open an app surface as a layer over whatever is on screen. */
15376
+ | {
15377
+ readonly type: "openLayer";
15378
+ readonly surfaceId: string;
15379
+ readonly modal?: boolean;
15380
+ readonly dismissible?: boolean;
15381
+ readonly group?: string | null;
15382
+ }
15383
+ /** End the playthrough and hand the screen to a page - the same act as the story's quit row. */
15384
+ | {
15385
+ readonly type: "quitToPage";
15386
+ readonly surfaceId: string;
15387
+ }
15388
+ /**
15389
+ * Close the game.
15390
+ *
15391
+ * The same act as the Quit Application node, which means the same terms: a graceful quit that
15392
+ * does NOT run the author's close handler (`On Window Close Requested` answers the window
15393
+ * frame's own close, and the node deliberately stands the guard aside so a quit the game asked
15394
+ * for is not asked back about). An author who wants their own "really quit?" screen binds a
15395
+ * function instead and calls the node at the end of it - which is the same answer this menu
15396
+ * gives for every other confirmation.
15397
+ */
15398
+ | {
15399
+ readonly type: "quitApp";
15400
+ }
15401
+ /** Advance the line on screen. */
15402
+ | {
15403
+ readonly type: "next";
15404
+ }
15405
+ /** Auto-forward on/off. Ticked while on. */
15406
+ | {
15407
+ readonly type: "toggleAutoForward";
15408
+ }
15409
+ /** Skipping on/off. Ticked while running. */
15410
+ | {
15411
+ readonly type: "toggleSkipping";
15412
+ }
15413
+ /** Which lines skipping stops at. The two values are a radio pair, never one switch. */
15414
+ | {
15415
+ readonly type: "setSkipReadText";
15416
+ readonly value: boolean;
15417
+ }
15418
+ /** Show or hide the dialogue box. Ticked while shown. */
15419
+ | {
15420
+ readonly type: "toggleDialog";
15421
+ }
15422
+ /** Step back one line of history. Greyed out when there is none. */
15423
+ | {
15424
+ readonly type: "historyUndo";
15425
+ }
15426
+ /** Step forward again after an undo. Greyed out when there is none. */
15427
+ | {
15428
+ readonly type: "historyRedo";
15429
+ }
15430
+ /** Full-screen on/off. Ticked while full-screen, including when the OS did it. */
15431
+ | {
15432
+ readonly type: "toggleFullscreen";
15433
+ }
15434
+ /**
15435
+ * Call a function declared in the global blueprint.
15436
+ *
15437
+ * `fnRef` is the catalogue's own reference (see `fnCatalog`), and `args` are keyed by the head's
15438
+ * parameter pin ids. Everything the named actions above do not cover goes through here -
15439
+ * including every confirmation, because what a game asks before losing a playthrough is the
15440
+ * author's screen and not a native box in a language the game may not even be running in.
15441
+ */
15442
+ | {
15443
+ readonly type: "fn";
15444
+ readonly fnRef: string;
15445
+ readonly args?: Readonly<Record<string, unknown>>;
15446
+ };
15447
+ type GameMenuItemSpec = {
15448
+ readonly kind: "separator";
15449
+ } | {
15450
+ readonly kind: "submenu";
15451
+ readonly label: GameMenuLabel;
15452
+ readonly items: readonly GameMenuItemSpec[];
15453
+ } | {
15454
+ readonly kind: "action";
15455
+ readonly label: GameMenuLabel;
15456
+ readonly action: GameMenuAction;
15457
+ } | {
15458
+ readonly kind: "dynamic";
15459
+ readonly source: GameMenuDynamicSource;
15460
+ };
15461
+ type GameMenuSpecMenu = {
15462
+ readonly label: GameMenuLabel;
15463
+ readonly items: readonly GameMenuItemSpec[];
15464
+ };
15465
+ type GameMenuSpec = {
15466
+ readonly menus: readonly GameMenuSpecMenu[];
15467
+ };
15468
+ /**
15469
+ * How much memory a running game costs the machine it runs on, process by process.
15470
+ *
15471
+ * Read in the main process from Electron's process metrics (`app.getAppMetrics()`), which is the
15472
+ * operating system's own account: the same working set a task manager shows. Nothing a page can
15473
+ * read gives this. `performance.memory` describes one JavaScript heap, and in Electron it is also
15474
+ * rounded to coarse buckets - the two arms of a measured comparison both read exactly 92.9 MB -
15475
+ * while the memory a visual novel actually spends is mostly outside any heap:
15476
+ * decoded pictures, textures in the GPU process, audio buffers, the main process serving and
15477
+ * decrypting assets. The whole game is several processes, and only the process that owns them can
15478
+ * see all of them.
15479
+ *
15480
+ * `process.memoryUsage()` in the main process was the other candidate and answers a narrower
15481
+ * question - the main process's own heap and buffers - which is the one process a game's pictures
15482
+ * are not in.
15483
+ *
15484
+ * Comments in English per project convention.
15485
+ */
15486
+ /** What a process is for, in the words a reader of a memory report needs. */
15487
+ type GameProcessKind = "main" | "renderer" | "gpu" | "utility" | "other";
15488
+ type GameProcessMemoryEntry = {
15489
+ kind: GameProcessKind;
15490
+ /**
15491
+ * What the platform calls it, when it says: a utility process names its service ("Network
15492
+ * Service", "Audio Service", a sidecar's own name). Absent for the rest.
15493
+ */
15494
+ name?: string;
15495
+ /** Whether this is the process the asking page runs in. */
15496
+ current: boolean;
15497
+ /** Memory currently resident in physical RAM, in bytes. */
15498
+ workingSetBytes: number;
15499
+ /** The most this process has ever had resident, in bytes. */
15500
+ peakWorkingSetBytes: number;
15501
+ /** Memory no other process shares, in bytes. Windows reports it; other platforms do not. */
15502
+ privateBytes?: number;
15503
+ };
15504
+ type GameProcessMemoryReading = {
15505
+ /**
15506
+ * What the processes below add up to.
15507
+ *
15508
+ * - `game` - every process the game is made of. A packaged game and a preview.
15509
+ * - `window` - only the process that draws this window. Dev Mode: the window is Studio's, and
15510
+ * the processes around it (Studio's main process, its GPU process, its other windows) are
15511
+ * shared with everything else Studio has open, so counting them would report Studio.
15512
+ */
15513
+ scope: "game" | "window";
15514
+ processes: GameProcessMemoryEntry[];
15515
+ /** The sum of every process's working set, in bytes. */
15516
+ workingSetBytes: number;
15517
+ /** The sum of every process's private bytes, or null where the platform does not report them. */
15518
+ privateBytes: number | null;
15519
+ };
15520
+ declare const UI_INPUT_POINTER_GESTURES: readonly [
15521
+ "click",
15522
+ "doubleClick",
15523
+ "rightClick",
15524
+ "wheelUp",
15525
+ "wheelDown",
15526
+ "wheelLeft",
15527
+ "wheelRight",
15528
+ "longPress",
15529
+ "middleClick"
15530
+ ];
15531
+ type UIInputPointerGesture = (typeof UI_INPUT_POINTER_GESTURES)[number];
15532
+ type UIInputBinding = {
15533
+ kind: "pointer";
15534
+ gesture: UIInputPointerGesture;
15535
+ } | {
15536
+ kind: "key";
15537
+ key: string;
15538
+ } | {
15539
+ kind: "gamepad";
15540
+ button: string;
15541
+ };
15542
+ type UIInputActionDef = {
15543
+ id: string;
15544
+ /** Author-facing name. */
15545
+ name: string;
15546
+ /** The bindings a surface gets unless it overrides them. */
15547
+ bindings: UIInputBinding[];
15548
+ };
15549
+ type UISurfaceActionEnablement = {
15550
+ actionId: string;
15551
+ /** Whether firing this action stops the input here. Default true. */
15552
+ consume?: boolean;
15553
+ };
15554
+ declare const UI_PAGE_ANIMATION_PRESETS: readonly [
15555
+ "none",
15556
+ "fade",
15557
+ "slide",
15558
+ "push",
15559
+ "zoom",
15560
+ "pop",
15561
+ "blur"
15562
+ ];
15563
+ declare const UI_PAGE_ANIMATION_DIRECTIONS: readonly [
15564
+ "auto",
15565
+ "left",
15566
+ "right",
15567
+ "up",
15568
+ "down",
15569
+ "angle"
15570
+ ];
15571
+ type UIPageAnimationPreset = (typeof UI_PAGE_ANIMATION_PRESETS)[number];
15572
+ type UIPageAnimationDirection = (typeof UI_PAGE_ANIMATION_DIRECTIONS)[number];
15573
+ type UIPageAnimationSettings = {
15574
+ enter: UIPageAnimationPreset;
15575
+ exit: UIPageAnimationPreset;
15576
+ enterDirection: UIPageAnimationDirection;
15577
+ exitDirection: UIPageAnimationDirection;
15578
+ enterAngleDegrees: number;
15579
+ exitAngleDegrees: number;
15580
+ enterDurationSeconds: number;
15581
+ exitDurationSeconds: number;
15582
+ /** Held before this host's own enter starts, counted from the moment its subtree begins entering. */
15583
+ enterDelaySeconds: number;
15584
+ /** Held before this host's own exit starts, counted from the moment its subtree begins exiting. */
15585
+ exitDelaySeconds: number;
15586
+ /** Added to each successive direct child, so a row of children arrives (and leaves) one after another. */
15587
+ childStaggerSeconds: number;
15588
+ /** Hold this host's own exit until every child has finished leaving. Chains through nested parents. */
15589
+ exitWaitsForChildren: boolean;
15590
+ /** Surfaces only: hold the incoming Page until this exit has finished. */
15591
+ exitBlocking: boolean;
15592
+ };
15593
+ type UISurfaceBackgroundFillMode = "cover" | "contain" | "stretch" | "tile";
15594
+ type UISurfaceBackgroundImage = {
15595
+ /** Image library asset id. Absent background = absent field, so this is never empty. */
15596
+ assetId: string;
15597
+ fillMode: UISurfaceBackgroundFillMode;
15598
+ };
15599
+ type UIDocumentVersion = number;
15600
+ type UIDocumentId = string;
15601
+ type UISurfaceId = string;
15602
+ type UIElementId = string;
15603
+ type UIDocument = {
15604
+ schemaVersion: UIDocumentVersion;
15605
+ id: UIDocumentId;
15606
+ name: string;
15607
+ surfaces: UISurface[];
15608
+ components?: UIComponentDefinition[];
15609
+ elements: Record<UIElementId, UIElement>;
15610
+ /**
15611
+ * The shapes this document's widgets describe their data with, keyed by id.
15612
+ *
15613
+ * No authoring surface manages this table: an author edits the fields on the widget that uses
15614
+ * them, and a widget stores only the id. It is a document-level table rather than a copy on each
15615
+ * widget so that a shape has one spelling for the whole project - which is what lets one list's
15616
+ * content feed another's, and what a Break node's pins are generated from. The rules that keep
15617
+ * an implicit library safe to edit are in `struct.ts`.
15618
+ */
15619
+ structs?: Record<UIStructId, UIStructDef>;
15620
+ /**
15621
+ * What the gestures of this project mean, keyed by id.
15622
+ *
15623
+ * The vocabulary half of the input model: "advance", "skip", "open the menu" - named once for
15624
+ * the whole project, with the bindings a surface gets unless it says otherwise. A surface stores
15625
+ * only the id, in {@link UISurface.actions}, so renaming an action here renames it everywhere
15626
+ * and rebinding it rebinds every surface that took the default. The rules are in
15627
+ * `inputAction.ts`.
15628
+ */
15629
+ actions?: Record<string, UIInputActionDef>;
15630
+ /**
15631
+ * The page the game starts on, by id.
15632
+ *
15633
+ * Absent means the page carrying `MAIN_APP_SURFACE_ID` - the id a new document gives its first
15634
+ * page, and the only entry a document had before this field existed - so a document whose author
15635
+ * never moved the entry stays exactly as short as it was. Read only through `resolveEntrySurface`
15636
+ * (`entrySurface.ts`), which also says what a pointer that has lost its page falls back to.
15637
+ *
15638
+ * A pointer rather than an id swap: a page's id is what its blueprints are filed under and what
15639
+ * every `Go Page` names, so making another page the entry must not change which id it has.
15640
+ *
15641
+ * Carries no schema bump, for the reason `actions` and `structs` did not: absence already means a
15642
+ * defined answer, so a document written before this field reads correctly without one.
15643
+ */
15644
+ entrySurfaceId?: UISurfaceId;
15645
+ meta?: UIDocumentMeta;
15646
+ };
15647
+ type UIDocumentMeta = {
15648
+ createdAt?: string;
15649
+ updatedAt?: string;
15650
+ };
15651
+ type UIHost = "app" | "player";
15652
+ type UISurfaceKind = "appSurface" | "stageSurface";
15653
+ type UIStageSlotId = "onStage" | "dialog" | "notification" | "choice" | "nvl";
15654
+ type UIStageSurfaceMount = {
15655
+ kind: "slot";
15656
+ slotId: UIStageSlotId;
15657
+ };
15658
+ type UIAppSurface = {
15659
+ id: UISurfaceId;
15660
+ name: string;
15661
+ host: "app";
15662
+ kind: "appSurface";
15663
+ designSize: UISurfaceDesignSize;
15664
+ rootElementId: UIElementId;
15665
+ settings?: UISurfaceSettings;
15666
+ /**
15667
+ * Which of the project's actions this surface answers. An id here names an entry of
15668
+ * {@link UIDocument.actions}; absent means it answers none of them.
15669
+ *
15670
+ * Input that lands on this surface's content stops here whether or not an action answered it,
15671
+ * unless the action that answered says otherwise. There is no surface-wide setting for that: a
15672
+ * surface drawn over another is drawn over it, and an action is the only thing that knows
15673
+ * whether what it just did leaves anything for the surface behind to do.
15674
+ */
15675
+ actions?: UISurfaceActionEnablement[];
15676
+ /** The values this page is opened with, in author order. See {@link UIPageParam}. */
15677
+ params?: UIPageParam[];
15678
+ };
15679
+ type UIPageParam = {
15680
+ id: string;
15681
+ name: string;
15682
+ type: UIPageParamType;
15683
+ /**
15684
+ * For a `list`: the shape each row has, as a struct id (an engine shape such as `nl.confirmButton`,
15685
+ * a plugin's, or one of the project's). It types the value on both ends - the inputs that give it
15686
+ * and `Get Page Param` - and a list on the page bound to it is checked against it. Absent for
15687
+ * every other type, and for a list of rows of no declared shape.
15688
+ */
15689
+ struct?: string;
15690
+ /**
15691
+ * What the page reads when whoever opened it gave nothing for this name, in the declared type.
15692
+ * Absent reads as the type's empty value (`uiPageParamDefaultValue`).
15693
+ */
15694
+ defaultValue?: unknown;
15695
+ };
15696
+ type UIPageParamType = "string" | "text" | "number" | "boolean" | "list" | "json";
15697
+ type UIStageSurface = {
15698
+ id: UISurfaceId;
15699
+ name: string;
15700
+ host: "player";
15701
+ kind: "stageSurface";
15702
+ designSize: UISurfaceDesignSize;
15703
+ rootElementId: UIElementId;
15704
+ settings?: UISurfaceSettings;
15705
+ mount: UIStageSurfaceMount;
15706
+ slots?: Record<string, UISlotDefinition>;
15707
+ /** @see UIAppSurface.actions */
15708
+ actions?: UISurfaceActionEnablement[];
15709
+ };
15710
+ type UISurface = UIAppSurface | UIStageSurface;
15711
+ type UISurfaceDesignSize = {
15712
+ width: number;
15713
+ height: number;
15714
+ };
15715
+ type UISurfaceSettings = {
15716
+ backgroundColor?: string;
15717
+ /**
15718
+ * Painted over {@link backgroundColor}, inside the design area only - the letterbox bars around
15719
+ * a scaled Surface keep showing the colour, so the two settings are not alternatives.
15720
+ */
15721
+ backgroundImage?: UISurfaceBackgroundImage;
15722
+ pageAnimation?: UIPageAnimationSettings;
15723
+ /**
15724
+ * What the asset set this Surface's own background names resolves to, per locale. Build-written
15725
+ * exactly as {@link UIElement.assetVariants} is, and here rather than on the Surface so the
15726
+ * answer sits beside the only asset a Surface names by itself.
15727
+ */
15728
+ assetVariants?: AssetVariantMap;
15729
+ };
15730
+ type UISlotDefinition = {
15731
+ id: string;
15732
+ name: string;
15733
+ rootElementId?: UIElementId;
15734
+ };
15735
+ type UIComponentId = string;
15736
+ type UIComponentDefinition = {
15737
+ id: UIComponentId;
15738
+ name: string;
15739
+ rootElementId: UIElementId;
15740
+ elements: Record<UIElementId, UIElement>;
15741
+ /** Declared in the component's own inspector; each instance supplies its own values. */
15742
+ params?: UIComponentParam[];
15743
+ previewMeta?: {
15744
+ width?: number;
15745
+ height?: number;
15872
15746
  };
15747
+ createdAt?: string;
15748
+ updatedAt?: string;
15749
+ };
15750
+ type UIComponentParam = {
15751
+ /** Stable identity. Blueprints and instance values reference this, so renaming `name` is free. */
15752
+ id: string;
15753
+ /** What the author called it, shown in the instance's inspector. */
15754
+ name: string;
15755
+ type: UIComponentParamType;
15756
+ /**
15757
+ * Used by an instance that has not overridden it, and by the definition's own preview. For a
15758
+ * `text` parameter these are words written directly, translated through the definition's own unit
15759
+ * (`uiComponentParamUnitId(componentId, paramId)`).
15760
+ */
15761
+ defaultValue: string;
15762
+ };
15763
+ type UIComponentParamType = "string" | "text" | "audioTrack";
15764
+ type UIElement = {
15765
+ id: UIElementId;
15766
+ type: string;
15767
+ name?: string;
15768
+ parentId: UIElementId | null;
15769
+ childrenIds: UIElementId[];
15770
+ layout: UILayout;
15771
+ style?: UIStyle;
15772
+ props?: Record<string, unknown>;
15773
+ valueBindings?: Record<string, UIElementValueBinding>;
15774
+ /**
15775
+ * How this element arrives and leaves - the same record a Surface uses for its page animation.
15776
+ * Absent means it does neither. Read it through `elementAnimation.ts` rather than directly.
15777
+ *
15778
+ * Its own field rather than a `props` key, because it belongs to the placement and not to the
15779
+ * widget: every type has one, no renderer reads it, and a linked component instance may set it
15780
+ * even though its props come from the definition.
15781
+ */
15782
+ animation?: UIPageAnimationSettings;
15783
+ extra?: Record<string, unknown>;
15784
+ /**
15785
+ * What each asset set THIS element names resolves to, per locale.
15786
+ *
15787
+ * **Never authored and never on disk under `editor/`.** It is written while a package is being
15788
+ * assembled (`@shared/build/uiAssetSets`) and exists only in the bundle a game runs from, which
15789
+ * is why editing a widget cannot produce one and why version control never sees it.
15790
+ *
15791
+ * It sits on the element rather than in a document-wide table for the reason the story row's
15792
+ * copy gives: a table would enumerate every localized asset the project has, which is the one
15793
+ * thing a protected pack refuses to disclose. One element discloses the variants of the sets
15794
+ * that element itself uses, which is what drawing the widget already discloses.
15795
+ *
15796
+ * Keyed by the set id the props still name - the prop keeps the set id, so a widget whose map is
15797
+ * missing fails loudly at a resolver rather than quietly fetching one language's picture.
15798
+ */
15799
+ assetVariants?: AssetVariantMap;
15873
15800
  };
15874
- type Messages = typeof en;
15875
- type Flatten<T, Prefix extends string = ""> = {
15876
- [K in keyof T & string]: T[K] extends string ? `${Prefix}${K}` : Flatten<T[K], `${Prefix}${K}.`>;
15877
- }[keyof T & string];
15878
- type TranslationKey = Flatten<Messages> | (string & {});
15801
+ type UIElementValueBindingValueType = "string" | "json" | "float" | "boolean";
15802
+ type UIElementValueBinding = {
15803
+ kind: "blueprintValue";
15804
+ blueprintId: string;
15805
+ valueType: UIElementValueBindingValueType;
15806
+ }
15807
+ /**
15808
+ * Read one declared field of the list item this element is being drawn for.
15809
+ *
15810
+ * The whole reason the struct model exists. Reaching a row's own data used to take a private
15811
+ * blueprint per element - Get List Item Props into Get JSON Field with the key typed into a
15812
+ * string pin - which is three abstractions deep for "show the title". This is the same read with
15813
+ * none of them: the field is picked from a dropdown, and an element outside an item template
15814
+ * simply has nothing to bind to.
15815
+ *
15816
+ * No `valueType` of its own. The field declares one and the bound prop declares one, and the
15817
+ * conversion between them belongs to the target, not to the binding - the same reason the
15818
+ * slider's binding does not restate that a slider holds a number.
15819
+ */
15820
+ | {
15821
+ kind: "listItemField";
15822
+ fieldId: string;
15823
+ }
15824
+ /**
15825
+ * Show the value one placement gives a text parameter of the component this element is part of.
15826
+ *
15827
+ * The component's counterpart of `listItemField`, and the same kind of read: no graph, nothing
15828
+ * kept alive - the value is taken from the placement being drawn (`resolveUIComponentTextParams`),
15829
+ * so the editing canvas draws each placement with its own words, and the game translates each
15830
+ * through the placement's own unit or reads the key it names. Only a text parameter answers one,
15831
+ * and only a widget's words take one; outside a placement (the component's own editor) the
15832
+ * element's own words are drawn, as sample text.
15833
+ */
15834
+ | {
15835
+ kind: "componentParam";
15836
+ paramId: string;
15837
+ }
15838
+ /**
15839
+ * Show the value the page this element is on was opened with for one of its text parameters.
15840
+ *
15841
+ * The page's counterpart of `componentParam`, read the same way: no graph - the value is taken from
15842
+ * the props the page was opened with (`resolveUIPageTextParams`), words given at the opening shown
15843
+ * as given, the declared default translated through the page's own unit. Only a text parameter
15844
+ * answers one, only a widget's words take one, and only on a page: a component's insides and a
15845
+ * Game UI have no page parameters to show. The editing canvas, where the page has not been opened,
15846
+ * draws the default, or the element's own words as sample text while the default is empty.
15847
+ */
15848
+ | {
15849
+ kind: "pageParam";
15850
+ paramId: string;
15851
+ };
15852
+ type UILayout = {
15853
+ x: number;
15854
+ y: number;
15855
+ width: number;
15856
+ height: number;
15857
+ rotation?: number;
15858
+ opacity?: number;
15859
+ visible?: boolean;
15860
+ /** When true, width and height stay proportional in the inspector and on-canvas resize. */
15861
+ lockAspectRatio?: boolean;
15862
+ };
15863
+ type UIStyle = Record<string, unknown>;
15864
+ type GameTestLogLevel = "verbose" | "info" | "success" | "warning" | "error";
15865
+ type GameTestExitReason =
15866
+ /** Closed from inside the game - the window's own close, or the engine quitting itself. */
15867
+ "closed-by-user"
15868
+ /** The host asked: an explicit stop, or the run being cancelled. */
15869
+ | "stopped-by-host"
15870
+ /** Non-zero exit code, a fatal signal, or an uncaught exception in the game's main process. */
15871
+ | "crashed"
15872
+ /** Never got far enough to run: compile failed, or the runner binary would not spawn. */
15873
+ | "failed-to-start";
15874
+ type GameTestExit = {
15875
+ reason: GameTestExitReason;
15876
+ code: number | null;
15877
+ signal: string | null;
15878
+ };
15879
+ type GameTestChoiceOption = {
15880
+ index: number;
15881
+ text: string;
15882
+ /** Offered but not selectable: the option's `disabledWhen` condition holds. */
15883
+ disabled: boolean;
15884
+ };
15885
+ type GameTestEvent = {
15886
+ kind: "console";
15887
+ level: GameTestLogLevel;
15888
+ source: string;
15889
+ message: string;
15890
+ }
15891
+ /**
15892
+ * An uncaught error inside the running game - `window.onerror` / `unhandledrejection` in its
15893
+ * renderer, `uncaughtException` / `unhandledRejection` in its main process. The game runtime had
15894
+ * no such hook before this pipeline; an uncaught renderer exception was reported nowhere.
15895
+ */
15896
+ | {
15897
+ kind: "runtime-error";
15898
+ scope: "renderer" | "main";
15899
+ message: string;
15900
+ stack?: string;
15901
+ }
15902
+ /** The engine reached an ending (`event:state.end`), forwarded out of the game's renderer. */
15903
+ | {
15904
+ kind: "game-end";
15905
+ }
15906
+ /**
15907
+ * An `/ending` row ran, naming the ending it declares.
15908
+ *
15909
+ * Beside `game-end` rather than replacing it: `game-end` also fires for a story that simply ran
15910
+ * out of rows, which has no ending to name. A test whose pass condition is "*this* ending" can
15911
+ * only be written against this one.
15912
+ */
15913
+ | {
15914
+ kind: "ending";
15915
+ endingId: string;
15916
+ name: string;
15917
+ }
15918
+ /**
15919
+ * A choice menu is on screen, with every option it evaluated.
15920
+ *
15921
+ * Fires each time a menu mounts, so a rollback into the same choice reports it again. The list
15922
+ * is what `choose` addresses; see {@link GameTestChoiceOption}.
15923
+ */
15924
+ | {
15925
+ kind: "choice";
15926
+ options: GameTestChoiceOption[];
15927
+ } | {
15928
+ kind: "exit";
15929
+ exit: GameTestExit;
15930
+ };
15931
+ type GameTestCommand =
15932
+ /**
15933
+ * Begin a story at a scene.
15934
+ *
15935
+ * Necessary because a test session boots to the main app surface - the title screen - and no
15936
+ * story runs until something starts one. The caller supplies the target, exactly as the
15937
+ * blueprint behind a Start button does.
15938
+ */
15939
+ {
15940
+ kind: "start";
15941
+ storyId: string;
15942
+ sceneId: string;
15943
+ }
15944
+ /** One click on the dialogue: finish the line being typed, or move to the next. */
15945
+ | {
15946
+ kind: "advance";
15947
+ }
15948
+ /** Pick an option of the choice currently on screen, by the index the `choice` event reported. */
15949
+ | {
15950
+ kind: "choose";
15951
+ index: number;
15952
+ };
15953
+ type UITemplateSurfacePlacement = {
15954
+ kind: UISurfaceKind;
15955
+ /** Required for `stageSurface`; the game-UI slot the surface mounts into. */
15956
+ slotId?: UIStageSlotId;
15957
+ };
15958
+ interface AssetTransferManifestEntry {
15959
+ /**
15960
+ * The asset's id in the source project, which the importing project reuses.
15961
+ *
15962
+ * Reusing it is what lets the rows that travelled alongside keep pointing at their files
15963
+ * without being rewritten, and it makes a second paste of the same clipboard a no-op: the id
15964
+ * is already in the library, so there is nothing to import.
15965
+ */
15966
+ assetId: string;
15967
+ /** Author-facing file name, extension included: what the file is called once imported. */
15968
+ fileName: string;
15969
+ /**
15970
+ * The `AssetType` value as a string.
15971
+ *
15972
+ * Structural for the reason `assetSet.ts` gives for the same field: the enum lives under the
15973
+ * renderer and cannot be imported here.
15974
+ */
15975
+ type: string;
15976
+ /** Size in bytes, when the offering side already knew it. */
15977
+ size?: number;
15978
+ /**
15979
+ * Whether the payload is a directory rather than a single file.
15980
+ *
15981
+ * True for model bundles, the one asset type whose contents are a tree. It decides both halves
15982
+ * of the transfer: the grant covering the entry reaches everything below the path, and the
15983
+ * importing project copies the tree instead of writing bytes.
15984
+ */
15985
+ isDirectory?: boolean;
15986
+ }
15879
15987
  type PrivilegedWriteBatchEntry = {
15880
15988
  path: string;
15881
15989
  encoding?: FsTextEncoding;
@@ -16170,6 +16278,19 @@ export type SearchJumpTarget = {
16170
16278
  unitId?: string;
16171
16279
  storyId?: string;
16172
16280
  }
16281
+ /**
16282
+ * A translation table showing its orphans: the translations of one language whose line is no
16283
+ * longer in the game (deleted, or disabled), where they are listed and can be deleted.
16284
+ */
16285
+ | {
16286
+ kind: "translationOrphans";
16287
+ locale: string;
16288
+ }
16289
+ /** A voice table showing its orphans: the takes of one voice language whose line is no longer in the game. */
16290
+ | {
16291
+ kind: "voiceOrphans";
16292
+ locale: string;
16293
+ }
16173
16294
  /** A story motion, open in its own editor. */
16174
16295
  | {
16175
16296
  kind: "storyMotion";
@@ -17507,7 +17628,14 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
17507
17628
  updateElementLayouts(layoutPatches: Record<string, Partial<UILayout>>, options?: {
17508
17629
  mergeKey?: string;
17509
17630
  }): void;
17510
- updateElementProps(elementId: string, propsPatch: Record<string, unknown>): void;
17631
+ /**
17632
+ * `skipHistory` is for bookkeeping writes nobody asked for - an inspector filling in the
17633
+ * appearance keys an element predates, the moment it is selected. Recorded, that left an undo
17634
+ * step behind every first selection, so Ctrl+Z after looking around a page did nothing visible.
17635
+ */
17636
+ updateElementProps(elementId: string, propsPatch: Record<string, unknown>, options?: {
17637
+ skipHistory?: boolean;
17638
+ }): void;
17511
17639
  /**
17512
17640
  * Every widget that reads its words from `keyName`, turned into one that holds `words` itself.
17513
17641
  *
@@ -17765,11 +17893,16 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
17765
17893
  * (`resolveWorkspaceUndoScope`). A definition's own stack holds edits *inside* it, and a deleted
17766
17894
  * definition has no tab to press Ctrl+Z in.
17767
17895
  *
17896
+ * `scopeId` names another stack for the one library operation that is made from inside a page's
17897
+ * editor - {@link createComponentFromElements}, from the canvas or the outline. The same rule
17898
+ * puts that step on the page's stack: Ctrl+Z pressed where the gesture was made has to take it
17899
+ * back, and on the project's stack it instead undid whatever the page's last edit had been.
17900
+ *
17768
17901
  * Each step is a command over whole records, not a snapshot of the library: whichever direction
17769
17902
  * runs reads the records as they stand at that moment and writes them back exactly, and nothing
17770
17903
  * else in the library is touched. Tagged, so a live session drops these steps with the interface
17771
17904
  * editors' stacks (`LiveSessionService`): taking back an addition after a session would remove
17772
- * whatever the room built inside it.
17905
+ * whatever the room built inside it. (A page's stack is cleared whole when a session starts.)
17773
17906
  */
17774
17907
  private pushLibraryStep;
17775
17908
  /**
@@ -18002,8 +18135,13 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18002
18135
  /**
18003
18136
  * A new definition made of copies of elements on a page, with the logic they carry.
18004
18137
  *
18005
- * The page is not changed, so the step that takes this back is the library's - the project's
18006
- * stack, the one {@link createEmptyComponent} uses - rather than the page's.
18138
+ * The page is not changed, but the step that takes this back goes on the page's stack all the
18139
+ * same: it is made from the page's canvas or outline, and that is where Ctrl+Z and the Edit menu
18140
+ * reach (`resolveWorkspaceUndoScope`). On the project's stack - the one {@link createEmptyComponent}
18141
+ * uses, from the rail - Ctrl+Z right after it undid the page's previous edit and left the copy.
18142
+ *
18143
+ * `name` names the definition; without one it takes the element's name, or the catalog's word for
18144
+ * a component when there are several elements or the one has no name.
18007
18145
  */
18008
18146
  createComponentFromElements(surfaceId: string, elementIds: string[], name?: string): UIComponentDefinition | null;
18009
18147
  /** Recorded in the definition's own undo stack, as a page's name is in the page's. */
@@ -18098,7 +18236,10 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18098
18236
  * element on a page has no parameters to show and is left alone.
18099
18237
  */
18100
18238
  setElementComponentParamBinding(elementId: string, propPath: string, paramId: string | null): void;
18101
- updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>): void;
18239
+ /** `skipHistory` as {@link updateElementProps} takes it. */
18240
+ updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>, options?: {
18241
+ skipHistory?: boolean;
18242
+ }): void;
18102
18243
  updateComponentElementAnimation(componentId: string, elementId: string, animation: UIPageAnimationSettings | null, options?: {
18103
18244
  mergeKey?: string;
18104
18245
  }): void;
@@ -18172,6 +18313,15 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18172
18313
  private createNotificationStageTemplate;
18173
18314
  private createChoiceStageTemplate;
18174
18315
  private createNvlStageTemplate;
18316
+ /**
18317
+ * The actions the project's dialogue box reads on with, as far as a new NVL page can follow them.
18318
+ *
18319
+ * Read from the box's graphs the way the engine's own NVL page reads them
18320
+ * (`resolveDialogueAdvanceActionIds`), and kept to actions the project still defines - an
18321
+ * enablement naming a missing one does nothing, and copying it onto a new surface would only
18322
+ * carry the dead entry further.
18323
+ */
18324
+ private resolveDialogueAdvanceActionsForStarter;
18175
18325
  /** Value graph: `Init -> Return Value` fed by `Get List Item Props -> Get JSON Field(propsPath)`. */
18176
18326
  private createListItemPropsValueGraph;
18177
18327
  /**
@@ -18186,8 +18336,24 @@ declare class UIDocumentService extends Service<UIDocumentService> implements IU
18186
18336
  * Panel (`nl.container`) because collection widgets like `nl.nvl.list` do not expose a
18187
18337
  * `Mouse Click` head; the panel's own Mouse Click plus Element Click on the interaction layer
18188
18338
  * and the list cover every click region.
18339
+ *
18340
+ * The page's wiring when the project has no action its dialogue box reads on with; see
18341
+ * {@link createNvlActionNextGraph} for the one it gets when it has.
18189
18342
  */
18190
18343
  private createNvlNextGraph;
18344
+ /**
18345
+ * Event graph for an NVL page in a project whose dialogue box reads on with actions: each of
18346
+ * those actions runs `Next`, and so does a click on the NVL list.
18347
+ *
18348
+ * The actions bring the keys, the gamepad and a click anywhere on the page that is not on a
18349
+ * control - the same presses that read the box on. The list is the exception: it is a control,
18350
+ * and an action stands down over a control (`pointerInputClaimedByControl`), so a click on the
18351
+ * lines would otherwise do nothing. `Element Click` on the list hears exactly that click and no
18352
+ * other, so no press reads on twice and no widget answers a gesture the page answers as well.
18353
+ *
18354
+ * On the surface's blueprint, because only a surface answers an action.
18355
+ */
18356
+ private createNvlActionNextGraph;
18191
18357
  private configureDefaultNotificationBlueprints;
18192
18358
  private configureDefaultChoiceBlueprints;
18193
18359
  private configureDefaultNvlBlueprints;
@@ -18899,6 +19065,12 @@ type BlueprintInspectorParamDef = {
18899
19065
  * dynamically provided options via `dynamicOptionsSource`.
18900
19066
  */
18901
19067
  options?: BlueprintInspectorParamSelectOption[];
19068
+ /**
19069
+ * For `kind: "select"`: the options' values are value types, so the card shows each by the type's
19070
+ * word (`formatBlueprintValueTypeLabel`) rather than by its `label`, which stays the English the
19071
+ * command line prints.
19072
+ */
19073
+ optionsAreValueTypes?: boolean;
18902
19074
  /**
18903
19075
  * For `kind: "select"` without static `options`: the flow projection
18904
19076
  * populates options from context data keyed by this source id.
@@ -19492,7 +19664,9 @@ interface IUIDocumentService extends IService {
19492
19664
  updateElementLayouts(layoutPatches: Record<string, Partial<UILayout>>, options?: {
19493
19665
  mergeKey?: string;
19494
19666
  }): void;
19495
- updateElementProps(elementId: string, propsPatch: Record<string, unknown>): void;
19667
+ updateElementProps(elementId: string, propsPatch: Record<string, unknown>, options?: {
19668
+ skipHistory?: boolean;
19669
+ }): void;
19496
19670
  ensureElementBlueprintValueBinding(elementId: string, propPath: string, input: {
19497
19671
  valueType: UIElementValueBindingValueType;
19498
19672
  displayName?: string;
@@ -19532,7 +19706,9 @@ interface IUIDocumentService extends IService {
19532
19706
  updateComponentElementLayouts(componentId: string, layoutPatches: Record<string, Partial<UILayout>>, options?: {
19533
19707
  mergeKey?: string;
19534
19708
  }): void;
19535
- updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>): void;
19709
+ updateComponentElementProps(componentId: string, elementId: string, propsPatch: Record<string, unknown>, options?: {
19710
+ skipHistory?: boolean;
19711
+ }): void;
19536
19712
  updateComponentElementExtra(componentId: string, elementId: string, extraPatch: Record<string, unknown>): void;
19537
19713
  renameComponentElement(componentId: string, elementId: string, name: string): void;
19538
19714
  reorderComponentChildren(componentId: string, parentId: string, orderedChildIds: string[]): void;