SanScript 的定位、研究方法与证据边界
分析日期:2026-10-30。基于 D:\Code\GTA5 中的源码快照,仅进行文件读取、静态追踪和文档校验,未编译工程、未运行编译器、未启动游戏,也未执行随附批处理。
1. 研究对象#
本报告承接项目结构与技术栈分析报告,将其中“玩法脚本 → Native → 游戏系统 → RAGE”的路径展开到语言实现层。
对象是工程文件明确命名的 SanScript:以 .sc 为入口、.sch 为公共定义的专用玩法语言。它是显式类型、命令式、面向游戏流程编排的语言,语法接近 BASIC,并加入 C 风格运算符、结构体、引用参数和函数指针。它离线编译为专用字节码,由 RAGE 脚本虚拟机执行,通过 Native 操作 C++ 游戏系统。[S1][S2]
项目中的 Ruby、Python、MaxScript、C# 编辑器和音频脚本系统不应混入这门语言。尤其是 .sch 包含函数实现、数据和条件编译内容,并非只有接口声明。
2. 实现地图#
| 层 | 核心位置 | 应回答的问题 |
|---|---|---|
| 玩法内容 | script/dev_ng/{singleplayer,multiplayer,shared,core} | 团队实际怎样组织游戏逻辑 |
| 编译器 | rage/script/tools/scriptcompiler | 接受什么语法,如何检查类型和生成代码 |
| 资源转换 | rage/script/tools/scriptrc | 字节码如何成为平台资源 |
| 编辑器 | rage/script/tools/SanScriptParser2、ragScriptEditor2 | 补全、依赖构建、错误定位如何工作 |
| VM | rage/script/src/script | 槽位、指令、调用栈、挂起、Native ABI |
| 游戏接入 | game/script、game/script_headers | 创建线程、清理资源、调用引擎能力 |
| 框架网络 | rage/framework/src/fwscript、fwnet | 脚本主机、广播变量、资源归属 |
| 存档 | game/SaveLoad/SavegameScriptData | 数据注册、序列化与恢复 |
这是离线编译、运行期解释执行的主链路。源码中有 C++ 输出和旧平台二进制脚本路径,但 EXECUTABLE_SCRIPTS 的定义含 0 && ...,不能据此将当前工程描述为 JIT 或默认 AOT 脚本系统。[S3]
3. 规模与抽样#
重新统计 script/dev_ng 下实际 .sc/.sch 文件,物理行数含空行、注释、重复和生成内容,不等于有效代码量或运行时代价:
| 一级目录 | .sc 文件 | .sch 文件 | 合计物理行 |
|---|---|---|---|
| singleplayer | 636 | 938 | 3,314,320 |
| multiplayer | 357 | 1,695 | 6,119,863 |
| shared | 14 | 208 | 364,136 |
| core | 1 | 149 | 245,174 |
| Code | 45 | 3 | 41,340 |
| BGNG | 1 | 4 | 835 |
| Tools | 0 | 2 | 6,251 |
| 合计 | 1,054 | 2,999 | 10,091,919 |
此外,game/script_headers 有 68 个 .sch 文件,编译器目录有 115 个 .sc 样例或测试文件。文件统计用 Python 标准库 Path.rglob 和 bytes.splitlines() 完成,未执行内容。
抽样覆盖:入口 main.sc、自动存档控制器、车辆开发测试、多人 net_jacking_soaktest.sc、freemode.sc、Native 车辆创建、网络广播与存档登记。这里不是对一千万行玩法代码的逐行审计,也不把测试脚本当成所有生产脚本的规范。
4. 如何理解报告中的结论#
- 源码事实:当前文件中明确出现的分支、数据结构、算法和配置。
- 静态推导:根据这些实现推导出的成本或风险,例如按值传大结构体的槽位复制;不是实测结果。
- 设计建议:面向新语言提出的方案,不代表 SanScript 已有能力。
- 未确认:需要具体构建配置、运行环境或测试才能定论的事项。
各篇的 [S编号] 都链接到本地文件及已核对的行号。文末证据入口同时给出关键符号,便于源码变化后继续查找。主报告通过 Summary.md 汇总。
5. 必须处理的版本差异#
| 容易沿用的旧说法 | 当前证据 |
|---|---|
| 全游戏只有一个 GLOBALS 块 | scrProgram 定义 64 块,24 位全局地址分成块号与偏移 |
| VM word 永远四字节 | scrValue 在 64 位目标通常为八字节;.sco 初始化数据仍以四字节元素保存 |
| 每帧固定运行 1,000 条指令 | 旧手册有此说法;当前默认常量是 13,000,000,快速内核的限制机制又不同 |
| AND/OR 永远不短路 | 编译器可由 -shortcircuit 改变语义,当前 SP 工程启用了该选项 |
| 预处理只支持不可嵌套的 IFDEF | 当前 lexer 要求 #IF,支持嵌套计数及 DEFINED、NOT |
| 有 HSM 示例就说明当前语言支持 STATE/EVENT | 目录有历史 HSM 示例和编辑器开关;当前 scr.y token/产生式不能证明这些语法仍可编译 |
| 有 threadrun.cpp 就应以它为准 | 当前 thread.cpp 末尾已合入快速内核,应优先分析这个实现 |
旧手册适合解释设计意图,当前文法、生成器、运行时和工程选项共同决定真实行为。源码来自跨平台、跨年代的集合,不能据此确认某一零售版本的全部构建宏。[S2–S6]
6. 对新语言研究的启发#
研究一门游戏脚本语言,需要同时回答“程序表达了什么”“帧调度何时执行它”“它可以造成哪些引擎副作用”。只模仿关键词会错过最有价值的部分:资源归属、可恢复执行、Native 契约、调试观测和内容规模下的工程组织。
后续设计应维护一个可版本化的语言规范,明确编译选项、目标 ABI、调试模式和资源格式。避免让旧手册、编辑器语法与编译器悄悄成为三套规范。
源码证据#
- S1 — GTA5_SP.scproj:Language 与工具路径。
- S2 — script.dtx:历史语言手册。
- S3 — program.h:EXECUTABLE_SCRIPTS、分页及全局块。
- S4 — value.h:scrValue。
- S5 — scr.y:预处理器。
- S6 — thread.cpp:合入的快速解释内核。