@lingfan-engine/editor 0.0.0-stage → 0.1.2

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 (79) hide show
  1. package/LICENSE +201 -0
  2. package/package.json +39 -3
  3. package/src/chapters/build.ts +180 -0
  4. package/src/chapters/index.ts +34 -0
  5. package/src/chapters/types.ts +71 -0
  6. package/src/contracts/diagnostics.ts +62 -0
  7. package/src/contracts/editing.ts +26 -0
  8. package/src/contracts/forms.ts +69 -0
  9. package/src/contracts/index.ts +22 -0
  10. package/src/contracts/texts.ts +204 -0
  11. package/src/diagnostics/analyze.ts +161 -0
  12. package/src/diagnostics/grouping.ts +164 -0
  13. package/src/diagnostics/index.ts +35 -0
  14. package/src/diagnostics/message.ts +60 -0
  15. package/src/diagnostics/refs.ts +123 -0
  16. package/src/diagnostics/symbols.ts +221 -0
  17. package/src/editing/columnGrouping.ts +331 -0
  18. package/src/editing/graphConnect.ts +88 -0
  19. package/src/editing/index.ts +72 -0
  20. package/src/editing/pointers.ts +291 -0
  21. package/src/editing/saveNormalization.ts +58 -0
  22. package/src/editing/session.ts +134 -0
  23. package/src/editing/storyOps.ts +268 -0
  24. package/src/editing/tsSourceGuard.ts +40 -0
  25. package/src/element/drag.ts +22 -0
  26. package/src/element/dragCreate.ts +60 -0
  27. package/src/element/index.ts +11 -0
  28. package/src/i18n/coverage.ts +191 -0
  29. package/src/i18n/extract.ts +38 -0
  30. package/src/i18n/index.ts +47 -0
  31. package/src/i18n/reconcile.ts +71 -0
  32. package/src/i18n/skeleton.ts +112 -0
  33. package/src/i18n/storyKeys.ts +27 -0
  34. package/src/i18n/surfaces.ts +54 -0
  35. package/src/i18n/table.ts +235 -0
  36. package/src/index.ts +342 -0
  37. package/src/layout/index.ts +14 -0
  38. package/src/layout/steps.ts +378 -0
  39. package/src/schema/catalog.ts +300 -0
  40. package/src/schema/elementForms.ts +307 -0
  41. package/src/schema/elementPalette.ts +100 -0
  42. package/src/schema/forms.ts +196 -0
  43. package/src/schema/index.ts +55 -0
  44. package/src/schema/opSchemas/commands.ts +374 -0
  45. package/src/schema/opSchemas/fragments.ts +24 -0
  46. package/src/schema/opSchemas/index.ts +16 -0
  47. package/src/schema/opSchemas/map.ts +134 -0
  48. package/src/schema/opSchemas/types.ts +42 -0
  49. package/src/schema/opSchemas/validate.ts +184 -0
  50. package/src/schema/surface.ts +101 -0
  51. package/src/schema/validation.ts +92 -0
  52. package/src/schema/walk.ts +167 -0
  53. package/src/script/expr/audit/index.ts +8 -0
  54. package/src/script/expr/audit/parser.ts +370 -0
  55. package/src/script/expr/audit/token.ts +23 -0
  56. package/src/script/expr/build.ts +203 -0
  57. package/src/script/expr/index.ts +17 -0
  58. package/src/script/expr/registry.ts +130 -0
  59. package/src/script/index.ts +168 -0
  60. package/src/script/vocabulary-gen.ts +202 -0
  61. package/src/script/vocabulary.g.ts +175 -0
  62. package/src/script/words/audio.ts +114 -0
  63. package/src/script/words/cells.ts +78 -0
  64. package/src/script/words/compile-time.ts +27 -0
  65. package/src/script/words/composite.ts +125 -0
  66. package/src/script/words/elements.ts +202 -0
  67. package/src/script/words/extension.ts +25 -0
  68. package/src/script/words/flow.ts +391 -0
  69. package/src/script/words/index.ts +23 -0
  70. package/src/script/words/interaction.ts +44 -0
  71. package/src/script/words/minigame.ts +66 -0
  72. package/src/script/words/narrative.ts +261 -0
  73. package/src/script/words/save.ts +54 -0
  74. package/src/script/words/vars.ts +155 -0
  75. package/src/script/words/video.ts +80 -0
  76. package/src/shared/guards.ts +19 -0
  77. package/src/shared/index.ts +6 -0
  78. package/src/shared/pointer.ts +23 -0
  79. package/README.md +0 -3
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Script 词汇层 · **音频域**(bgm / se / ambient / voice 四通道 + 对称 stop)。
3
+ * 生命周期由作者手动控制(状态走 `__audio_*` 系统键 ⇒ 快照/存档/回溯随行)。
4
+ * Options 派生自 schema(零漂移);voice 例外——autoStop 是作者侧驼峰词,
5
+ * builder 负责映射到 op 的 auto_stop 字段。
6
+ */
7
+ import type { CommandOf } from "../../schema/index.ts";
8
+
9
+ /** bgm / ambient 共用的可选项:volume 音量、loop 循环、fade 渐入毫秒、restart 重播 */
10
+ export type BgmOptions = Omit<CommandOf<"bgm">, "op" | "resource">;
11
+
12
+ /** 一次性播放(se)的可选项:只有音量 */
13
+ export type PlayOptions = Pick<BgmOptions, "volume">;
14
+
15
+ /** stop_* 共用的可选项:fade 渐出毫秒 */
16
+ export type StopOptions = Omit<CommandOf<"stop_bgm">, "op">;
17
+
18
+ /**
19
+ * 播放背景音乐:立即开始(故事继续,不等待),一直循环播放到
20
+ * {@link stopBgm} 或下一次换曲。
21
+ *
22
+ * - `loop` 缺省循环;`volume` 缺省 1(范围 0–1);`fade` 渐入毫秒(缺省 0)。
23
+ * - 同一曲重复调用保留播放位置;换曲或 `restart: true` 时从 0 重播。
24
+ * - 通道状态随快照/存档/回溯走:回溯到检查点时 BGM 自动恢复到当时的曲子。
25
+ *
26
+ * @param resource 音频资源路径。
27
+ * @param opts 可选项,见 {@link BgmOptions}。
28
+ * @returns `bgm` 故事命令。
29
+ * @example
30
+ * bgm("audio/town.mp3", { fade: 1000 });
31
+ * stopBgm();
32
+ */
33
+ export function bgm(resource: string, opts?: BgmOptions): CommandOf<"bgm"> {
34
+ return { op: "bgm", resource, ...opts };
35
+ }
36
+
37
+ /**
38
+ * 停止背景音乐:立即停止(`fade` 渐出毫秒,缺省立即),故事继续。
39
+ * @param opts 可选项:fade 渐出毫秒。
40
+ * @returns `stop_bgm` 故事命令。
41
+ */
42
+ export function stopBgm(opts?: StopOptions): CommandOf<"stop_bgm"> {
43
+ return { op: "stop_bgm", ...opts };
44
+ }
45
+
46
+ /**
47
+ * 播放一次性音效(不循环):立即开始(故事继续,不等待),播完自然结束。
48
+ * @param resource 音频资源路径。
49
+ * @param opts 可选项:volume 音量(缺省 1,范围 0–1)。
50
+ * @returns `se` 故事命令。
51
+ * @example
52
+ * se("audio/door_open.ogg");
53
+ */
54
+ export function se(resource: string, opts?: PlayOptions): CommandOf<"se"> {
55
+ return { op: "se", resource, ...opts };
56
+ }
57
+
58
+ /**
59
+ * 播放环境音:语义同 {@link bgm}(独立通道、缺省循环、状态随存档/回溯走),
60
+ * 常用于雨声、市场喧闹等持续底噪。
61
+ * @param resource 音频资源路径。
62
+ * @param opts 可选项,见 {@link BgmOptions}。
63
+ * @returns `ambient` 故事命令。
64
+ * @example
65
+ * ambient("audio/rain_loop.ogg");
66
+ */
67
+ export function ambient(
68
+ resource: string,
69
+ opts?: BgmOptions,
70
+ ): CommandOf<"ambient"> {
71
+ return { op: "ambient", resource, ...opts };
72
+ }
73
+
74
+ /** 停止环境音(`fade` 渐出毫秒,缺省立即),故事继续。 */
75
+ export function stopAmbient(opts?: StopOptions): CommandOf<"stop_ambient"> {
76
+ return { op: "stop_ambient", ...opts };
77
+ }
78
+
79
+ /** voice 的可选项:autoStop 自动停止、restart 同曲重播、volume 音量 */
80
+ export interface VoiceOptions extends PlayOptions {
81
+ autoStop?: boolean;
82
+ restart?: boolean;
83
+ }
84
+
85
+ /**
86
+ * 播放语音(独立通道、不循环):立即开始(故事继续,不等待)。
87
+ *
88
+ * - `autoStop` 缺省 true:语音不再需要时自动停止;需要手动管理时设为
89
+ * false 并用 {@link stopVoice}。
90
+ * - `restart: true` 强制从头重播同一条语音;`volume` 缺省 1。
91
+ *
92
+ * @param resource 语音资源路径。
93
+ * @param opts 可选项,见 {@link VoiceOptions}。
94
+ * @returns `voice` 故事命令。
95
+ * @example
96
+ * voice("audio/line_01.ogg", { volume: 0.9 });
97
+ */
98
+ export function voice(
99
+ resource: string,
100
+ opts?: VoiceOptions,
101
+ ): CommandOf<"voice"> {
102
+ return {
103
+ op: "voice",
104
+ resource,
105
+ ...(opts?.autoStop === undefined ? {} : { auto_stop: opts.autoStop }),
106
+ ...(opts?.restart === undefined ? {} : { restart: opts.restart }),
107
+ ...(opts?.volume === undefined ? {} : { volume: opts.volume }),
108
+ };
109
+ }
110
+
111
+ /** 停止语音(`fade` 渐出毫秒,缺省立即),故事继续。 */
112
+ export function stopVoice(opts?: StopOptions): CommandOf<"stop_voice"> {
113
+ return { op: "stop_voice", ...opts };
114
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Script 词汇层 · **具名实现槽位**(cell —— build-time 声明词)。
3
+ *
4
+ * cell 声明的实现由构建期扫描收集进生成注册物(`Stories.src/gen/fun_register.g.ts`):
5
+ * 故事 JSON 只留名字引用(guard 的 `fn` 字段),实现住生成模块——
6
+ * 「名字在数据、实现在代码、build 做名字闭合」。
7
+ *
8
+ * cell 是 **build-time 声明词**:运行期(模块被导入时,如测试/类型检查)必须纯——
9
+ * 只返回句柄、零副作用;实现文本由构建期 AST 提取,不经过本函数。
10
+ */
11
+ import type { GuardFn } from "@lingfan-engine/core";
12
+
13
+ /** 具名实现句柄:消费方(guard 等)按 face 取名字;face v1 恒 `guards` */
14
+ export interface CellHandle<N extends string = string> {
15
+ readonly face: "guards";
16
+ readonly name: N;
17
+ }
18
+
19
+ /**
20
+ * 守卫名注册表(类型扩充点):生成物尾部 `declare module "@lingfan-engine/editor"`
21
+ * 往本接口塞进每个已声明守卫名的字面量键 ⇒ `guard("…")` 的名字参数获得
22
+ * 补全与写错即红(vue-router typed-routes 同机制)。
23
+ * 空表 = `KnownGuardName` 退化为普通 string——不空注册表吓跑作者。
24
+ */
25
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- 类型扩充点:只有接口可被生成物 declare module 合并(type 别名不能),空体是有意形态
26
+ export interface GuardNameRegistry {}
27
+
28
+ /** 已声明守卫名(有声明 = 字面量联合;空 = string) */
29
+ export type KnownGuardName = keyof GuardNameRegistry extends never
30
+ ? string
31
+ : keyof GuardNameRegistry & string;
32
+
33
+ /**
34
+ * 列名注册表(类型扩充点):构建期生成的 `Stories.src/gen/stories.gen.d.ts`
35
+ * 往本接口塞进工程内每个列 id 的字面量键 ⇒ `jump("…")` 与 `option("…", "…")`
36
+ * 的目标列名获得补全、写错即红。空表退化为普通 string。
37
+ */
38
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- 类型扩充点:只有接口可被生成物 declare module 合并(type 别名不能),空体是有意形态
39
+ export interface ColumnNameRegistry {}
40
+
41
+ /** 已声明列名(有声明 = 字面量联合;空 = string) */
42
+ export type KnownColumnName = keyof ColumnNameRegistry extends never
43
+ ? string
44
+ : keyof ColumnNameRegistry & string;
45
+
46
+ /**
47
+ * 插值键注册表(类型扩充点):生成物塞进工程内所有能当 `{…}` 键的名字——
48
+ * state schema 展平键、defineVars 登记变量、产物 op 流里的声明名,
49
+ * 以及扩展命名空间前缀(生成物写成 `ext.<id>.${string}` 的字面量类型)。
50
+ * 空表退化为普通 string;富文本行内标记由词汇层的模板串类型单独豁免。
51
+ */
52
+ // eslint-disable-next-line @typescript-eslint/no-empty-object-type -- 类型扩充点:只有接口可被生成物 declare module 合并(type 别名不能),空体是有意形态
53
+ export interface InterpolationKeyRegistry {}
54
+
55
+ /** 已声明插值键(有声明 = 字面量联合;空 = string) */
56
+ export type KnownInterpolationKey = keyof InterpolationKeyRegistry extends never
57
+ ? string
58
+ : keyof InterpolationKeyRegistry & string;
59
+
60
+ /**
61
+ * 声明一个守卫实现槽位:构建期扫描收集 `impl` 生成注册物,故事数据里
62
+ * 只留名字({@link guard} 的 fn 字段)。运行期是纯声明——不执行 impl。
63
+ * 返回的句柄可直接传给 {@link guard},名字只写一次。
64
+ *
65
+ * @param name 守卫名(进故事 JSON 与生成注册物)。
66
+ * @param impl 守卫实现纯函数(构建期 AST 提取,不在运行期调用)。
67
+ * @returns 实现句柄(传给 {@link guard} 引用)。
68
+ * @example
69
+ * export const checkMoney = cell("checkMoney", (ctx, args) => true);
70
+ * guard(checkMoney, { args: { cost: 50 } });
71
+ */
72
+ export function cell<const N extends string>(
73
+ name: N,
74
+ impl: GuardFn,
75
+ ): CellHandle<N> {
76
+ void impl;
77
+ return { face: "guards", name };
78
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Script 词汇层 · **编译期构造**:只在 Stories.src 源内有意义,由 stories 构建管线静态展开,
3
+ * 运行期不提供实现(误调用即抛错)。这里只放纯编译期声明与类型,不产 op、不进 op schema。
4
+ */
5
+ import type { StoryCommand } from "@lingfan-engine/core";
6
+
7
+ /** 故事文件的 default 导出引用(jump 跨故事引用等编译期引用形态的目标类型)。 */
8
+ export type StoryFn = () => void;
9
+
10
+ /**
11
+ * 编译期定数循环:`staticFor(n, (i) => [命令…])` 在构建期展开为 n 份按序内联的静态命令,
12
+ * i 以数字字面量代换(含模板串插值)。运行期不可调用。
13
+ *
14
+ * @param count 展开份数(构建期常量)。
15
+ * @param generator 以序号产出命令数组的生成函数(构建期求值)。
16
+ * @returns 展开后的静态命令(只在 Stories.src 源内有意义;误调用即抛错)。
17
+ * @example
18
+ * staticFor(3, (i) => [say(`第 ${i} 次操练`)]);
19
+ */
20
+ export function staticFor(
21
+ _count: number,
22
+ _generator: (i: number) => readonly StoryCommand[],
23
+ ): StoryCommand[] {
24
+ throw new Error(
25
+ "staticFor 是编译期构造:由 stories 构建管线静态展开,运行期不可调用",
26
+ );
27
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Script 词汇层 · **复合词**(多 op/元素的高频组合,纯展开)。
3
+ * 复合词不产出新语义:每个函数 = 既有元素的语法糖。
4
+ */
5
+ import type { ElementNode } from "@lingfan-engine/core";
6
+
7
+ export interface ElementOptions {
8
+ id?: string;
9
+ x?: number | string;
10
+ y?: number | string;
11
+ width?: number | string;
12
+ height?: number | string;
13
+ color?: string;
14
+ size?: number | string;
15
+ halign?: string;
16
+ opacity?: number;
17
+ zindex?: number;
18
+ [attr: string]: unknown;
19
+ }
20
+
21
+ /**
22
+ * 文本元素:在场景里显示一段文字。产物是元素**节点**(数据),
23
+ * 放进 scene 列的 `elements` 数组才会出现在画面上。
24
+ * @param text 显示的文字。
25
+ * @param opts 元素属性(id/x/y/width/height/color/size/halign/opacity/zindex 等)。
26
+ * @returns text 元素节点。
27
+ * @example
28
+ * textElement("第二章 · 广场", { x: "5%", y: "12%", size: 32 });
29
+ */
30
+ export function textElement(text: string, opts?: ElementOptions): ElementNode {
31
+ return { type: "text", text, ...opts };
32
+ }
33
+
34
+ /**
35
+ * 图片元素:在场景里显示一张图。产物是元素**节点**(数据)。
36
+ * @param source 图片资源路径。
37
+ * @param opts 元素属性(id/x/y/width/height/opacity/zindex 等)。
38
+ * @returns image 元素节点。
39
+ * @example
40
+ * imageElement("images/market.png", { x: "10%", y: "30%", width: "40%" });
41
+ */
42
+ export function imageElement(
43
+ source: string,
44
+ opts?: ElementOptions,
45
+ ): ElementNode {
46
+ return { type: "image", source, ...opts };
47
+ }
48
+
49
+ /**
50
+ * 按钮元素:可点击的按钮;`nav` 给出点击后跳转的目标列名。
51
+ * 产物是元素**节点**(数据)。
52
+ * @param text 按钮上的文字。
53
+ * @param opts 元素属性 + nav(点击跳转的目标列名)。
54
+ * @returns button 元素节点。
55
+ * @example
56
+ * buttonElement("开始游戏", { nav: "intro", y: "60%" });
57
+ */
58
+ export function buttonElement(
59
+ text: string,
60
+ opts?: ElementOptions & { nav?: string },
61
+ ): ElementNode {
62
+ return { type: "button", text, ...opts };
63
+ }
64
+
65
+ /**
66
+ * 场景设置:背景 + 可选标题两个元素。
67
+ * 产物 = 元素**数组**(作者展开进 scene 列的 elements)。
68
+ * @param backgroundPath 背景图资源路径(铺满整屏)。
69
+ * @param title 场景标题文字(可省略,出现在画面上方居中)。
70
+ * @param opts 可选项:bgOpacity 背景不透明度、titleSize 标题字号、
71
+ * titleColor 标题颜色。
72
+ * @returns 背景与标题元素节点数组(可省略标题)。
73
+ * @example
74
+ * export default scene("square", () => ({
75
+ * type: "menu",
76
+ * elements: [...sceneSetup("images/square.png", "城镇广场")],
77
+ * columns: { ... },
78
+ * }));
79
+ */
80
+ export function sceneSetup(
81
+ backgroundPath: string,
82
+ title?: string,
83
+ opts?: {
84
+ bgOpacity?: number;
85
+ titleSize?: number | string;
86
+ titleColor?: string;
87
+ },
88
+ ): ElementNode[] {
89
+ const bg: ElementNode = {
90
+ type: "background",
91
+ source: backgroundPath,
92
+ x: "0",
93
+ y: "0",
94
+ width: "100%",
95
+ height: "100%",
96
+ ...(opts?.bgOpacity === undefined ? {} : { opacity: opts.bgOpacity }),
97
+ };
98
+ const heading =
99
+ title === undefined
100
+ ? []
101
+ : [
102
+ {
103
+ type: "text",
104
+ text: title,
105
+ x: "5%",
106
+ y: "12%",
107
+ width: "90%",
108
+ halign: "center",
109
+ ...(opts?.titleSize === undefined ? {} : { size: opts.titleSize }),
110
+ ...(opts?.titleColor === undefined
111
+ ? {}
112
+ : { color: opts.titleColor }),
113
+ } satisfies ElementNode,
114
+ ];
115
+ return [bg, ...heading];
116
+ }
117
+
118
+ /**
119
+ * 列的 `type` 字段(game 缺省不写——与序列化口径一致)。
120
+ * @param type 列类型:menu(菜单列)或 ui(界面列)。
121
+ * @returns 带类型标记的对象,展开进列定义。
122
+ */
123
+ export function sceneType(type: "menu" | "ui"): { type: "menu" | "ui" } {
124
+ return { type };
125
+ }
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Script 词汇层 · **元素域**(show / hide / background / bg_switch / zindex / style /
3
+ * window / animate / animate_block / transition / shake / text_typewriter)。
4
+ * x/y = 数字(px)或 CSS 长度串(`"50%"`)——与元素系统契约一致。
5
+ * Options 派生自 schema(零漂移);返回类型 = `CommandOf<op>` 判别联合成员。
6
+ */
7
+ import type { CommandOf, ScriptValue } from "../../schema/index.ts";
8
+
9
+ /** show 的可选项:x/y 位置(数字 px 或 CSS 长度串)、id/name 标识、background 背景位 */
10
+ export type ShowOptions = Omit<CommandOf<"show">, "op" | "target">;
11
+
12
+ /**
13
+ * 在画面上追加一个元素:`target` 是资源路径(落到元素的 source)。
14
+ * 故事立即继续,不等待。
15
+ *
16
+ * - `x` / `y` 定位(数字 px 或 `"50%"` 长度串);`id` / `name` 给元素
17
+ * 起标识(之后 {@link hide} / {@link setZ} / {@link style} / {@link animate}
18
+ * 按它寻址)。
19
+ * - `background: true` 表示这是一张背景:先清掉旧背景再追加,固定在最底层。
20
+ *
21
+ * @param target 元素资源路径。
22
+ * @param opts 可选项,见 {@link ShowOptions}。
23
+ * @returns `show` 故事命令。
24
+ * @example
25
+ * show("images/npc_guide.png", { x: "30%", y: "40%", id: "guide" });
26
+ */
27
+ export function show(target: string, opts?: ShowOptions): CommandOf<"show"> {
28
+ return { op: "show", target, ...opts };
29
+ }
30
+
31
+ /**
32
+ * 从画面移除元素:按 `id` / `name` / 资源路径匹配(含子元素)。
33
+ * 没找到就什么都不做(幂等,不报错)。立即生效、故事继续。
34
+ * @param target 要移除的元素(id、name 或资源路径)。
35
+ * @returns `hide` 故事命令。
36
+ * @example
37
+ * hide("guide");
38
+ */
39
+ export function hide(target: string): CommandOf<"hide"> {
40
+ return { op: "hide", target };
41
+ }
42
+
43
+ /**
44
+ * 换背景:先清掉旧背景,再把新图追加为固定在最底层的背景元素。
45
+ * 立即生效、故事继续。
46
+ * @param resource 背景图资源路径。
47
+ * @returns `background` 故事命令。
48
+ * @example
49
+ * background("images/inn_night.png");
50
+ */
51
+ export function background(resource: string): CommandOf<"background"> {
52
+ return { op: "background", resource };
53
+ }
54
+
55
+ /**
56
+ * 切换背景(与 {@link background} 同语义的另一形式):先清旧背景再追加新背景。
57
+ * @param resource 背景图资源路径。
58
+ * @returns `bg_switch` 故事命令。
59
+ */
60
+ export function bgSwitch(resource: string): CommandOf<"bg_switch"> {
61
+ return { op: "bg_switch", resource };
62
+ }
63
+
64
+ /**
65
+ * 调整元素层级(z 序)。目标元素未找到时报错停机。
66
+ * @param target 元素(id、name 或资源路径)。
67
+ * @param value 新层级值(数字越大越靠上)。
68
+ * @returns `zindex` 故事命令。
69
+ */
70
+ export function setZ(target: string, value: number): CommandOf<"zindex"> {
71
+ return { op: "zindex", target, value };
72
+ }
73
+
74
+ /**
75
+ * 批量修改元素的展示属性(键限于元素系统支持的属性集,未知键报错停机)。
76
+ * @param target 元素(id、name 或资源路径)。
77
+ * @param props 属性名 → 新值的映射。
78
+ * @returns `style` 故事命令。
79
+ * @example
80
+ * style("guide", { opacity: 0.6 });
81
+ */
82
+ export function style(
83
+ target: string,
84
+ props: Record<string, ScriptValue>,
85
+ ): CommandOf<"style"> {
86
+ return { op: "style", target, props: { ...props } };
87
+ }
88
+
89
+ /** 对话框显隐模式:auto 跟随对话状态(缺省)、show 显示、hide 隐藏 */
90
+ export type DialogWindowMode = CommandOf<"window">["mode"];
91
+
92
+ /**
93
+ * 控制对话框窗口的显隐:`"auto"` 跟随对话状态(默认行为)、
94
+ * `"show"` 强制显示、`"hide"` 强制隐藏。立即生效、故事继续。
95
+ * @param mode 显隐模式。
96
+ * @returns `window` 故事命令。
97
+ * @example
98
+ * dialogWindow("hide"); // 无对话演出时收起对话框
99
+ */
100
+ export function dialogWindow(mode: DialogWindowMode): CommandOf<"window"> {
101
+ return { op: "window", mode };
102
+ }
103
+
104
+ /** animate 的可选项:duration 时长(秒,缺省 0.3)、easing 缓动名(缺省 EaseOutQuad) */
105
+ export type AnimateOptions = Omit<
106
+ CommandOf<"animate">,
107
+ "op" | "target" | "property" | "value"
108
+ >;
109
+
110
+ /**
111
+ * 让元素的某个属性平滑过渡到目标值:起点取元素当前值(无值按 0),
112
+ * 结束值写回元素。故事**不等待**动画播完,直接继续执行下一句。
113
+ * @param target 元素(id、name 或资源路径)。
114
+ * @param property 要动的属性名(如 x / opacity)。
115
+ * @param value 目标值。
116
+ * @param opts 可选项:duration 秒数(缺省 0.3)、easing 缓动名。
117
+ * @returns `animate` 故事命令。
118
+ * @example
119
+ * animate("guide", "x", "60%", { duration: 0.5 });
120
+ */
121
+ export function animate(
122
+ target: string,
123
+ property: string,
124
+ value: number,
125
+ opts?: AnimateOptions,
126
+ ): CommandOf<"animate"> {
127
+ return { op: "animate", target, property, value, ...opts };
128
+ }
129
+
130
+ export type AnimateBlockOptions = Omit<
131
+ CommandOf<"animate_block">,
132
+ "op" | "target"
133
+ >;
134
+
135
+ /**
136
+ * 让元素的多个属性**同时**平滑过渡(共用一个 duration,并行插值)。
137
+ * 故事不等待;需要先后顺序时改用多条 {@link animate}。
138
+ * @param target 元素(id、name 或资源路径)。
139
+ * @param opts 目标属性(x/y/opacity/rotation/scale,数字)+ duration 秒数
140
+ * (缺省 0.3)+ easing 缓动名(缺省 EaseOutQuad)。
141
+ * @returns `animate_block` 故事命令。
142
+ * @example
143
+ * animateBlock("guide", { x: 300, opacity: 1, duration: 0.6 });
144
+ */
145
+ export function animateBlock(
146
+ target: string,
147
+ opts?: AnimateBlockOptions,
148
+ ): CommandOf<"animate_block"> {
149
+ return { op: "animate_block", target, ...opts };
150
+ }
151
+
152
+ /**
153
+ * 全屏转场效果(如淡入淡出、黑屏):`type` 给出转场名(必填),
154
+ * `duration` 控制时长(秒,缺省 0.5)。立即生效、故事继续。
155
+ * @param type 转场类型名。
156
+ * @param duration 转场时长(秒)。
157
+ * @returns `transition` 故事命令。
158
+ * @example
159
+ * transition("fade", 0.8);
160
+ */
161
+ export function transition(
162
+ type: string,
163
+ duration?: number,
164
+ ): CommandOf<"transition"> {
165
+ return {
166
+ op: "transition",
167
+ type,
168
+ ...(duration === undefined ? {} : { duration }),
169
+ };
170
+ }
171
+
172
+ /** shake 的可选项:intensity 震动强度(缺省 8)、duration 时长(秒) */
173
+ export type ShakeOptions = Omit<CommandOf<"shake">, "op">;
174
+
175
+ /**
176
+ * 让画面震动一下(表现强调)。故事立即继续,不等待。
177
+ * @param opts 可选项:intensity 强度(缺省 8)、duration 时长(秒)。
178
+ * @returns `shake` 故事命令。
179
+ * @example
180
+ * shake({ intensity: 12 });
181
+ */
182
+ export function shake(opts?: ShakeOptions): CommandOf<"shake"> {
183
+ return { op: "shake", ...opts };
184
+ }
185
+
186
+ /** text_typewriter 的可选项:enabled 开关、speed 打字速度(字符/秒,至少其一) */
187
+ export type TypewriterOptions = Omit<CommandOf<"text_typewriter">, "op">;
188
+
189
+ /**
190
+ * 设置全局打字机效果:`enabled` 开关、`speed` 打字速度(字符/秒)——
191
+ * 两者至少给一个。与玩家自己的偏好设置分离:玩家显式设置时以玩家为准。
192
+ * 立即生效、故事继续。
193
+ * @param opts 可选项:enabled 开关、speed 速度(字符/秒)。
194
+ * @returns `text_typewriter` 故事命令。
195
+ * @example
196
+ * textTypewriter({ enabled: true, speed: 30 });
197
+ */
198
+ export function textTypewriter(
199
+ opts?: TypewriterOptions,
200
+ ): CommandOf<"text_typewriter"> {
201
+ return { op: "text_typewriter", ...opts };
202
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Script 词汇层 · **扩展域**(extOp 通用出口)。
3
+ *
4
+ * 扩展 op = 宿主经 `EngineOptions.extensions` 注册的自定义 op。本出口只产
5
+ * 数据、不持白名单——持有就是重复定义真相。放行与否归两端把守:
6
+ * - **构建期**:清单 `extensions` 声明扩展模块 → `buildStories` 校验故事里的 op 名
7
+ * 是否被声明扩展提供(未被放行 = 构建期拦截,fail-early);
8
+ * - **运行期**:opRegistry 注册制,未注册 = `unknown-op` fail-closed。
9
+ */
10
+ import type { StoryCommand } from "@lingfan-engine/core";
11
+
12
+ /**
13
+ * 扩展 op 通用构造:op 恒由首参决定(负载展开在前、op 在后——负载不得劫持 op 键)。
14
+ * @param op 扩展 op 名(须由宿主注册的扩展模块提供)。
15
+ * @param payload 扩展负载(原样透传,形状由扩展自定)。
16
+ * @returns 扩展故事命令。
17
+ * @example
18
+ * extOp("weather:set", { sky: "rain" });
19
+ */
20
+ export function extOp(
21
+ op: string,
22
+ payload?: Record<string, unknown>,
23
+ ): StoryCommand {
24
+ return { ...payload, op };
25
+ }