summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md82
-rw-r--r--docs/assets-and-licenses.md15
-rw-r--r--docs/release-notes-m6c.md28
-rwxr-xr-xscripts/m6c-package.sh75
-rwxr-xr-xscripts/m6c-run.sh29
5 files changed, 229 insertions, 0 deletions
diff --git a/README.md b/README.md
index 414bda1..04652e0 100644
--- a/README.md
+++ b/README.md
@@ -6,3 +6,85 @@ NPC 社会涌现沙盘:扮演村庄 30 人中的一员过日子,底层是完
- 技术栈:F# + MonoGame + Mibo(MVU)
- 开发协议:全部编码由 OpenCode 执行(ark-plan/glm-5.3-flash);Hermes 只做需求派发与验收
- 里程碑 M0-M6 见设计文档第六节
+
+## Linux 交付
+
+### 从源码构建
+
+环境要求:Linux x64、.NET 8 SDK,以及 MonoGame DesktopGL 所需的系统图形/音频库。发布脚本优先生成 self-contained 包:.NET 运行时会随包提供,但 SDL/OpenGL/X11/OpenAL 等 Linux 原生库仍由系统提供。
+
+```bash
+./scripts/m6c-package.sh
+```
+
+产物输出到 `artifacts/m6c-release/<UTC timestamp>/`。包目录包含 `app/`、`run-living-village.sh`、运行模式元数据和交付文档。`artifacts/` 已被 `.gitignore` 忽略,不应提交大包或运行日志。
+
+如果 self-contained publish 受环境或依赖限制失败,脚本会回退到 framework-dependent publish,并在 `PACKAGE-INFO.txt` 中写明失败状态和所需的系统 .NET 8 runtime。
+
+### 启动交付包
+
+从包根目录运行:
+
+```bash
+./run-living-village.sh
+```
+
+启动器以自身目录为基准解析 `app/` 和相对存档路径。未设置 `LV_SAVE_PATH` 时,默认存档为包目录下的 `living-village.save`;设置相对路径时也会落在包目录下,绝对路径保持不变。
+
+### 主菜单
+
+首次启动进入主菜单:
+
+- `新游戏`:使用固定种子 `42` 创建 30 NPC 世界并进入游戏。
+- `继续`:在已有世界时返回当前世界。
+- `读档`:从 `LV_SAVE_PATH` 读取存档;失败会停在可返回的读档错误页。
+- `设置`:选择 `1X`、`2X`、`5X` 或暂停模拟。
+- `控制`:查看键位说明。
+- `退出`:结束程序。
+
+主菜单使用 `Up/Down` 或 `W/S` 选择,`Enter` 确认,`Esc` 返回或退出。游戏内 `Esc` 依次关闭面板、关闭关系视图,最后返回主菜单。
+
+### 游戏控制
+
+- `WASD`:移动
+- `E`:互动
+- `1-6`:选择对话意图
+- `Q`:观察附近 NPC
+- `Tab`:需求面板
+- `C`:编年史
+- `L`:关系网络视图
+- `P`:暂停/继续
+- `F1/F2/F3`:1X/2X/5X 速度
+- `F6`:保存
+- `F7`:读取
+- `Esc`:关闭当前面板、关闭关系视图或返回主菜单
+
+### Autoplay 验证
+
+`LV_AUTOPLAY=1` 会自动开始新游戏并持续推进 tick;普通运行不会自行退出,适合用 `timeout` 做 bounded 检查:
+
+```bash
+LV_AUTOPLAY=1 timeout 8s xvfb-run -a ./run-living-village.sh
+```
+
+`LV_AUTOPLAY_FLOW=1` 会执行一次可审计流程:新游戏、返回菜单、打开设置并应用速度、打开/返回控制页、保存、读档、返回菜单并退出:
+
+```bash
+LV_AUTOPLAY_FLOW=1 xvfb-run -a ./run-living-village.sh
+```
+
+`LV_SAVE_PATH` 可用于把验收存档指向临时目录。包验收的 stdout、exit code、文件清单、大小、SHA-256 和启动截图保存在对应包的 `verification/` 目录,不进入 Git。
+
+## Release Notes
+
+M0-M6 的落地摘要和实际验收口径见 [`docs/release-notes-m6c.md`](docs/release-notes-m6c.md)。
+
+资产与许可证边界见 [`docs/assets-and-licenses.md`](docs/assets-and-licenses.md)。仓库没有随包分发的 PNG、字体、音频或 Kenney 素材;当前像素占位图由代码生成,不能把设计文档中的候选资产描述成已包含资产。
+
+## 当前已知限制
+
+- 这是 Linux x64 DesktopGL 交付包;self-contained 不会打包发行版的 X11、OpenGL、SDL2、OpenAL 等原生库。
+- 当前美术是代码生成的像素占位图,没有外部美术资产或音频。
+- `LV_AUTOPLAY_FLOW` 是验收驱动,不是完整的键盘用户体验替代品。
+- 默认世界使用固定种子 `42` 和 30 NPC;更长时间稳定性仍应以 headless 批量验收为准。
+- 包含完整源码构建入口,但本交付不承诺安装器、桌面文件或发行版仓库包。
diff --git a/docs/assets-and-licenses.md b/docs/assets-and-licenses.md
new file mode 100644
index 0000000..372979b
--- /dev/null
+++ b/docs/assets-and-licenses.md
@@ -0,0 +1,15 @@
+# 资产与许可证清单
+
+## 仓库和交付包中的实际资产
+
+- **代码生成像素占位图**:`src/LivingVillage.Desktop/Game.fs` 中的 `PixelText`、色块和 `Atlas` 绘制逻辑在运行时生成图形,没有独立图片文件,不主张外部素材来源。
+- **外部美术资产**:当前仓库没有 PNG、JPG、GIF、SVG、字体或音频文件。设计文档提到的 Kenney CC 资产只是候选方向,未随仓库或 M6c 包分发。
+
+## 第三方运行依赖
+
+- **MonoGame.Framework.DesktopGL 3.8.5.1**:由 `src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj` 通过 NuGet 引用,是运行框架依赖而非本仓库创作的美术资产。其许可证和随包二进制的再分发义务以该版本上游包的许可证文件为准。
+- **.NET 8**:self-contained 包包含目标运行时的编译输出;framework-dependent 回退模式要求目标 Linux 安装 .NET 8 runtime。运行时及其第三方组件遵循各自的上游许可证。
+
+## 再分发边界
+
+M6c 没有复制或声称拥有外部艺术、字体、音频或 Kenney 素材。发布前若将 `app/` 二进制包提供给第三方,应同时保留 NuGet/runtime 上游要求的许可证和 notices;本仓库只记录实际存在并实际进入交付边界的依赖,不虚构未包含资产的来源。
diff --git a/docs/release-notes-m6c.md b/docs/release-notes-m6c.md
new file mode 100644
index 0000000..8faeac6
--- /dev/null
+++ b/docs/release-notes-m6c.md
@@ -0,0 +1,28 @@
+# M6c Linux Release Notes
+
+## 交付范围
+
+M6c 将已经完成 M6b 验收的 Living Village 制作为 Linux x64 可运行包,并补齐启动、存档、autoplay 验证和资产边界说明。发布脚本优先执行 self-contained publish;如果环境无法完成该模式,会回退到 framework-dependent 并在包元数据中明确记录。
+
+## M0-M6 已落地功能
+
+- **M0**:F# 解决方案、MonoGame DesktopGL 游戏循环、32x32 瓦片地图、化身移动和镜头跟随。
+- **M1**:纯函数世界步进、SplitMix64 确定性 RNG、headless 模拟和同种子对拍基础。
+- **M2**:NPC 需求、性格、决策、日程、行动粘性和记忆。
+- **M3**:30 NPC 社区、闲聊事件队列、记忆、关系矩阵、关系视图和 headless 社会统计。
+- **M4**:交易、库存、金钱、动态定价、闲聊传递记忆、谣言路径和回放。
+- **M5**:WASD 玩家操控、互动、对话意图、观察、需求面板、编年史和关系视图。
+- **M6**:世界存档、读档、模拟变速、暂停、昼夜表现和代码生成像素占位渲染。
+- **M6b**:主菜单、设置、控制页、继续/新游戏/读档/退出、读档错误页、Esc 返回语义和可审计 autoplay 流程。
+
+## 实际验收口径
+
+- M6b focused tests:6/6 passed。
+- Release solution tests:Desktop 12/12 passed,Kernel 42/42 passed。
+- `LV_AUTOPLAY_FLOW=1`:从包目录启动,实际覆盖新游戏、设置应用、控制页返回、保存、读档、菜单退出。
+- `LV_AUTOPLAY=1`:从包目录启动并观察 tick 持续前进;使用 bounded timeout 结束普通 autoplay。
+- 包级证据:每个发布目录的 `verification/` 保存实际 stdout、exit code、文件清单、包大小、SHA-256 和启动截图。
+
+## M6c 不改变的边界
+
+本次只增加 Linux 交付脚本和文档,不修改 M3 内核、冻结验收状态、共享 OpenCode 配置或 overnight 记录。`artifacts/` 仅保存本地发布包和验收证据,不进入 Git。
diff --git a/scripts/m6c-package.sh b/scripts/m6c-package.sh
new file mode 100755
index 0000000..d887ea6
--- /dev/null
+++ b/scripts/m6c-package.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
+PROJECT="$REPO_ROOT/src/LivingVillage.Desktop/LivingVillage.Desktop.fsproj"
+STAMP="${M6C_TIMESTAMP:-$(date -u +%Y%m%dT%H%M%SZ)}"
+PACKAGE_ROOT="$REPO_ROOT/artifacts/m6c-release/$STAMP"
+APP_DIR="$PACKAGE_ROOT/app"
+PUBLISH_LOG="$PACKAGE_ROOT/publish.log"
+
+if [[ -e "$PACKAGE_ROOT" ]]; then
+ printf 'Refusing to overwrite existing package directory: %s\n' "$PACKAGE_ROOT" >&2
+ exit 2
+fi
+
+if ! command -v dotnet >/dev/null 2>&1; then
+ printf 'dotnet SDK is required to build the Linux package.\n' >&2
+ exit 127
+fi
+
+mkdir -p "$APP_DIR"
+
+publish_args=(
+ "$PROJECT"
+ --configuration Release
+ --runtime linux-x64
+ --output "$APP_DIR"
+ --nologo
+ -p:DebugType=None
+ -p:DebugSymbols=false
+)
+
+runtime_mode="self-contained"
+self_contained_exit=0
+if dotnet publish "${publish_args[@]}" --self-contained true >"$PUBLISH_LOG" 2>&1; then
+ :
+else
+ self_contained_exit=$?
+ runtime_mode="framework-dependent"
+ rm -rf "$APP_DIR"
+ mkdir -p "$APP_DIR"
+ if ! dotnet publish "${publish_args[@]}" --self-contained false >>"$PUBLISH_LOG" 2>&1; then
+ printf 'Both self-contained and framework-dependent publish failed. See %s\n' "$PUBLISH_LOG" >&2
+ exit 1
+ fi
+fi
+
+cp "$SCRIPT_DIR/m6c-run.sh" "$PACKAGE_ROOT/run-living-village.sh"
+chmod +x "$PACKAGE_ROOT/run-living-village.sh"
+cp "$REPO_ROOT/README.md" "$PACKAGE_ROOT/README.md"
+cp "$REPO_ROOT/docs/release-notes-m6c.md" "$PACKAGE_ROOT/RELEASE-NOTES-M6C.md"
+cp "$REPO_ROOT/docs/assets-and-licenses.md" "$PACKAGE_ROOT/ASSETS-AND-LICENSES.md"
+
+source_commit="unknown"
+if git -C "$REPO_ROOT" rev-parse HEAD >/dev/null 2>&1; then
+ source_commit="$(git -C "$REPO_ROOT" rev-parse HEAD)"
+fi
+
+{
+ printf 'package=M6c Linux x64\n'
+ printf 'created_utc=%s\n' "$STAMP"
+ printf 'source_commit=%s\n' "$source_commit"
+ printf 'runtime_mode=%s\n' "$runtime_mode"
+ printf 'rid=linux-x64\n'
+ printf 'entrypoint=run-living-village.sh\n'
+ printf 'native_desktopgl_dependencies=system-provided\n'
+ if [[ "$runtime_mode" == "framework-dependent" ]]; then
+ printf 'self_contained_publish_exit=%s\n' "$self_contained_exit"
+ printf 'framework_dependent_requirement=.NET 8 runtime\n'
+ fi
+} > "$PACKAGE_ROOT/PACKAGE-INFO.txt"
+
+printf 'M6C_PACKAGE_DIR=%s\n' "$PACKAGE_ROOT"
+printf 'M6C_RUNTIME_MODE=%s\n' "$runtime_mode"
diff --git a/scripts/m6c-run.sh b/scripts/m6c-run.sh
new file mode 100755
index 0000000..5b73c6f
--- /dev/null
+++ b/scripts/m6c-run.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
+APP_DIR="$SCRIPT_DIR/app"
+
+if [[ ! -d "$APP_DIR" ]]; then
+ printf 'M6c package app directory not found: %s\n' "$APP_DIR" >&2
+ exit 2
+fi
+
+if [[ -z "${LV_SAVE_PATH:-}" ]]; then
+ export LV_SAVE_PATH="$SCRIPT_DIR/living-village.save"
+elif [[ "$LV_SAVE_PATH" != /* ]]; then
+ export LV_SAVE_PATH="$SCRIPT_DIR/$LV_SAVE_PATH"
+fi
+
+cd "$SCRIPT_DIR"
+
+if [[ -x "$APP_DIR/LivingVillage.Desktop" ]]; then
+ exec "$APP_DIR/LivingVillage.Desktop" "$@"
+fi
+
+if [[ -f "$APP_DIR/LivingVillage.Desktop.dll" ]] && command -v dotnet >/dev/null 2>&1; then
+ exec dotnet "$APP_DIR/LivingVillage.Desktop.dll" "$@"
+fi
+
+printf 'Living Village executable not found or framework-dependent dotnet is unavailable.\n' >&2
+exit 127