dse-core 0.1.0__tar.gz

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 (38) hide show
  1. dse_core-0.1.0/LICENSE +21 -0
  2. dse_core-0.1.0/PKG-INFO +117 -0
  3. dse_core-0.1.0/README.md +87 -0
  4. dse_core-0.1.0/pyproject.toml +59 -0
  5. dse_core-0.1.0/setup.cfg +4 -0
  6. dse_core-0.1.0/src/dse_core/__init__.py +40 -0
  7. dse_core-0.1.0/src/dse_core/cli.py +148 -0
  8. dse_core-0.1.0/src/dse_core/example/content/starter.yml +79 -0
  9. dse_core-0.1.0/src/dse_core/example/dse.yml +64 -0
  10. dse_core-0.1.0/src/dse_core/graph.py +125 -0
  11. dse_core-0.1.0/src/dse_core/loader.py +99 -0
  12. dse_core-0.1.0/src/dse_core/manifest.py +76 -0
  13. dse_core-0.1.0/src/dse_core/modes.py +72 -0
  14. dse_core-0.1.0/src/dse_core/plugins/__init__.py +5 -0
  15. dse_core-0.1.0/src/dse_core/plugins/combat.py +258 -0
  16. dse_core-0.1.0/src/dse_core/plugins/console_io.py +60 -0
  17. dse_core-0.1.0/src/dse_core/plugins/engine.py +204 -0
  18. dse_core-0.1.0/src/dse_core/plugins/scenes.py +129 -0
  19. dse_core-0.1.0/src/dse_core/plugins/state.py +151 -0
  20. dse_core-0.1.0/src/dse_core/runtime.py +152 -0
  21. dse_core-0.1.0/src/dse_core/safety.py +101 -0
  22. dse_core-0.1.0/src/dse_core/schema/scene.schema.json +139 -0
  23. dse_core-0.1.0/src/dse_core/schema.py +38 -0
  24. dse_core-0.1.0/src/dse_core/sdk.py +146 -0
  25. dse_core-0.1.0/src/dse_core.egg-info/PKG-INFO +117 -0
  26. dse_core-0.1.0/src/dse_core.egg-info/SOURCES.txt +36 -0
  27. dse_core-0.1.0/src/dse_core.egg-info/dependency_links.txt +1 -0
  28. dse_core-0.1.0/src/dse_core.egg-info/entry_points.txt +2 -0
  29. dse_core-0.1.0/src/dse_core.egg-info/requires.txt +7 -0
  30. dse_core-0.1.0/src/dse_core.egg-info/top_level.txt +1 -0
  31. dse_core-0.1.0/tests/test_cli.py +74 -0
  32. dse_core-0.1.0/tests/test_engine.py +70 -0
  33. dse_core-0.1.0/tests/test_graph.py +65 -0
  34. dse_core-0.1.0/tests/test_loader.py +107 -0
  35. dse_core-0.1.0/tests/test_manifest.py +39 -0
  36. dse_core-0.1.0/tests/test_modes.py +29 -0
  37. dse_core-0.1.0/tests/test_safety.py +65 -0
  38. dse_core-0.1.0/tests/test_state.py +93 -0
dse_core-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 全哥 (siwpan)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: dse-core
3
+ Version: 0.1.0
4
+ Summary: 字典场景引擎(Dictionary Scene Engine)—— 场景即字典,能力即插件
5
+ Author: 全哥 (siwpan)
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitee.com/siwpan/cordis-py
8
+ Project-URL: Repository, https://gitee.com/siwpan/cordis-py
9
+ Project-URL: Issues, https://gitee.com/siwpan/cordis-py/issues
10
+ Keywords: text-adventure,interactive-fiction,game-engine,plugin-architecture,narrative-engine,dialogue-graph
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Games/Entertainment
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: PyYAML>=6.0
24
+ Requires-Dist: jsonschema>=4.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == "dev"
27
+ Requires-Dist: build; extra == "dev"
28
+ Requires-Dist: twine; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # dse-core · 字典场景引擎(Python 版)
32
+
33
+ > Dictionary Scene Engine —— 场景即字典,能力即插件。
34
+ > 本仓库是引擎的 **Python 原生实现**(cordis-py),Node/Cordis 版见姊妹仓库
35
+ > [dse-core](https://gitee.com/siwpan/dse-core)。立项计划书见 [PLAN.md](PLAN.md)。
36
+
37
+ ## 30 秒上手
38
+
39
+ ```bash
40
+ pip install -e . # 开发安装(或 pip install dse-core)
41
+ dse play # 玩自带示例内容包(战斗线最小可玩版)
42
+ dse play --input "1,1,a,a,a" # 脚本模式:无交互跑完整条线路(CI 友好)
43
+ dse scenes # 列出场景
44
+ dse check # 校验内容(悬空跳转等),有问题退出码 1
45
+ ```
46
+
47
+ 自带示例工程随包发布(`src/dse_core/example/`):在含 `dse.yml` 的目录里
48
+ 运行 `dse play` 玩你自己的工程,否则玩包内示例。
49
+
50
+ ## 内容包 = 数据,不是代码
51
+
52
+ 与 Node 版最大的差异:内容包是 **YAML 数据文件**,不是可执行模块。
53
+ 条件表达式经 **AST 白名单求值**(仅比较/布尔/算术/下标,禁调用、禁下划线属性)——
54
+ 不需要 import 任何用户代码,也没有 `new Function`。
55
+
56
+ ```yaml
57
+ # content/starter.yml(节选)
58
+ pack:
59
+ id: starter
60
+ name: 启程
61
+ entry: start
62
+ scenes:
63
+ start:
64
+ title: 村口 · 启程
65
+ text: '天没亮透……三条路摆在你面前。'
66
+ choices:
67
+ - { text: '往北进山', do: { 'flags.starter.mountain': true }, goto: forest }
68
+ forest:
69
+ text: '……一只独眼狼从阴影里站起来。'
70
+ choices:
71
+ - { text: '拔剑', goto: wolf-fight }
72
+ wolf-fight:
73
+ mode: battle # 非 normal 模式由能力插件接管
74
+ battle: { enemy: wolf, onWin: forest-win, onLose: forest-lose, onFlee: start }
75
+ ```
76
+
77
+ ## 架构
78
+
79
+ ```
80
+ dse_core/
81
+ ├── modes.py # 模式注册表(scene.mode 唯一真源)
82
+ ├── graph.py # 场景图静态分析(可达性/悬空/结局/最短路径,纯函数)
83
+ ├── manifest.py # 内容包元数据(规范化/校验)
84
+ ├── loader.py # 工程加载(dse.yml + YAML 内容包合并)
85
+ ├── schema.py # JSON Schema 校验(schema/scene.schema.json)
86
+ ├── safety.py # 条件表达式 AST 白名单求值
87
+ ├── runtime.py # 插件运行时(Service/Context/依赖拓扑/事件)—— Cordis 的 Python 原生替代
88
+ ├── sdk.py # 编程式门面(create_engine / play / inspect_project)
89
+ ├── cli.py # 命令行 dse:play / scenes / info / check
90
+ └── plugins/ # 内核层 scenes/state/console_io/engine + 参考能力插件 combat
91
+ ```
92
+
93
+ 插件用声明式依赖组装,引擎零改动即可扩展:
94
+
95
+ ```python
96
+ from dse_core.runtime import Service
97
+
98
+ class MyPlugin(Service):
99
+ name = "my-plugin"
100
+ inject = ("state", "io") # 等价于 Cordis 的 static inject
101
+ def setup(self):
102
+ self.ctx.on("scene/enter", lambda scene_id, scene: ...)
103
+ ```
104
+
105
+ ## 与 Node 版的关系
106
+
107
+ - Node 版持续维护;本仓库是**移植版**,内容格式不互通(YAML ↔ ESM)。
108
+ - 移植路线图:**P1 可发布最小引擎(本阶段)→ P2 系统层(save/achievements/i18n)+ 完整 CLI → P3 能力层(trade/dialogue/diary)→ P4 表现层(sound/hot-reload)+ 内容迁移工具**。详见 [PLAN.md](PLAN.md)。
109
+
110
+ ## 安全模型
111
+
112
+ - 内容包是数据;表达式求值禁调用与下划线属性访问。
113
+ - `dse.yml` 与内容包路径均限制在工程目录内(路径白名单)。
114
+
115
+ ## License
116
+
117
+ MIT · Copyright (c) 2026 全哥 (siwpan)
@@ -0,0 +1,87 @@
1
+ # dse-core · 字典场景引擎(Python 版)
2
+
3
+ > Dictionary Scene Engine —— 场景即字典,能力即插件。
4
+ > 本仓库是引擎的 **Python 原生实现**(cordis-py),Node/Cordis 版见姊妹仓库
5
+ > [dse-core](https://gitee.com/siwpan/dse-core)。立项计划书见 [PLAN.md](PLAN.md)。
6
+
7
+ ## 30 秒上手
8
+
9
+ ```bash
10
+ pip install -e . # 开发安装(或 pip install dse-core)
11
+ dse play # 玩自带示例内容包(战斗线最小可玩版)
12
+ dse play --input "1,1,a,a,a" # 脚本模式:无交互跑完整条线路(CI 友好)
13
+ dse scenes # 列出场景
14
+ dse check # 校验内容(悬空跳转等),有问题退出码 1
15
+ ```
16
+
17
+ 自带示例工程随包发布(`src/dse_core/example/`):在含 `dse.yml` 的目录里
18
+ 运行 `dse play` 玩你自己的工程,否则玩包内示例。
19
+
20
+ ## 内容包 = 数据,不是代码
21
+
22
+ 与 Node 版最大的差异:内容包是 **YAML 数据文件**,不是可执行模块。
23
+ 条件表达式经 **AST 白名单求值**(仅比较/布尔/算术/下标,禁调用、禁下划线属性)——
24
+ 不需要 import 任何用户代码,也没有 `new Function`。
25
+
26
+ ```yaml
27
+ # content/starter.yml(节选)
28
+ pack:
29
+ id: starter
30
+ name: 启程
31
+ entry: start
32
+ scenes:
33
+ start:
34
+ title: 村口 · 启程
35
+ text: '天没亮透……三条路摆在你面前。'
36
+ choices:
37
+ - { text: '往北进山', do: { 'flags.starter.mountain': true }, goto: forest }
38
+ forest:
39
+ text: '……一只独眼狼从阴影里站起来。'
40
+ choices:
41
+ - { text: '拔剑', goto: wolf-fight }
42
+ wolf-fight:
43
+ mode: battle # 非 normal 模式由能力插件接管
44
+ battle: { enemy: wolf, onWin: forest-win, onLose: forest-lose, onFlee: start }
45
+ ```
46
+
47
+ ## 架构
48
+
49
+ ```
50
+ dse_core/
51
+ ├── modes.py # 模式注册表(scene.mode 唯一真源)
52
+ ├── graph.py # 场景图静态分析(可达性/悬空/结局/最短路径,纯函数)
53
+ ├── manifest.py # 内容包元数据(规范化/校验)
54
+ ├── loader.py # 工程加载(dse.yml + YAML 内容包合并)
55
+ ├── schema.py # JSON Schema 校验(schema/scene.schema.json)
56
+ ├── safety.py # 条件表达式 AST 白名单求值
57
+ ├── runtime.py # 插件运行时(Service/Context/依赖拓扑/事件)—— Cordis 的 Python 原生替代
58
+ ├── sdk.py # 编程式门面(create_engine / play / inspect_project)
59
+ ├── cli.py # 命令行 dse:play / scenes / info / check
60
+ └── plugins/ # 内核层 scenes/state/console_io/engine + 参考能力插件 combat
61
+ ```
62
+
63
+ 插件用声明式依赖组装,引擎零改动即可扩展:
64
+
65
+ ```python
66
+ from dse_core.runtime import Service
67
+
68
+ class MyPlugin(Service):
69
+ name = "my-plugin"
70
+ inject = ("state", "io") # 等价于 Cordis 的 static inject
71
+ def setup(self):
72
+ self.ctx.on("scene/enter", lambda scene_id, scene: ...)
73
+ ```
74
+
75
+ ## 与 Node 版的关系
76
+
77
+ - Node 版持续维护;本仓库是**移植版**,内容格式不互通(YAML ↔ ESM)。
78
+ - 移植路线图:**P1 可发布最小引擎(本阶段)→ P2 系统层(save/achievements/i18n)+ 完整 CLI → P3 能力层(trade/dialogue/diary)→ P4 表现层(sound/hot-reload)+ 内容迁移工具**。详见 [PLAN.md](PLAN.md)。
79
+
80
+ ## 安全模型
81
+
82
+ - 内容包是数据;表达式求值禁调用与下划线属性访问。
83
+ - `dse.yml` 与内容包路径均限制在工程目录内(路径白名单)。
84
+
85
+ ## License
86
+
87
+ MIT · Copyright (c) 2026 全哥 (siwpan)
@@ -0,0 +1,59 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dse-core"
7
+ version = "0.1.0"
8
+ description = "字典场景引擎(Dictionary Scene Engine)—— 场景即字典,能力即插件"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "全哥 (siwpan)" }]
13
+ keywords = [
14
+ "text-adventure",
15
+ "interactive-fiction",
16
+ "game-engine",
17
+ "plugin-architecture",
18
+ "narrative-engine",
19
+ "dialogue-graph",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 3 - Alpha",
23
+ "Intended Audience :: Developers",
24
+ "License :: OSI Approved :: MIT License",
25
+ "Programming Language :: Python :: 3",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ "Topic :: Games/Entertainment",
31
+ ]
32
+ dependencies = [
33
+ "PyYAML>=6.0",
34
+ "jsonschema>=4.0",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ dev = [
39
+ "pytest>=7.0",
40
+ "build",
41
+ "twine",
42
+ ]
43
+
44
+ [project.urls]
45
+ Homepage = "https://gitee.com/siwpan/cordis-py"
46
+ Repository = "https://gitee.com/siwpan/cordis-py"
47
+ Issues = "https://gitee.com/siwpan/cordis-py/issues"
48
+
49
+ [project.scripts]
50
+ dse = "dse_core.cli:main"
51
+
52
+ [tool.setuptools.packages.find]
53
+ where = ["src"]
54
+
55
+ [tool.setuptools.package-data]
56
+ dse_core = ["schema/*.json", "example/dse.yml", "example/content/*.yml"]
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,40 @@
1
+ """dse-core · 字典场景引擎(Python 版)—— 场景即字典,能力即插件。
2
+
3
+ 快速上手::
4
+
5
+ from dse_core import create_engine, play, inspect_project
6
+
7
+ play(root="./my-game") # 装配并跑完整局
8
+ ctx = create_engine(root="./my-game") # 只装配,自行驱动主循环
9
+ inspect_project("./my-game") # 只读体检
10
+
11
+ Node/Cordis 版(姊妹仓库 dse-core)的移植;内容包为 YAML 数据文件,
12
+ 条件表达式走 AST 白名单求值(dse_core.safety)。
13
+ """
14
+
15
+ from .runtime import Context, Service
16
+ from .sdk import (
17
+ __version__,
18
+ analyze,
19
+ create_engine,
20
+ inspect_project,
21
+ load_dict,
22
+ load_packs,
23
+ load_project,
24
+ play,
25
+ scene_chars,
26
+ )
27
+
28
+ __all__ = [
29
+ "__version__",
30
+ "Context",
31
+ "Service",
32
+ "analyze",
33
+ "create_engine",
34
+ "inspect_project",
35
+ "load_dict",
36
+ "load_packs",
37
+ "load_project",
38
+ "play",
39
+ "scene_chars",
40
+ ]
@@ -0,0 +1,148 @@
1
+ """命令行 dse:play / scenes / info / check。
2
+
3
+ 移植自 dse-core(Node) cli.js 的 P1 子集(packs / new / doctor / saves
4
+ 属 P2)。约定:stdout 只放结果,诊断走 stderr;check 发现问题退出码 1。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import argparse
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ from . import __version__, graph
15
+ from .loader import load_pack_file, load_project
16
+ from .plugins.scenes import find_dangling, validate_scenes
17
+ from .sdk import inspect_project, load_dict, play
18
+ from .schema import validate_pack
19
+
20
+
21
+ def _print_banner() -> None:
22
+ title = f"字 典 场 景 引 擎 v{__version__}"
23
+ width = sum(2 if ord(ch) > 0x2E7F else 1 for ch in title)
24
+ border = "═" * 40
25
+ print(f"╔{border}╗")
26
+ print(f"║ {title}{' ' * max(0, 37 - width)}║")
27
+ print(f"║ Dictionary Scene Engine (Python){' ' * max(0, 37 - 32)}║")
28
+ print(f"╚{border}╝")
29
+
30
+
31
+ def _resolve_root(root: str | None) -> str:
32
+ """未指定 --root 时:当前目录有 dse.yml 就用它,否则用包内自带示例工程。"""
33
+ if root is not None:
34
+ return root
35
+ cwd = Path.cwd()
36
+ if (cwd / "dse.yml").is_file():
37
+ return str(cwd)
38
+ from .sdk import engine_root
39
+
40
+ return str(engine_root())
41
+
42
+
43
+ def _cmd_play(args) -> int:
44
+ if args.input:
45
+ os.environ["DSE_INPUT"] = args.input
46
+ if args.banner:
47
+ _print_banner()
48
+ return play(root=args.root, entry=args.entry)
49
+
50
+
51
+ def _cmd_scenes(args) -> int:
52
+ data = load_dict(args.root)
53
+ print(f"工程: {data['root']}(入口 {data['entry']},内容包 {len(data['packFiles'])} 个)")
54
+ for scene_id in data["ids"]:
55
+ scene = data["dict"][scene_id]
56
+ mode = scene.get("mode") or "normal"
57
+ title = scene.get("title") or scene_id
58
+ n_choices = len(scene.get("choices") or [])
59
+ print(f" {scene_id:<20} [{mode:<8}] {title}({n_choices} 选项)")
60
+ print(f"共 {len(data['ids'])} 个场景")
61
+ return 0
62
+
63
+
64
+ def _cmd_info(args) -> int:
65
+ report = inspect_project(args.root)
66
+ print(f"工程: {report['root']}")
67
+ print(f"入口: {report['entry']}")
68
+ print(f"场景: {report['scenes']} | 结局: {report['endings']}(可达 {report['endingsReachable']})")
69
+ print(f"内容包: {', '.join(report['packFiles']) or '(无)'}")
70
+ for m in report["packs"]:
71
+ print(f" - {m['id']} v{m['version']}《{m['name']}》 {m['scenes']} 场景 modes={m['modes']}")
72
+ if report["dangling"]:
73
+ print("悬空跳转:", file=sys.stderr)
74
+ for d in report["dangling"]:
75
+ print(f" - {d['from']} -> {d['to']} ({d['kind']})", file=sys.stderr)
76
+ if report["unreachable"]:
77
+ print(f"警告: 不可达场景 {', '.join(report['unreachable'])}", file=sys.stderr)
78
+ return 0
79
+
80
+
81
+ def _cmd_check(args) -> int:
82
+ root = Path(args.root)
83
+ problems: list[str] = []
84
+ try:
85
+ project = load_project(root)
86
+ except Exception as exc: # noqa: BLE001 — 加载失败本身就是 check 的结论
87
+ print(f"[check] 工程加载失败: {exc}", file=sys.stderr)
88
+ return 1
89
+
90
+ # 1. Schema + 结构校验(逐内容包)
91
+ for rel in project.pack_files:
92
+ path = (root / rel).resolve()
93
+ _, scenes = load_pack_file(path)
94
+ problems += validate_pack(scenes, source=rel)
95
+ problems += validate_scenes(scenes, rel)
96
+
97
+ # 2. 悬空跳转
98
+ problems += find_dangling(project.dict)
99
+
100
+ # 3. 图分析(不可达仅警告,不判失败)
101
+ report = graph.analyze(project.dict, project.entry)
102
+
103
+ if problems:
104
+ print(f"[check] 发现 {len(problems)} 个问题:", file=sys.stderr)
105
+ for p in problems:
106
+ print(p, file=sys.stderr)
107
+ return 1
108
+ print(f"[check] 通过:{report['scenes']} 场景 / {len(report['endings'])} 结局(可达 {len(report['endingsReachable'])})")
109
+ if report["unreachable"]:
110
+ print(f"警告: 不可达场景 {', '.join(report['unreachable'])}", file=sys.stderr)
111
+ return 0
112
+
113
+
114
+ def main(argv: list[str] | None = None) -> int:
115
+ parser = argparse.ArgumentParser(
116
+ prog="dse",
117
+ description="字典场景引擎 CLI —— 场景即字典,能力即插件",
118
+ )
119
+ parser.add_argument("--version", action="version", version=f"dse-core {__version__}")
120
+ sub = parser.add_subparsers(dest="command", required=True)
121
+
122
+ p_play = sub.add_parser("play", help="开始游戏")
123
+ p_play.add_argument("--root", default=None, help="工程根目录(含 dse.yml);缺省:当前目录有则用,否则用包内示例")
124
+ p_play.add_argument("--entry", help="覆盖入口场景 ID")
125
+ p_play.add_argument("--input", help='脚本输入序列,逗号分隔(如 "1,1,a,a"),用尽自动退出')
126
+ p_play.add_argument("--banner", action="store_true", help="打印启动横幅")
127
+ p_play.set_defaults(func=_cmd_play)
128
+
129
+ for name, help_text, func in (
130
+ ("scenes", "列出全部场景", _cmd_scenes),
131
+ ("info", "工程体检(结局/悬空/不可达)", _cmd_info),
132
+ ("check", "校验内容(Schema/悬空),有问题退出码 1", _cmd_check),
133
+ ):
134
+ p = sub.add_parser(name, help=help_text)
135
+ p.add_argument("--root", default=None, help="工程根目录(含 dse.yml);缺省:当前目录有则用,否则用包内示例")
136
+ p.set_defaults(func=func)
137
+
138
+ args = parser.parse_args(argv)
139
+ args.root = _resolve_root(args.root)
140
+ try:
141
+ return args.func(args)
142
+ except (FileNotFoundError, ValueError, KeyError) as exc:
143
+ print(f"[dse] {exc}", file=sys.stderr)
144
+ return 1
145
+
146
+
147
+ if __name__ == "__main__":
148
+ sys.exit(main())
@@ -0,0 +1,79 @@
1
+ # 内容包:starter(P1 · 战斗线最小可玩版)
2
+ #
3
+ # 定位与 Node 版 starter.js 相同:骨架演示,不是游戏 —— 让新同学读完这
4
+ # 一个文件就知道内容包能写什么。P1 覆盖 normal / battle 两种模式与
5
+ # @restart;trade / dialogue / diary 线随 P3 能力插件补齐后迁入。
6
+ pack:
7
+ id: starter
8
+ name: 启程 · 山道
9
+ version: 0.1.0
10
+ entry: start
11
+ description: P1 示例:一条跑通 normal/battle/结局/@restart 的最小可玩线路
12
+ author: dse-core
13
+ modes: [battle]
14
+ requires:
15
+ plugins: [combat]
16
+ state: [gold, hp, atk]
17
+
18
+ scenes:
19
+ # ── 枢纽:进山,或回屋 ──────────────────────────────────────
20
+ start:
21
+ title: 村口 · 启程
22
+ text: 天没亮透,土路泛着潮气。你背上行囊,你娘把最后一包干粮塞进侧袋,手在包袱上按了按,没说话。往北是山,山里有过狼;身后是自家那扇还没上漆的木门。
23
+ choices:
24
+ - text: 往北进山
25
+ do: { 'flags.starter.mountain': true }
26
+ goto: forest
27
+ - text: 回屋,今天不出门
28
+ goto: inn-wait
29
+
30
+ # ── 战斗线:normal → battle ─────────────────────────────────
31
+ forest:
32
+ title: 山道 · 林深处
33
+ text: 松针压了一路,越往里越静。走到半坡,风忽然停了,林子里只剩你自己的脚步声。前面的灌木动了一下——一只独眼狼从阴影里站起来,耳朵贴着背,喉咙里滚着低音。它挡的是唯一的路。腰间那柄锈剑冰凉。
34
+ choices:
35
+ - text: 拔剑
36
+ goto: wolf-fight
37
+ - text: 握着剑柄,慢慢退回去
38
+ do: { 'flags.starter.fled': true }
39
+ goto: start
40
+
41
+ wolf-fight:
42
+ title: 山道 · 遭遇
43
+ text: 狼绕着你走了半圈,那只独眼始终盯着你的右手,没有眨过。你想起村里老猎户的话:它看的不是你的人,是你什么时候先动。于是你也不动,任由汗从额头滑到下巴,等它先犯错。
44
+ mode: battle
45
+ battle: { enemy: wolf, onWin: forest-win, onLose: forest-lose, onFlee: start }
46
+
47
+ forest-win:
48
+ title: 山道 · 狼倒下之后
49
+ text: 狼倒在松针上,血把针叶黏成一团,慢慢不再起伏。你喘了很久才直起腰。狼窝旁边压着一只旧木匣,里头是一枚生锈的渡口令牌。顺着狼来的方向往下走,能听见水声——但雾太浓,今天过不了河。
50
+ onEnter: { 'items.token': +1, 'flags.starter.wolfWin': true }
51
+ choices:
52
+ - text: 揣好令牌,回村
53
+ do: { 'flags.starter.backHome': true }
54
+ goto: start
55
+ - text: 力气还够,在林间再过一夜
56
+ if: 'hp >= 10'
57
+ goto: end-hero
58
+
59
+ forest-lose:
60
+ title: 结局 · 山道无归
61
+ text: 你倒下的时候,最后看见的是松针间漏下来的那点天光。狼没有立刻动口,它先闻了闻你的手——那手上什么也没有。这山上从此多了一条不该走的道,可每年还是有人走,因为每个人都觉得自己跟别人不一样。
62
+ choices:
63
+ - text: 再来一次
64
+ goto: '@restart'
65
+
66
+ # ── 结局 ────────────────────────────────────────────────────
67
+ end-hero:
68
+ title: 结局 · 过河的人
69
+ text: 你在林间生了一堆火。令牌在怀里,锈迹被体温焐得温热。河那头有什么你不知道,但你知道自己是从哪边过来的——这就够了。
70
+ choices:
71
+ - text: 再来一次
72
+ goto: '@restart'
73
+
74
+ inn-wait:
75
+ title: 结局 · 留在岸这边
76
+ text: 你回了屋,把门关上。路还摆在那儿,明天还在——你这样想,然后就睡着了。有些山,是不用过的。
77
+ choices:
78
+ - text: 再来一次
79
+ goto: '@restart'
@@ -0,0 +1,64 @@
1
+ # 字典场景引擎内核(dse-core)· 插件清单(Python 版)
2
+ #
3
+ # 与内容仓库的关系:本文件是**引擎默认开发配置**,只挂自带示例内容包
4
+ # (content/starter.yml)。真正的项目应在自己的仓库里放一份 dse.yml,
5
+ # 指向自己的内容包与数值表 —— 引擎代码一行都不用改。
6
+ #
7
+ # 依赖由运行时按 inject 声明拓扑编排:全部就绪后才会 setup。
8
+ #
9
+ # 插件分层:
10
+ # 内核层 scenes / state / io / engine 引擎不可拔的四个基础服务
11
+ # 能力层 combat 接管 scene.mode 的可选玩法
12
+ # (P3 补 trade / dialogue / diary)
13
+
14
+ # ── 内核层 ──────────────────────────────────────────────────────
15
+ - id: scenes
16
+ config:
17
+ packs: # 内容包列表(后者覆盖前者同 ID 场景)
18
+ - ./content/starter.yml # 引擎自带示例;换成自己的内容包即可
19
+
20
+ - id: state
21
+ config:
22
+ # 时间循环保留域:goto '@loop:<场景>' 时保留这些前缀下的知识,
23
+ # 其余状态回 initial("知识跨轮保留、消耗态每轮复原")
24
+ loopKeep:
25
+ - clues.loop
26
+ - flags.loop
27
+ # 玩家初始状态。内容包用到的顶层字段必须在这里预声明,
28
+ # 否则条件表达式求值时报 NameError(dse check 会拦下)
29
+ initial:
30
+ name: 旅人
31
+ hp: 20
32
+ maxHp: 20
33
+ atk: 3 # 基础攻击(+ 武器加成 = 实际 ATK)
34
+ def: 2 # 防御(每点抵消 defenseFactor 点伤害)
35
+ crit: 0.15 # 暴击率
36
+ gold: 20
37
+ items: {}
38
+ flags:
39
+ loop: {} # 时间循环中间层(预声明,条件链才不抛错)
40
+ clues:
41
+ loop: {} # 时间循环跨轮知识(loopKeep 保留域)
42
+
43
+ - id: io
44
+
45
+ - id: engine
46
+ config:
47
+ entry: start # 入场场景 ID(可用 DSE_ENTRY / --entry 覆盖)
48
+ prompt: '>> '
49
+
50
+ # ── 能力层(每种能力接管一种 scene.mode)────────────────────────
51
+ - id: combat
52
+ config:
53
+ formula: # 数值公式参数(可选,覆盖默认值)
54
+ variance: 0.2 # 伤害随机波动 ±20%
55
+ defenseFactor: 0.5 # 每点防御抵消的伤害
56
+ critMult: 1.8 # 暴击倍率
57
+ defendCut: 0.5 # 防御姿态减伤 50%
58
+ fleeChance: 0.45 # 逃跑成功率
59
+ weapons: # 武器加成(按背包数量累加到玩家 ATK)
60
+ rusty_sword: { name: 锈剑, atk: 4 }
61
+ silver_dagger: { name: 银匕首, atk: 3 }
62
+ enemies: # 敌人字典(数据驱动,与场景字典同哲学)
63
+ wolf: { name: 独眼狼, hp: 12, atk: 3, def: 0, crit: 0.10, drops: { gold: +5, 'items.wolf_pelt': +1 } }
64
+ bandit: { name: 沙盗, hp: 14, atk: 3, def: 1, crit: 0.12, drops: { gold: +12 } }