diff --git a/.gitea/workflows/baseline-mac.yml b/.gitea/workflows/baseline-mac.yml new file mode 100644 index 00000000..fc01fd4f --- /dev/null +++ b/.gitea/workflows/baseline-mac.yml @@ -0,0 +1,47 @@ +# mac 平台 golden 基线生成工作流(workflow_dispatch 手动触发型,2026-11-03)。 +# 用途:渲染基线按平台各存——mac 基线必须在 Mac 的图形会话里窗口化渲染产出(Windows 基线不可跨平台替代)。 +# 纪律(Q6):本工作流只「生成」候选基线文件(落在 runner 作业工作区,不提交不入库); +# 入库走人工通道:人工目检四张基线(体验验证清单㊾)→ 取回文件显式提交并写明触发原因。 +# 禁由 CI 自动提交基线(防静默重制掩盖视觉回归)。 +# 运行形态:runs-on=ci-mac(host 模式,作业活在 ci-runner 图形会话内——窗口可开、截图可用)。 +name: baseline-mac +on: + workflow_dispatch: + +jobs: + refresh: + runs-on: ci-mac + timeout-minutes: 30 + steps: + - name: 检出(纯 shell 克隆) + env: + TOKEN: ${{ github.token }} + run: | + git clone -q "https://${TOKEN}@git.kuaimeo.com/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE/repo" + cd "$GITHUB_WORKSPACE/repo" && git log --oneline -1 + + - name: 导入缓存刷新 + run: | + cd "$GITHUB_WORKSPACE/repo" + /usr/local/bin/godot --headless --path game --editor --quit + + - name: 退出判别实验(quit-after 能否解 macOS 窗口化退出挂死) + run: | + cd "$GITHUB_WORKSPACE/repo" + mkdir -p /tmp/shots_diag + /usr/local/bin/godot --path game --quit-after 3000 res://probe/board_shot_probe.tscn -- /tmp/shots_diag + echo "diag_rc=$?" + ls -la /tmp/shots_diag/ | head -8 + + - name: 基线生成(棋盘三图) + run: | + cd "$GITHUB_WORKSPACE/repo" + GODOT_BIN=/usr/local/bin/godot bash tools/golden_baseline_refresh.sh "$GITHUB_WORKSPACE/repo/game" + ls -la game/tests/golden/mac/ || true + + - name: 基线生成(菜单全屏) + run: | + cd "$GITHUB_WORKSPACE/repo" + GODOT_BIN=/usr/local/bin/godot bash tools/golden_baseline_refresh.sh "$GITHUB_WORKSPACE/repo/game" --menu + ls -la game/tests/golden/mac/ || true + echo "WORKSPACE_REMOINDER: 基线文件在 runner 侧 $GITHUB_WORKSPACE/repo/game/tests/golden/mac/,人工取回目检后入库" diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 00000000..7237235d --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,51 @@ +# 远端 CI 工作流(2026-11-03 用户裁:CI 迁远端 Mac runner,host 模式直跑)。 +# 触发:master 收到 push(post-merge 钩子合回自动推,或人手推 master)——分支推送不触发 +# (开发分支不烧远端引擎时长;分支级验证=开发期手动跑批档)。 +# 跑什么由 tools/ci/domain_route.sh 按改动域分流(乙案)—— +# 游戏码批=两域全量(含渲染闸);文档批=文档域秒级闸;纯闸件/管线批=零触发。 +# 运行环境:Mac host 模式(tools/ci/mac_setup.sh 自举),GODOT_BIN/GDSTYLE_BIN 由自举件安装到位。 +# 检出用纯 shell 克隆:host 模式执行 JS 系动作(actions/checkout)需要 node 运行时, +# runner 机器无 node——首跑实证「Cannot find: node in PATH」即败(run 4),本仓禁用 JS 系动作, +# 引入新 Actions 前必须确认其执行器形态(shell/composite 可,node/docker 不可)。 +# 闸清单与域划分唯一出处 = tools/run_all_gates.sh(本文件只负责触发与分流,不另列闸件)。 +name: ci +on: + push: + branches: [master] + +jobs: + gates: + runs-on: ci-mac + timeout-minutes: 90 + steps: + - name: 检出(纯 shell 克隆) + env: + TOKEN: ${{ github.token }} + run: | + git clone -q "https://${TOKEN}@git.kuaimeo.com/${GITHUB_REPOSITORY}.git" "$GITHUB_WORKSPACE/repo" + cd "$GITHUB_WORKSPACE/repo" + git log --oneline -1 + + - name: 域分流(乙案) + id: route + run: | + cd "$GITHUB_WORKSPACE/repo" + bash tools/ci/domain_route.sh "${{ github.event.before }}" "${{ github.sha }}" + + - name: 导入缓存刷新(闸前置——.godot 不入 git,冷检出一付) + if: steps.route.outputs.mode != 'skip' + run: | + cd "$GITHUB_WORKSPACE/repo" + /usr/local/bin/godot --headless --path game --editor --quit + + - name: 跑闸 + if: steps.route.outputs.mode != 'skip' + env: + GODOT_BIN: /usr/local/bin/godot + GDSTYLE_BIN: /usr/local/bin/gdstyle + run: | + cd "$GITHUB_WORKSPACE/repo" + MODE="${{ steps.route.outputs.mode }}" + if [ "$MODE" = "full" ]; then ARGS=""; else ARGS="--docs"; fi + echo "[ci] 域=$MODE 跑闸参数=$ARGS" + bash tools/run_all_gates.sh $ARGS diff --git a/.gitignore b/.gitignore index ef6a5961..fa6e5311 100644 --- a/.gitignore +++ b/.gitignore @@ -53,10 +53,9 @@ archive/ # === 在途图像素材(2026-10-02 用户裁:artwork/ 整目录不入库,取消 md 例外——此前文本指引入库留痕的口子关闭)=== artwork/ -# === 游戏包美术素材(2026-09-29 裁定维持:素材不入 git,运行时资源本地生成不入库)=== -game/assets/art/** -!game/assets/art/**/*.md -!game/assets/art/**/ +# === 游戏包美术素材(2026-11-03 用户裁:素材回 git,推翻 2026-09-29 不入库裁定—— +# 仓库自包含原则:远端 CI 与任何新克隆必须拉到完整可跑素材)=== +!game/assets/art/**/*.import # === 模拟数据缓存层(2026-10-03 用户裁:sim-data/ 数据件不入库——最新 MC 锚常驻留盘作分析缓存,无数值调整不重跑;同日二裁:README+分析工具三件留库,其余留盘)=== sim-data/* diff --git a/AGENTS.md b/AGENTS.md index 9fdd17c3..47711831 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,7 +16,7 @@ ## 运行与验证(工作目录 = 含 project.godot 的切片目录) -- **合回验证 = 本地 CI(2026-10-30 用户裁)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)后台触发 `tools/ci_runner.sh` 跑一次全量域闸(调 `tools/run_all_gates.sh` 不带参数档,02 全套件),结果落 `game/tests/.gates/ci-status.json`(commit/绿红/红闸名单+关键日志行/耗时)+`ci-history.jsonl` 流水账;CI 红态下修复合回落地即由 post-merge 钩子复验追绿(红态修复走新开修复 worktree,`fix-<任务名>` 分支,免重塑免数卡;post-commit 钩子保留为红态主区直提兜底触发,绿态与无状态零触发)——批内提交零钩子检查、合回只验一次,同一份字节禁止第二次人手跑。CI 红不回滚合并,红项在案、修复合回追绿;开工先读 CI 状态,红未修不开新批。跑批起手自动 `--editor --quit` 刷类名缓存(.godot 缓存不入 git,防合并后失配假红)。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起。**回执等待一律后台(2026-11-02 用户裁)**:合回落地先向用户呈报本批总结,随即 run_in_background 运行 `tools/ci_watch.sh <合并提交>` 等回执、唤醒后再续,禁前台 sleep/轮询等 CI(细则=process-conventions §1 第 3 条) +- **合回验证 = 远端 CI(2026-11-03 用户裁,取代 2026-10-30 本地 CI)**:git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。回执由 `tools/ci_watch.sh remote` 轮询 Gitea API 写回 `game/tests/.gates/ci-status.json`(合同不变:commit/绿红/红闸(job 级)+job 日志链接/耗时)+`ci-history.jsonl` 流水账;**回执等待一律后台(2026-11-02 用户裁)**:合回落地先向用户呈报本批总结,随即 run_in_background 运行 `tools/ci_watch.sh remote <合并提交>`,禁前台 sleep/轮询等 CI(细则=process-conventions §1 第 3 条)。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 手动档只服务开发期按域跑批(开发期窄测纪律见 process-conventions §6),不构成验收;全量回归由用户自发发起。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举 - 单件直跑(调试期最窄确认用,**不构成验收**):冒烟测试 `python tools/run_smoke.py`(`game/tests/smoke_boot.tscn` 极小启动场景)· 系统测试 `python tools/run_system_test.py`(宿主 `game/tests/system_test.gd`)· 场景探针 `…console.exe --headless --path . res://probe/ui_probe.tscn` · 单元与集成 `… --script res://tests/run_all.gd` - 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险 - 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`) @@ -40,7 +40,7 @@ | 时机 | 权威文档 | |---|---| -| 批次开工 / 流程纪律 | `design/engineering/process-conventions.md`——车道铁律(一切改动走独立 worktree,主区只作合并落点)、合回后本地 CI、五段流水线、批次清单、测试两入口与 MC/闸机细则、像素纪律 | +| 批次开工 / 流程纪律 | `design/engineering/process-conventions.md`——车道铁律(一切改动走独立 worktree,主区只作合并落点)、合回后远端 CI、五段流水线、批次清单、测试两入口与 MC/闸机细则、像素纪律 | | 流水线段入口 | 技能对照——doc-design=文档设计(兼任反哺点落文)/asset-ingest=美术录入/req-card=写卡/iter-plan=迭代编排/iter=实现(五者均装于项目 `.zcode/skills/`);登记不立技能;批后收尾与中间文档退役=batch-closure(同一目录);格式与状态机权威=`design/engineering/doc-conventions.md` §12 | | 定代码方案 / 写代码 / 改代码前 | `design/engineering/code-conventions.md`——结构/契约/语言/数值/流程五类硬纪律(反例库 = 同目录 code-conventions-cases.md) | | 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 提交前验证 / 验收清单落码 | @@ -103,11 +103,11 @@ ## 硬性红线(勿违反;操作细则在 process-conventions.md 与各规约) -- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→删区(合回即删,2026-09-19 原裁),CI 红不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,免重塑免数卡)修复合回追绿;主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役),全量验证归合回后本地 CI(`tools/hooks/post-merge` 触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1) +- 车道铁律(2026-10-31 用户裁恢复)——主工作区只作合并落点,一切开发/设计改动走独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`(建区即跑 `tools/setup_worktree_userdir.sh`),收口=冻结→件核→并 master→重塑→主区 `git merge --no-ff`→删区(合回即删,2026-09-19 原裁),CI 红不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,免重塑免数卡)修复合回追绿;主区唯一合法写动作=合并,agent 侧由 worktree-guard 拦主区 Edit/Write 与非 merge 提交;豁免面仅 `.zcode/`、`.qoder/`(不入 git,主区直改);commit 点零钩子检查(pre-commit 直改闸已退役),全量验证归合回后远端 CI(post-merge 钩子推送触发,见「运行与验证」首条);`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1) - **仓库根禁止设会话状态文件**(`active.md` 已废除)——现状看 roadmap「当前位」,待办看待办清单,过程看 git 提交信息,产出物看 `reports/`;任何文档禁止另设「当前状态快照」节,两处记同一件事必然漂移(细则见 doc-conventions 第 10 节) - 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测 -- 合回后验证 = 本地 CI 全量域闸全绿(唯一强制验证点;唯一入口、唯一闸清单、唯一域划分与选闸算法仍只在 `tools/run_all_gates.sh`,禁在文档另列;触发与追绿语义见「运行与验证」首条与 process-conventions §1)。开发期手动跑批按需选档(`--code` 关联档选测——02 跑「卡片申报 ∪ diff 推导」并集套件、三件保险恒跑、横切面回落全量——算法只在闸脚本内定义),手动跑不构成验收;全量回归由用户自发发起。文档面批与代码面批验收同走合回 CI 全量(`--docs` 仅属开发期手动档,process-conventions §1) -- **改动四类**——**需求改动**(游戏代码,含随逻辑写/改的测试代码):唯一走「写需求卡片+编入迭代批次」通道;迭代实现批禁写 `design/` 整个目录、两份根流程清单(`待办清单.md`/`体验验证清单.md`)与 `game/tests/README.md`(能力清单,归写卡段维护)(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;两份流程清单的条目状态只由流水线各段推进,实现批禁止顺手改;产出物只准写 `reports/`;合回后由本地 CI 全量验证(红项修复合回追绿,process-conventions §1)。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,由用户发令走独立 worktree 直改+合回(车道铁律,commit 点零钩子检查);大体量改动(含触及代码面的工程改动如纯测试维护)同车道无特殊通道;文档设计类写入走 doc-design 技能(登记条目格式由流水线闸强制) +- 合回后验证 = 远端 CI 闸全绿(唯一强制验证点;唯一入口、唯一闸清单、唯一域划分与选闸算法仍只在 `tools/run_all_gates.sh`,禁在文档另列;触发与追绿语义见「运行与验证」首条与 process-conventions §1)。开发期手动跑批按需选档(`--code` 关联档选测——02 跑「卡片申报 ∪ diff 推导」并集套件、三件保险恒跑、横切面回落全量——算法只在闸脚本内定义),手动跑不构成验收;全量回归由用户自发发起。远端按改动域分流:文档面批走文档域秒级闸,代码面批走两域全量(分流算法单点=`tools/ci/domain_route.sh`) +- **改动四类**——**需求改动**(游戏代码,含随逻辑写/改的测试代码):唯一走「写需求卡片+编入迭代批次」通道;迭代实现批禁写 `design/` 整个目录、两份根流程清单(`待办清单.md`/`体验验证清单.md`)与 `game/tests/README.md`(能力清单,归写卡段维护)(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;两份流程清单的条目状态只由流水线各段推进,实现批禁止顺手改;产出物只准写 `reports/`;合回后由远端 CI 验证(红项修复合回追绿,process-conventions §1)。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,由用户发令走独立 worktree 直改+合回(车道铁律,commit 点零钩子检查);大体量改动(含触及代码面的工程改动如纯测试维护)同车道无特殊通道;文档设计类写入走 doc-design 技能(登记条目格式由流水线闸强制) - 五段流水线铁律——**需求改动**(游戏代码)按「登记 → 文档设计/美术设计 → 写需求卡片 → 迭代编排 → 实现」五段推进,段间不自动流转、每段转进由用户发令;编入迭代的卡片必须设计闭合+零待裁点——需求卡片任何工作量皆可写、皆可编入迭代,工作量限制只属迭代层(迭代估时总量 <5 个工作日=「冻结待凑批」、不可开工);流水线闸 `tools/pipeline_gate.py` 强制(已注册进 run_all_gates.sh 文档域);实现段零裁断——卡片没写清=上游缺陷,停该卡退回上游,禁内联求裁(process-conventions §2,格式与状态机=doc-conventions §12)。工程/文档设计/美术素材三类改动不走写卡与编排段:(如需设计讨论)→ 用户发令 → 独立 worktree 直改 → 合回 CI(车道细则=process-conventions §1) - MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起 - 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6) diff --git a/design/engineering/architecture/architecture.md b/design/engineering/architecture/architecture.md index 7aab90d8..ff7060fa 100644 --- a/design/engineering/architecture/architecture.md +++ b/design/engineering/architecture/architecture.md @@ -267,7 +267,7 @@ |---|---|---|---| | **worktree-guard**(ZCode PreToolUse) | `.zcode/hooks/worktree-guard.sh`(本机件,不入 git) | agent 侧车道纪律:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免);`git commit --no-verify` 拦截(该旗标仅属用户);iter 分支合回一卡一提交数卡校验;规则闸 | 路径性质+分支名+MERGE_HEAD+git 图 | -验证收拢为合回后本地 CI 全量(post-merge 触发),commit 点零执法;主工作区防污染由 ZCode 层守卫承担——只约束 agent,用户手工改动不受限。子代理覆盖面(2026-10-26 冷启动评估实测):子代理的工具调用不经过 ZCode 层守卫——ZCode 层实际只覆盖主会话工具调用;残余暴露面=子代理写主工作区与蓄意 `--no-verify`(该旗标整族跳过 git 层钩子),属故意越权而非遗忘型失误,与防遗忘的设计目标正交。 +验证收拢为合回后远端 CI(post-merge 钩子推送 origin master 触发 Gitea Actions,runner=Mac 本机 host 模式;按改动域分流,分流单点=tools/ci/domain_route.sh),commit 点零执法;主工作区防污染由 ZCode 层守卫承担——只约束 agent,用户手工改动不受限。子代理覆盖面(2026-10-26 冷启动评估实测):子代理的工具调用不经过 ZCode 层守卫——ZCode 层实际只覆盖主会话工具调用;残余暴露面=子代理写主工作区与蓄意 `--no-verify`(该旗标整族跳过 git 层钩子),属故意越权而非遗忘型失误,与防遗忘的设计目标正交。 ### 三条判定硬闸现行状态 diff --git a/design/engineering/process-conventions.md b/design/engineering/process-conventions.md index 1efcf908..72fe5a13 100644 --- a/design/engineering/process-conventions.md +++ b/design/engineering/process-conventions.md @@ -7,12 +7,12 @@ ## 1. 分支纪律(worktree 铁律) -主工作区(master 检出)只作合并落点,一切开发/设计改动禁止直接发生在主工作区(2026-10-31 用户裁恢复;2026-10-30 的「主区直改+直提」车道与 commit 点 pre-commit 直改闸随之退役;验证模型不动——合回后本地 CI 仍是唯一强制验证点): +主工作区(master 检出)只作合并落点,一切开发/设计改动禁止直接发生在主工作区(2026-10-31 用户裁恢复;2026-10-30 的「主区直改+直提」车道与 commit 点 pre-commit 直改闸随之退役;验证模型同构——合回后远端 CI 是唯一强制验证点,2026-11-03 用户裁,细则见本节第 3 条): 1. **worktree 车道(一切面)**:每批先建独立 worktree:`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效) 2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。游戏类测试(冒烟/单测/MC/探针)批内默认零执行(两入口铁规,见 §4),用户点名或当场问才跑 -3. **合回后验证(本地 CI,2026-10-30 用户裁)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)后台触发 `tools/ci_runner.sh merge` 对 master 尖跑一次全量域闸——唯一强制全量验证点;CI 红态下修复合回落地即由 post-merge 钩子复验追绿(红态修复走新开修复 worktree,`fix-<任务名>` 分支,非 iter 形免重塑免数卡;post-commit 钩子保留为红态主区直提兜底触发,绿态与无状态零触发)。重跑判据:同一份字节禁止第二次人手跑;有意义的重跑只剩「验证修复」一类。CI 红不回滚合并——红项记 `game/tests/.gates/ci-status.json`,开工先读 CI 状态,红未修不开新批。跑批起手 `--editor --quit` 刷类名缓存(在册坑:.godot 缓存不入 git,合并后失配假红假绿)。并跑语义=跑完补跑(coalesce):跑批期间新触发不中断当前轮,轮尾对最新尖端补跑,绝不逐笔各跑一轮。**回执等待纪律(2026-11-02 用户裁)**:合回落地即向用户呈报本批总结,回执等待一律以 run_in_background 后台任务运行 `tools/ci_watch.sh <合并提交>`(认领判据与容错细则=该件头注),禁前台 sleep/轮询循环等 CI;后台任务唤醒会话后按回执续行——绿即删区收口,红或超时即呈报走修复批。闸重跑等一切分钟级跑批等待同此纪律 -4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge/post-commit 钩子——本地 CI 触发(仅主工作区;任务 worktree 内的合并不触发)。③git 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) +3. **合回后验证(远端 CI,2026-11-03 用户裁取代本地 CI)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制验证点。回执由 `tools/ci_watch.sh remote` 轮询 Gitea API 写回 `game/tests/.gates/ci-status.json`(合同不变:commit/verdict/red_gates/key_lines/note),agent 开工先读 CI 状态的纪律不变,红未修不开新批。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链;本地跑批件 `ci_runner.sh` 与本地追绿钩子 post-commit 已退役)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**回执等待纪律(2026-11-02 用户裁)**:合回落地即向用户呈报本批总结,回执等待一律以 run_in_background 后台任务运行 `tools/ci_watch.sh remote <合并提交>`(认领判据与容错细则=该件头注),禁前台 sleep/轮询循环等 CI;后台任务唤醒会话后按回执续行——绿即删区收口,红或超时即呈报走修复批。闸重跑等一切分钟级跑批等待同此纪律 +4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(推送+挂回执等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层不设车道钩子(pre-commit 直改闸已退役)——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) ## 2. 五段流水线 @@ -48,8 +48,8 @@ 7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补 **完成** -8. **合回后 CI 验证(唯一强制验证点)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动跑全量域闸(不带参数档,02 全套件),判定与红项落 `game/tests/.gates/ci-status.json`。全量是唯一强制档:跨卡触面、探针与未申报套件的消费漏网只有全量闸能抓(迭代 44 实证),关联档选测的盲区由它兜底。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期手动跑批按需选档(按文件性质判域:代码面→`--code`,文档件→`--docs`,两类都有→全量;`--code` 关联档选测——02 只跑「需求卡片申报 ∪ diff 推导」并集套件、00 冒烟/01 系统测试/09 静态扫描三件保险恒跑、横切面回落全量——算法见闸脚本),手动跑不构成验收。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=落批时人工确认闸可跑通且判据口径未变;闸真崩溃时 CI 判红在案(跑不出回执=verdict 红,fail-closed 语义保留) -9. **重跑语义(2026-10-30 用户裁,取代原「每批一次/合并后零复跑」口径)**——同一份字节禁止第二次人手跑;允许的重跑只剩两类:① CI 红后的修复合回(post-merge 钩子复验,每轮修复至多触发一轮);② 用户自发发起的全量回归。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。类名缓存刷新与开机确认由 CI 跑批起手接管(§1 第 2 条),合并后无手工动作 +8. **合回后 CI 验证(唯一强制验证点)**——主工作区 `git merge --no-ff <任务分支>` 落地后 post-merge 钩子自动推送 origin master 触发远端 CI;远端按改动域分流(§1 第 3 条),游戏码批跑全量域闸(不带参数档,02 全套件),判定与红项由 `ci_watch.sh remote` 写回 `game/tests/.gates/ci-status.json`。全量是游戏码批的强制档:跨卡触面、探针与未申报套件的消费漏网只有全量闸能抓(迭代 44 实证),关联档选测的盲区由它兜底。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。`tools/run_all_gates.sh` 是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。开发期手动跑批按需选档(按文件性质判域:代码面→`--code`,文档件→`--docs`,两类都有→全量;`--code` 关联档选测——02 只跑「需求卡片申报 ∪ diff 推导」并集套件、00 冒烟/01 系统测试/09 静态扫描三件保险恒跑、横切面回落全量——算法见闸脚本),手动跑不构成验收。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占 CI 全量——其质量义务=落批时人工确认闸可跑通且判据口径未变;闸真崩溃时远端 CI 判红在案(job 失败=verdict 红,fail-closed 语义保留) +9. **重跑语义(2026-10-30 用户裁,取代原「每批一次/合并后零复跑」口径)**——同一份字节禁止第二次人手跑;允许的重跑只剩两类:① CI 红后的修复合回(post-merge 钩子再推再验,每轮修复至多触发一轮);② 用户自发发起的全量回归。「跑都跑了再跑一遍保险」「顺手改点再补一跑」都不构成重跑理由。导入缓存刷新由远端工作流「导入缓存刷新」步接管,合并后无手工动作 10. **批次完成后的登记义务**——合回 master 后按 doc-conventions §12.1 推进待办清单条目状态(做完整条删除),涉及迭代编排的在 roadmap 迭代总表与顶部「当前位」节换代——执行入口=项目技能 batch-closure(批后收尾与中间文档退役),跨会话教训写记忆库。批次过程与结论由 git 提交信息承载,禁止再写会话状态文件(见 doc-conventions 第 10 节)。`design/` 与根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)实现批禁写,见 §12 11. **文档同步闸(实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,**另禁写 `game/tests/README.md`(能力清单,归写卡段维护,test-conventions §8.9)**,要往外交代的东西一律落 `reports/`:实现报告 `reports/iter-实现报告-<日期>.md`(各卡状态+实机目检总清单+遗留读数义务+实现决定记录——超白名单/超卡片明列范围事项逐条记「决了什么/为什么/代价/撤销方式」,全部按卡执行时只写一行「零拍板」)、GDD 反哺点清单 `reports/iter-反哺点-<日期>.md`(每条写清哪个文件哪一节、改成什么、依据是什么)。设计权威文档(GDD/art-bible)**任何批都不直改**:用户在会话中逐点裁决后,由工程/文档设计改动批(直改类)写入 `design/`(执行入口=doc-design 技能),并把反哺条目从 `reports/` 清掉(吸收即死;退役执行=batch-closure 技能)。机械面文档(roadmap 行/术语表/README 工具注)同样归工程/文档设计改动批在合并前更新。反哺点未落地期间相关 GDD 节视为待反哺,读前先查 `reports/` 与待办清单;需求卡片必列「关联文档同步面」按两层标注 12. **实机目检归用户,只登记不阻塞**——真人检测项登记进实现报告目检总清单与体验验证清单即完成批内义务;禁把等目检当停工理由或完成前置 @@ -62,7 +62,7 @@ **MC/verifier 触发细则**:仅在数值验证场景触发(改数值前后取证);normal 档为主、perfect 按需;**无数值改动的会话禁跑 MC**;结构位移类「存档锚」须先向用户请示定性再跑;需求卡片预设步骤的前提消失时(如「带值裁定后重锚」实际未投值)停问勿照字面执行。 -**分级周期口径**:全部验证闸收编在 `tools/run_all_gates.sh` 一个入口下(**脚本即清单,且即域划分的唯一出处**),分三档——`--code`/`--docs` 按本批改动域只跑该域闸(闸件清单与域划分只写在脚本头部注释,本文不重列);`--quick` 是全量档的中段自检形态(跳过渲染闸与静态扫描);**不带参数=两域全量**。强制验证点=合回后本地 CI 的全量档(§1 第 2 条);分档只服务开发期手动跑批。真人实机目检归用户不进自动闸。 +**分级周期口径**:全部验证闸收编在 `tools/run_all_gates.sh` 一个入口下(**脚本即清单,且即域划分的唯一出处**),分三档——`--code`/`--docs` 按本批改动域只跑该域闸(闸件清单与域划分只写在脚本头部注释,本文不重列);`--quick` 是全量档的中段自检形态(跳过渲染闸与静态扫描);**不带参数=两域全量**。强制验证点=合回后远端 CI(游戏码批走全量档,细则见 §1 第 3 条);分档只服务开发期手动跑批。真人实机目检归用户不进自动闸。 ## 5. MC 采集档位细则 diff --git a/design/engineering/test-conventions.md b/design/engineering/test-conventions.md index 039058c1..5d5b95d8 100644 --- a/design/engineering/test-conventions.md +++ b/design/engineering/test-conventions.md @@ -25,9 +25,9 @@ 每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(例:封印动作耗尽 AP → 必须进入第 2 轮; 选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。 -## 4. 验证时机(合回后本地 CI 制,2026-10-30 换代) +## 4. 验证时机(合回后远端 CI 制,2026-11-03 换代) -开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,process-conventions §6)。全量验证归合回后的本地 CI:git merge 合回 master 触发 post-merge 钩子跑 `tools/run_all_gates.sh` 不带参数档(两域全量,02 全套件——跨卡触面与关联档选测的盲区由全量兜底),结果落 `game/tests/.gates/ci-status.json`;CI 红态下修复合回落地即由 post-merge 钩子复验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。 +开发期窄测义务不变:每批开发中,本批新增/改动的验收测试自己跑绿即收口(卡内只测自己改的东西,process-conventions §6)。全量验证归合回后的远端 CI:git merge 合回 master 落地后 post-merge 钩子推送 origin master,远端按改动域分流跑 `tools/run_all_gates.sh`(游戏码批=不带参数档两域全量、02 全套件——跨卡触面与关联档选测的盲区由全量兜底;文档批=文档域秒级闸;分流算法单点=`tools/ci/domain_route.sh`),回执由 `tools/ci_watch.sh remote` 轮询写回 `game/tests/.gates/ci-status.json`;CI 红态下修复合回落地即再推再验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1;本地跑批件 ci_runner.sh 与本地追绿钩子 post-commit 已退役)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。 行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。 ## 5. 测试函数怎么写——验收清单条目的落码规范 diff --git a/game/README.md b/game/README.md index 962962a5..4ce89c8d 100644 --- a/game/README.md +++ b/game/README.md @@ -50,7 +50,7 @@ Godot_v4.7.2-stable_win64_console.exe --headless --path . --script res://sim/adv **试玩画像口径(2026-09-19 用户拍板固化——全项目试玩/扫描/面板模拟一律只用以下两档,随机策略口径退役)**:`policy="normal"` = **普通画像·基础定式**(单数字满足规则——满足即标/满足即开,无处可推盲挖边界格、不刷新;教学后正常玩家的下限近似);`policy="perfect"` = **完美画像**(single+subset 全推理零盲挖/卡死刷新——技能上界仪)。实现单一出口 = `sim/auto_player.gd`(novice_action / perfect_action)经 `sim/level_autoplay.gd`,调试浮窗批量模拟与各扫描工具自动同口径;战斗层冒烟夹具与调试转储的 `choose_dig` 直调是机制测试夹具、不承载画像语义。分级依据与新旧读数(乱点 0%/5% → 基础定式 65%/90%)见 auto_player.gd 普通策略档节与台账 77。 单关诊断工具(level_debug.gd)随单关直入口口径退役(2026-09-23)——败退归因走 balance_verifier 冒险段(层/节点/敌人人读归因)。 -战斗级工具(v1 遗产):`dump_battle.gd`(结算流水)/ `monte_carlo.gd`(战斗期望)/ `experiment_density.gd`(密度调参)。 +战斗级工具(v1 遗产):`dump_battle.gd`(结算流水)/ `monte_carlo.gd`(战斗期望)。 ## 范围(v1 范围 + v2 追加,用户 2026-09-10/11 确认) diff --git a/game/assets/art/board/seal_mark.png b/game/assets/art/board/seal_mark.png new file mode 100644 index 00000000..ddf875e1 Binary files /dev/null and b/game/assets/art/board/seal_mark.png differ diff --git a/game/assets/art/board/seal_mark.png.import b/game/assets/art/board/seal_mark.png.import new file mode 100644 index 00000000..769ad7d8 --- /dev/null +++ b/game/assets/art/board/seal_mark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://7mgi36hqrvej" +path="res://.godot/imported/seal_mark.png-4e00c69f08466c44d297a28c90752754.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/seal_mark.png" +dest_files=["res://.godot/imported/seal_mark.png-4e00c69f08466c44d297a28c90752754.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_deep_underdark_1.png b/game/assets/art/board/terrain_floor_deep_underdark_1.png new file mode 100644 index 00000000..79ee9353 Binary files /dev/null and b/game/assets/art/board/terrain_floor_deep_underdark_1.png differ diff --git a/game/assets/art/board/terrain_floor_deep_underdark_1.png.import b/game/assets/art/board/terrain_floor_deep_underdark_1.png.import new file mode 100644 index 00000000..4cb607d9 --- /dev/null +++ b/game/assets/art/board/terrain_floor_deep_underdark_1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bqvh7631mcuaj" +path="res://.godot/imported/terrain_floor_deep_underdark_1.png-c1b190b8e4b3895184bdd0ee6bd74f82.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_deep_underdark_1.png" +dest_files=["res://.godot/imported/terrain_floor_deep_underdark_1.png-c1b190b8e4b3895184bdd0ee6bd74f82.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_deep_underdark_2.png b/game/assets/art/board/terrain_floor_deep_underdark_2.png new file mode 100644 index 00000000..854ae1c9 Binary files /dev/null and b/game/assets/art/board/terrain_floor_deep_underdark_2.png differ diff --git a/game/assets/art/board/terrain_floor_deep_underdark_2.png.import b/game/assets/art/board/terrain_floor_deep_underdark_2.png.import new file mode 100644 index 00000000..ab154447 --- /dev/null +++ b/game/assets/art/board/terrain_floor_deep_underdark_2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://x6sl5ht6n2uc" +path="res://.godot/imported/terrain_floor_deep_underdark_2.png-c0469b1cf1af026328686618be34f5d3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_deep_underdark_2.png" +dest_files=["res://.godot/imported/terrain_floor_deep_underdark_2.png-c0469b1cf1af026328686618be34f5d3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_deep_underdark_3.png b/game/assets/art/board/terrain_floor_deep_underdark_3.png new file mode 100644 index 00000000..451109dc Binary files /dev/null and b/game/assets/art/board/terrain_floor_deep_underdark_3.png differ diff --git a/game/assets/art/board/terrain_floor_deep_underdark_3.png.import b/game/assets/art/board/terrain_floor_deep_underdark_3.png.import new file mode 100644 index 00000000..6693993a --- /dev/null +++ b/game/assets/art/board/terrain_floor_deep_underdark_3.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blj41w52x8poi" +path="res://.godot/imported/terrain_floor_deep_underdark_3.png-f159b9c2d516f78ef7d19b05a4631d11.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_deep_underdark_3.png" +dest_files=["res://.godot/imported/terrain_floor_deep_underdark_3.png-f159b9c2d516f78ef7d19b05a4631d11.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_deep_underdark_4.png b/game/assets/art/board/terrain_floor_deep_underdark_4.png new file mode 100644 index 00000000..436dd4f0 Binary files /dev/null and b/game/assets/art/board/terrain_floor_deep_underdark_4.png differ diff --git a/game/assets/art/board/terrain_floor_deep_underdark_4.png.import b/game/assets/art/board/terrain_floor_deep_underdark_4.png.import new file mode 100644 index 00000000..8f1a2858 --- /dev/null +++ b/game/assets/art/board/terrain_floor_deep_underdark_4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3bwubcxsl5jq" +path="res://.godot/imported/terrain_floor_deep_underdark_4.png-f5f15b985679a255ff00e1b8688e15b2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_deep_underdark_4.png" +dest_files=["res://.godot/imported/terrain_floor_deep_underdark_4.png-f5f15b985679a255ff00e1b8688e15b2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_deep_underdark_5.png b/game/assets/art/board/terrain_floor_deep_underdark_5.png new file mode 100644 index 00000000..eb1ca36e Binary files /dev/null and b/game/assets/art/board/terrain_floor_deep_underdark_5.png differ diff --git a/game/assets/art/board/terrain_floor_deep_underdark_5.png.import b/game/assets/art/board/terrain_floor_deep_underdark_5.png.import new file mode 100644 index 00000000..e7cc8e97 --- /dev/null +++ b/game/assets/art/board/terrain_floor_deep_underdark_5.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cv07q7w556bwy" +path="res://.godot/imported/terrain_floor_deep_underdark_5.png-143a53136daea878fff67d8ac9e05c42.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_deep_underdark_5.png" +dest_files=["res://.godot/imported/terrain_floor_deep_underdark_5.png-143a53136daea878fff67d8ac9e05c42.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_dungeon_1.png b/game/assets/art/board/terrain_floor_dungeon_1.png new file mode 100644 index 00000000..4851e3bc Binary files /dev/null and b/game/assets/art/board/terrain_floor_dungeon_1.png differ diff --git a/game/assets/art/board/terrain_floor_dungeon_1.png.import b/game/assets/art/board/terrain_floor_dungeon_1.png.import new file mode 100644 index 00000000..9f14b819 --- /dev/null +++ b/game/assets/art/board/terrain_floor_dungeon_1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b65onwkof3wsy" +path="res://.godot/imported/terrain_floor_dungeon_1.png-40bb7fc26d6ced997a8f1cc9005b6f18.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_dungeon_1.png" +dest_files=["res://.godot/imported/terrain_floor_dungeon_1.png-40bb7fc26d6ced997a8f1cc9005b6f18.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_dungeon_2.png b/game/assets/art/board/terrain_floor_dungeon_2.png new file mode 100644 index 00000000..3211eb39 Binary files /dev/null and b/game/assets/art/board/terrain_floor_dungeon_2.png differ diff --git a/game/assets/art/board/terrain_floor_dungeon_2.png.import b/game/assets/art/board/terrain_floor_dungeon_2.png.import new file mode 100644 index 00000000..b14ebbf7 --- /dev/null +++ b/game/assets/art/board/terrain_floor_dungeon_2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ddqxia2sv5ktq" +path="res://.godot/imported/terrain_floor_dungeon_2.png-c4869c7b250ede2771251e1c17af6563.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_dungeon_2.png" +dest_files=["res://.godot/imported/terrain_floor_dungeon_2.png-c4869c7b250ede2771251e1c17af6563.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_dungeon_3.png b/game/assets/art/board/terrain_floor_dungeon_3.png new file mode 100644 index 00000000..bb53aa6e Binary files /dev/null and b/game/assets/art/board/terrain_floor_dungeon_3.png differ diff --git a/game/assets/art/board/terrain_floor_dungeon_3.png.import b/game/assets/art/board/terrain_floor_dungeon_3.png.import new file mode 100644 index 00000000..5f011ca0 --- /dev/null +++ b/game/assets/art/board/terrain_floor_dungeon_3.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bieystjeu21q4" +path="res://.godot/imported/terrain_floor_dungeon_3.png-148125c305ac1d93b24d178431bf294e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_dungeon_3.png" +dest_files=["res://.godot/imported/terrain_floor_dungeon_3.png-148125c305ac1d93b24d178431bf294e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_dungeon_4.png b/game/assets/art/board/terrain_floor_dungeon_4.png new file mode 100644 index 00000000..601aaedf Binary files /dev/null and b/game/assets/art/board/terrain_floor_dungeon_4.png differ diff --git a/game/assets/art/board/terrain_floor_dungeon_4.png.import b/game/assets/art/board/terrain_floor_dungeon_4.png.import new file mode 100644 index 00000000..4858a3c7 --- /dev/null +++ b/game/assets/art/board/terrain_floor_dungeon_4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://q5eko1txuoq5" +path="res://.godot/imported/terrain_floor_dungeon_4.png-3fe81a46ece4b02dc031045919a12259.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_dungeon_4.png" +dest_files=["res://.godot/imported/terrain_floor_dungeon_4.png-3fe81a46ece4b02dc031045919a12259.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_dungeon_5.png b/game/assets/art/board/terrain_floor_dungeon_5.png new file mode 100644 index 00000000..b830793d Binary files /dev/null and b/game/assets/art/board/terrain_floor_dungeon_5.png differ diff --git a/game/assets/art/board/terrain_floor_dungeon_5.png.import b/game/assets/art/board/terrain_floor_dungeon_5.png.import new file mode 100644 index 00000000..f2e5f169 --- /dev/null +++ b/game/assets/art/board/terrain_floor_dungeon_5.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvyjsdrcsysi1" +path="res://.godot/imported/terrain_floor_dungeon_5.png-de6614fc89049d19ea41225a836f9d75.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_dungeon_5.png" +dest_files=["res://.godot/imported/terrain_floor_dungeon_5.png-de6614fc89049d19ea41225a836f9d75.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_underdark_1.png b/game/assets/art/board/terrain_floor_underdark_1.png new file mode 100644 index 00000000..58bbef8f Binary files /dev/null and b/game/assets/art/board/terrain_floor_underdark_1.png differ diff --git a/game/assets/art/board/terrain_floor_underdark_1.png.import b/game/assets/art/board/terrain_floor_underdark_1.png.import new file mode 100644 index 00000000..d247f281 --- /dev/null +++ b/game/assets/art/board/terrain_floor_underdark_1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tql72osnj7b4" +path="res://.godot/imported/terrain_floor_underdark_1.png-f600aa8d0e421e81abdeacc2ca1e684d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_underdark_1.png" +dest_files=["res://.godot/imported/terrain_floor_underdark_1.png-f600aa8d0e421e81abdeacc2ca1e684d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_underdark_2.png b/game/assets/art/board/terrain_floor_underdark_2.png new file mode 100644 index 00000000..bc81def6 Binary files /dev/null and b/game/assets/art/board/terrain_floor_underdark_2.png differ diff --git a/game/assets/art/board/terrain_floor_underdark_2.png.import b/game/assets/art/board/terrain_floor_underdark_2.png.import new file mode 100644 index 00000000..5d235be2 --- /dev/null +++ b/game/assets/art/board/terrain_floor_underdark_2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5wep4y81o1jw" +path="res://.godot/imported/terrain_floor_underdark_2.png-af44e8e4b98a05a2d39075dbcd9d4e01.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_underdark_2.png" +dest_files=["res://.godot/imported/terrain_floor_underdark_2.png-af44e8e4b98a05a2d39075dbcd9d4e01.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_underdark_3.png b/game/assets/art/board/terrain_floor_underdark_3.png new file mode 100644 index 00000000..fed5e8fd Binary files /dev/null and b/game/assets/art/board/terrain_floor_underdark_3.png differ diff --git a/game/assets/art/board/terrain_floor_underdark_3.png.import b/game/assets/art/board/terrain_floor_underdark_3.png.import new file mode 100644 index 00000000..37166c30 --- /dev/null +++ b/game/assets/art/board/terrain_floor_underdark_3.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dui4ogsp407gh" +path="res://.godot/imported/terrain_floor_underdark_3.png-2b02c10af1a0cdae05a3e6c4b62383db.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_underdark_3.png" +dest_files=["res://.godot/imported/terrain_floor_underdark_3.png-2b02c10af1a0cdae05a3e6c4b62383db.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_underdark_4.png b/game/assets/art/board/terrain_floor_underdark_4.png new file mode 100644 index 00000000..827034ce Binary files /dev/null and b/game/assets/art/board/terrain_floor_underdark_4.png differ diff --git a/game/assets/art/board/terrain_floor_underdark_4.png.import b/game/assets/art/board/terrain_floor_underdark_4.png.import new file mode 100644 index 00000000..1b28f0fd --- /dev/null +++ b/game/assets/art/board/terrain_floor_underdark_4.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cmhm5txfhgwx3" +path="res://.godot/imported/terrain_floor_underdark_4.png-f1fdca02c60c236ff6c7656d2c63a42e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_underdark_4.png" +dest_files=["res://.godot/imported/terrain_floor_underdark_4.png-f1fdca02c60c236ff6c7656d2c63a42e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_floor_underdark_5.png b/game/assets/art/board/terrain_floor_underdark_5.png new file mode 100644 index 00000000..b9f29454 Binary files /dev/null and b/game/assets/art/board/terrain_floor_underdark_5.png differ diff --git a/game/assets/art/board/terrain_floor_underdark_5.png.import b/game/assets/art/board/terrain_floor_underdark_5.png.import new file mode 100644 index 00000000..e36416ea --- /dev/null +++ b/game/assets/art/board/terrain_floor_underdark_5.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://l2rq0unla3uv" +path="res://.godot/imported/terrain_floor_underdark_5.png-b581b01cb084ee52178ca96663e73933.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_floor_underdark_5.png" +dest_files=["res://.godot/imported/terrain_floor_underdark_5.png-b581b01cb084ee52178ca96663e73933.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_crack_deep_underdark.png b/game/assets/art/board/terrain_wall_center_crack_deep_underdark.png new file mode 100644 index 00000000..12ed78a6 Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_crack_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_center_crack_deep_underdark.png.import b/game/assets/art/board/terrain_wall_center_crack_deep_underdark.png.import new file mode 100644 index 00000000..50457097 --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_crack_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://rgaccyhfmihw" +path="res://.godot/imported/terrain_wall_center_crack_deep_underdark.png-2d56722bbd6050f6b795a625b3edaa6c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_crack_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_center_crack_deep_underdark.png-2d56722bbd6050f6b795a625b3edaa6c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_crack_dungeon.png b/game/assets/art/board/terrain_wall_center_crack_dungeon.png new file mode 100644 index 00000000..2aee016e Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_crack_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_center_crack_dungeon.png.import b/game/assets/art/board/terrain_wall_center_crack_dungeon.png.import new file mode 100644 index 00000000..1c877644 --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_crack_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cgr1rjgu7yw3v" +path="res://.godot/imported/terrain_wall_center_crack_dungeon.png-df45d525c0fd838550460540defe30f2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_crack_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_center_crack_dungeon.png-df45d525c0fd838550460540defe30f2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_crack_underdark.png b/game/assets/art/board/terrain_wall_center_crack_underdark.png new file mode 100644 index 00000000..d17ef75a Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_crack_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_center_crack_underdark.png.import b/game/assets/art/board/terrain_wall_center_crack_underdark.png.import new file mode 100644 index 00000000..473ea274 --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_crack_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bv36y7mva45uy" +path="res://.godot/imported/terrain_wall_center_crack_underdark.png-8ca77a6201e1431a0db830c2275ad51e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_crack_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_center_crack_underdark.png-8ca77a6201e1431a0db830c2275ad51e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_deep_underdark.png b/game/assets/art/board/terrain_wall_center_deep_underdark.png new file mode 100644 index 00000000..2c61cf83 Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_center_deep_underdark.png.import b/game/assets/art/board/terrain_wall_center_deep_underdark.png.import new file mode 100644 index 00000000..26be9c37 --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvhyhx04wiouj" +path="res://.godot/imported/terrain_wall_center_deep_underdark.png-21dba86720ce186d7016925f8a498d80.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_center_deep_underdark.png-21dba86720ce186d7016925f8a498d80.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_dungeon.png b/game/assets/art/board/terrain_wall_center_dungeon.png new file mode 100644 index 00000000..796b1c84 Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_center_dungeon.png.import b/game/assets/art/board/terrain_wall_center_dungeon.png.import new file mode 100644 index 00000000..4ec18e73 --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://qxf2g7lov6th" +path="res://.godot/imported/terrain_wall_center_dungeon.png-658b0d351ec8a12f85a4143d159fb3fd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_center_dungeon.png-658b0d351ec8a12f85a4143d159fb3fd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_speckle_deep_underdark.png b/game/assets/art/board/terrain_wall_center_speckle_deep_underdark.png new file mode 100644 index 00000000..abf7f96f Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_speckle_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_center_speckle_deep_underdark.png.import b/game/assets/art/board/terrain_wall_center_speckle_deep_underdark.png.import new file mode 100644 index 00000000..3ed807d9 --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_speckle_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://l7y5j2ksy8ax" +path="res://.godot/imported/terrain_wall_center_speckle_deep_underdark.png-18c3524fcd66d9ed2ed95e2e87467104.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_speckle_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_center_speckle_deep_underdark.png-18c3524fcd66d9ed2ed95e2e87467104.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_speckle_dungeon.png b/game/assets/art/board/terrain_wall_center_speckle_dungeon.png new file mode 100644 index 00000000..cfb85b97 Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_speckle_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_center_speckle_dungeon.png.import b/game/assets/art/board/terrain_wall_center_speckle_dungeon.png.import new file mode 100644 index 00000000..2a6ba912 --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_speckle_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cf8sm7ryxvgph" +path="res://.godot/imported/terrain_wall_center_speckle_dungeon.png-2ccd555fef371b488c3dc218ae241295.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_speckle_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_center_speckle_dungeon.png-2ccd555fef371b488c3dc218ae241295.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_speckle_underdark.png b/game/assets/art/board/terrain_wall_center_speckle_underdark.png new file mode 100644 index 00000000..5bd9d774 Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_speckle_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_center_speckle_underdark.png.import b/game/assets/art/board/terrain_wall_center_speckle_underdark.png.import new file mode 100644 index 00000000..c84df32c --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_speckle_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://db0pevpne83kk" +path="res://.godot/imported/terrain_wall_center_speckle_underdark.png-a6e4207e2f82bef11f1ec867f81aeef8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_speckle_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_center_speckle_underdark.png-a6e4207e2f82bef11f1ec867f81aeef8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_center_underdark.png b/game/assets/art/board/terrain_wall_center_underdark.png new file mode 100644 index 00000000..1cd39d08 Binary files /dev/null and b/game/assets/art/board/terrain_wall_center_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_center_underdark.png.import b/game/assets/art/board/terrain_wall_center_underdark.png.import new file mode 100644 index 00000000..4670f55d --- /dev/null +++ b/game/assets/art/board/terrain_wall_center_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cvo0jytgh8bgh" +path="res://.godot/imported/terrain_wall_center_underdark.png-4586560d6659927249e74c4aef1c55db.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_center_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_center_underdark.png-4586560d6659927249e74c4aef1c55db.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_n_deep_underdark.png b/game/assets/art/board/terrain_wall_end_n_deep_underdark.png new file mode 100644 index 00000000..8d710978 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_n_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_end_n_deep_underdark.png.import b/game/assets/art/board/terrain_wall_end_n_deep_underdark.png.import new file mode 100644 index 00000000..72947459 --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_n_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ebb36udrrlco" +path="res://.godot/imported/terrain_wall_end_n_deep_underdark.png-953a459c1d7d70cb54ff21d6e65e947e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_n_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_end_n_deep_underdark.png-953a459c1d7d70cb54ff21d6e65e947e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_n_dungeon.png b/game/assets/art/board/terrain_wall_end_n_dungeon.png new file mode 100644 index 00000000..bc126ab6 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_n_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_end_n_dungeon.png.import b/game/assets/art/board/terrain_wall_end_n_dungeon.png.import new file mode 100644 index 00000000..5ed9e401 --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_n_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bjkoixrqxyfvi" +path="res://.godot/imported/terrain_wall_end_n_dungeon.png-0292ee08e0a9524fae5fce50c83e27a0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_n_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_end_n_dungeon.png-0292ee08e0a9524fae5fce50c83e27a0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_n_underdark.png b/game/assets/art/board/terrain_wall_end_n_underdark.png new file mode 100644 index 00000000..d2ba1c50 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_n_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_end_n_underdark.png.import b/game/assets/art/board/terrain_wall_end_n_underdark.png.import new file mode 100644 index 00000000..ac11cbac --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_n_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dwisuhgfosirs" +path="res://.godot/imported/terrain_wall_end_n_underdark.png-623c49a4e05a066226c2dfb6e066d735.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_n_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_end_n_underdark.png-623c49a4e05a066226c2dfb6e066d735.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_s_deep_underdark.png b/game/assets/art/board/terrain_wall_end_s_deep_underdark.png new file mode 100644 index 00000000..36030068 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_s_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_end_s_deep_underdark.png.import b/game/assets/art/board/terrain_wall_end_s_deep_underdark.png.import new file mode 100644 index 00000000..f22138c3 --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_s_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c3jin8ytakl8a" +path="res://.godot/imported/terrain_wall_end_s_deep_underdark.png-6c06ef145e41ecfc9bbdfc330ea28e29.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_s_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_end_s_deep_underdark.png-6c06ef145e41ecfc9bbdfc330ea28e29.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_s_dungeon.png b/game/assets/art/board/terrain_wall_end_s_dungeon.png new file mode 100644 index 00000000..13618f17 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_s_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_end_s_dungeon.png.import b/game/assets/art/board/terrain_wall_end_s_dungeon.png.import new file mode 100644 index 00000000..9bd24b30 --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_s_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dxu6my20xmrcw" +path="res://.godot/imported/terrain_wall_end_s_dungeon.png-57e9f35af5ee5188529d19914d6ca77c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_s_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_end_s_dungeon.png-57e9f35af5ee5188529d19914d6ca77c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_s_underdark.png b/game/assets/art/board/terrain_wall_end_s_underdark.png new file mode 100644 index 00000000..60d01669 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_s_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_end_s_underdark.png.import b/game/assets/art/board/terrain_wall_end_s_underdark.png.import new file mode 100644 index 00000000..c0345477 --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_s_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4vqrhnwi6dt" +path="res://.godot/imported/terrain_wall_end_s_underdark.png-bc9e276e09623b0334da8ed6dfe0eab1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_s_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_end_s_underdark.png-bc9e276e09623b0334da8ed6dfe0eab1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_w_deep_underdark.png b/game/assets/art/board/terrain_wall_end_w_deep_underdark.png new file mode 100644 index 00000000..3901d0c2 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_w_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_end_w_deep_underdark.png.import b/game/assets/art/board/terrain_wall_end_w_deep_underdark.png.import new file mode 100644 index 00000000..55fec3be --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_w_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bii82y3sk4grg" +path="res://.godot/imported/terrain_wall_end_w_deep_underdark.png-d8d8a5cb939f178b0b824311d090e725.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_w_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_end_w_deep_underdark.png-d8d8a5cb939f178b0b824311d090e725.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_w_dungeon.png b/game/assets/art/board/terrain_wall_end_w_dungeon.png new file mode 100644 index 00000000..34aa583f Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_w_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_end_w_dungeon.png.import b/game/assets/art/board/terrain_wall_end_w_dungeon.png.import new file mode 100644 index 00000000..6ab2eb30 --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_w_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b65l0bhfd558i" +path="res://.godot/imported/terrain_wall_end_w_dungeon.png-3aad2e0c1657cf6eebf27b020b395b2c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_w_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_end_w_dungeon.png-3aad2e0c1657cf6eebf27b020b395b2c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_end_w_underdark.png b/game/assets/art/board/terrain_wall_end_w_underdark.png new file mode 100644 index 00000000..56002e77 Binary files /dev/null and b/game/assets/art/board/terrain_wall_end_w_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_end_w_underdark.png.import b/game/assets/art/board/terrain_wall_end_w_underdark.png.import new file mode 100644 index 00000000..5f3c32d9 --- /dev/null +++ b/game/assets/art/board/terrain_wall_end_w_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cd5s6k3b7im7e" +path="res://.godot/imported/terrain_wall_end_w_underdark.png-9186bc905359de8487544281666215d5.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_end_w_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_end_w_underdark.png-9186bc905359de8487544281666215d5.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_hrun_deep_underdark.png b/game/assets/art/board/terrain_wall_hrun_deep_underdark.png new file mode 100644 index 00000000..7b9ea546 Binary files /dev/null and b/game/assets/art/board/terrain_wall_hrun_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_hrun_deep_underdark.png.import b/game/assets/art/board/terrain_wall_hrun_deep_underdark.png.import new file mode 100644 index 00000000..cfe6fe84 --- /dev/null +++ b/game/assets/art/board/terrain_wall_hrun_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5p724r8nn4d0" +path="res://.godot/imported/terrain_wall_hrun_deep_underdark.png-0420654e445530a86d1d34034a31da6d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_hrun_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_hrun_deep_underdark.png-0420654e445530a86d1d34034a31da6d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_hrun_dungeon.png b/game/assets/art/board/terrain_wall_hrun_dungeon.png new file mode 100644 index 00000000..f5cd79b6 Binary files /dev/null and b/game/assets/art/board/terrain_wall_hrun_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_hrun_dungeon.png.import b/game/assets/art/board/terrain_wall_hrun_dungeon.png.import new file mode 100644 index 00000000..607a46e2 --- /dev/null +++ b/game/assets/art/board/terrain_wall_hrun_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cbf66ix2absgu" +path="res://.godot/imported/terrain_wall_hrun_dungeon.png-3c9b70e7ac0fb58d990f82e83c6e731f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_hrun_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_hrun_dungeon.png-3c9b70e7ac0fb58d990f82e83c6e731f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_hrun_underdark.png b/game/assets/art/board/terrain_wall_hrun_underdark.png new file mode 100644 index 00000000..90a99e66 Binary files /dev/null and b/game/assets/art/board/terrain_wall_hrun_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_hrun_underdark.png.import b/game/assets/art/board/terrain_wall_hrun_underdark.png.import new file mode 100644 index 00000000..e9e060ac --- /dev/null +++ b/game/assets/art/board/terrain_wall_hrun_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://svrupdcmhsfl" +path="res://.godot/imported/terrain_wall_hrun_underdark.png-4291043e05ce452299e340333ee8e872.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_hrun_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_hrun_underdark.png-4291043e05ce452299e340333ee8e872.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_in_nw_deep_underdark.png b/game/assets/art/board/terrain_wall_in_nw_deep_underdark.png new file mode 100644 index 00000000..c83f80eb Binary files /dev/null and b/game/assets/art/board/terrain_wall_in_nw_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_in_nw_deep_underdark.png.import b/game/assets/art/board/terrain_wall_in_nw_deep_underdark.png.import new file mode 100644 index 00000000..58d16d76 --- /dev/null +++ b/game/assets/art/board/terrain_wall_in_nw_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bo6xkwfjmxosn" +path="res://.godot/imported/terrain_wall_in_nw_deep_underdark.png-2d2512aed9946584c3b8dd0df540cb09.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_in_nw_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_in_nw_deep_underdark.png-2d2512aed9946584c3b8dd0df540cb09.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_in_nw_dungeon.png b/game/assets/art/board/terrain_wall_in_nw_dungeon.png new file mode 100644 index 00000000..5c1266e2 Binary files /dev/null and b/game/assets/art/board/terrain_wall_in_nw_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_in_nw_dungeon.png.import b/game/assets/art/board/terrain_wall_in_nw_dungeon.png.import new file mode 100644 index 00000000..6f89e9ea --- /dev/null +++ b/game/assets/art/board/terrain_wall_in_nw_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdi3c5ap10ylv" +path="res://.godot/imported/terrain_wall_in_nw_dungeon.png-2f1ca8b7a692cfcb82277f1a1f644e81.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_in_nw_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_in_nw_dungeon.png-2f1ca8b7a692cfcb82277f1a1f644e81.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_in_nw_underdark.png b/game/assets/art/board/terrain_wall_in_nw_underdark.png new file mode 100644 index 00000000..cb06e7cb Binary files /dev/null and b/game/assets/art/board/terrain_wall_in_nw_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_in_nw_underdark.png.import b/game/assets/art/board/terrain_wall_in_nw_underdark.png.import new file mode 100644 index 00000000..e7591b0b --- /dev/null +++ b/game/assets/art/board/terrain_wall_in_nw_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cdw5m34cj1i7y" +path="res://.godot/imported/terrain_wall_in_nw_underdark.png-abdbd3fda6332dc184e23a71b3e64694.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_in_nw_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_in_nw_underdark.png-abdbd3fda6332dc184e23a71b3e64694.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_in_sw_deep_underdark.png b/game/assets/art/board/terrain_wall_in_sw_deep_underdark.png new file mode 100644 index 00000000..0b82e6c6 Binary files /dev/null and b/game/assets/art/board/terrain_wall_in_sw_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_in_sw_deep_underdark.png.import b/game/assets/art/board/terrain_wall_in_sw_deep_underdark.png.import new file mode 100644 index 00000000..0081da6c --- /dev/null +++ b/game/assets/art/board/terrain_wall_in_sw_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfdvybhhsiab" +path="res://.godot/imported/terrain_wall_in_sw_deep_underdark.png-95a51c1cfaa299327d9521102d62a8fd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_in_sw_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_in_sw_deep_underdark.png-95a51c1cfaa299327d9521102d62a8fd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_in_sw_dungeon.png b/game/assets/art/board/terrain_wall_in_sw_dungeon.png new file mode 100644 index 00000000..4fd8d1c2 Binary files /dev/null and b/game/assets/art/board/terrain_wall_in_sw_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_in_sw_dungeon.png.import b/game/assets/art/board/terrain_wall_in_sw_dungeon.png.import new file mode 100644 index 00000000..bae01494 --- /dev/null +++ b/game/assets/art/board/terrain_wall_in_sw_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bm1ym8guy8hy7" +path="res://.godot/imported/terrain_wall_in_sw_dungeon.png-af8146ab3ec09f7ec81d311545df2db8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_in_sw_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_in_sw_dungeon.png-af8146ab3ec09f7ec81d311545df2db8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_in_sw_underdark.png b/game/assets/art/board/terrain_wall_in_sw_underdark.png new file mode 100644 index 00000000..b9981cc4 Binary files /dev/null and b/game/assets/art/board/terrain_wall_in_sw_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_in_sw_underdark.png.import b/game/assets/art/board/terrain_wall_in_sw_underdark.png.import new file mode 100644 index 00000000..28503b9f --- /dev/null +++ b/game/assets/art/board/terrain_wall_in_sw_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c0ucsu6l6goha" +path="res://.godot/imported/terrain_wall_in_sw_underdark.png-f6e0900dc4f816e4abc37c0f028e53b1.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_in_sw_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_in_sw_underdark.png-f6e0900dc4f816e4abc37c0f028e53b1.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_lone_deep_underdark.png b/game/assets/art/board/terrain_wall_lone_deep_underdark.png new file mode 100644 index 00000000..49d1c140 Binary files /dev/null and b/game/assets/art/board/terrain_wall_lone_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_lone_deep_underdark.png.import b/game/assets/art/board/terrain_wall_lone_deep_underdark.png.import new file mode 100644 index 00000000..26e03b63 --- /dev/null +++ b/game/assets/art/board/terrain_wall_lone_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://hvnun5n6056x" +path="res://.godot/imported/terrain_wall_lone_deep_underdark.png-1a6b6f4455c09948d8f791bff7c778f8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_lone_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_lone_deep_underdark.png-1a6b6f4455c09948d8f791bff7c778f8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_lone_dungeon.png b/game/assets/art/board/terrain_wall_lone_dungeon.png new file mode 100644 index 00000000..550efaa0 Binary files /dev/null and b/game/assets/art/board/terrain_wall_lone_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_lone_dungeon.png.import b/game/assets/art/board/terrain_wall_lone_dungeon.png.import new file mode 100644 index 00000000..18cca459 --- /dev/null +++ b/game/assets/art/board/terrain_wall_lone_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bm2h8q6lnsj2o" +path="res://.godot/imported/terrain_wall_lone_dungeon.png-52df3c8260010c5129ad3213cd9b9a43.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_lone_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_lone_dungeon.png-52df3c8260010c5129ad3213cd9b9a43.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_lone_underdark.png b/game/assets/art/board/terrain_wall_lone_underdark.png new file mode 100644 index 00000000..8a5d5468 Binary files /dev/null and b/game/assets/art/board/terrain_wall_lone_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_lone_underdark.png.import b/game/assets/art/board/terrain_wall_lone_underdark.png.import new file mode 100644 index 00000000..d159e400 --- /dev/null +++ b/game/assets/art/board/terrain_wall_lone_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://77v8wirdmy68" +path="res://.godot/imported/terrain_wall_lone_underdark.png-7edaf1e8452f7900b29c17db52c0e2fb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_lone_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_lone_underdark.png-7edaf1e8452f7900b29c17db52c0e2fb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_n_deep_underdark.png b/game/assets/art/board/terrain_wall_n_deep_underdark.png new file mode 100644 index 00000000..32e215ea Binary files /dev/null and b/game/assets/art/board/terrain_wall_n_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_n_deep_underdark.png.import b/game/assets/art/board/terrain_wall_n_deep_underdark.png.import new file mode 100644 index 00000000..d2711691 --- /dev/null +++ b/game/assets/art/board/terrain_wall_n_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://82hovv8uc8x8" +path="res://.godot/imported/terrain_wall_n_deep_underdark.png-fbb2006ac471691867c3bfc9bc5f8f4a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_n_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_n_deep_underdark.png-fbb2006ac471691867c3bfc9bc5f8f4a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_n_dungeon.png b/game/assets/art/board/terrain_wall_n_dungeon.png new file mode 100644 index 00000000..da17d322 Binary files /dev/null and b/game/assets/art/board/terrain_wall_n_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_n_dungeon.png.import b/game/assets/art/board/terrain_wall_n_dungeon.png.import new file mode 100644 index 00000000..19900fdd --- /dev/null +++ b/game/assets/art/board/terrain_wall_n_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xi6qqpfssh4f" +path="res://.godot/imported/terrain_wall_n_dungeon.png-92bed658d0d444b4b3af2c7099283ac6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_n_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_n_dungeon.png-92bed658d0d444b4b3af2c7099283ac6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_n_underdark.png b/game/assets/art/board/terrain_wall_n_underdark.png new file mode 100644 index 00000000..a5cf1f1b Binary files /dev/null and b/game/assets/art/board/terrain_wall_n_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_n_underdark.png.import b/game/assets/art/board/terrain_wall_n_underdark.png.import new file mode 100644 index 00000000..c68b7144 --- /dev/null +++ b/game/assets/art/board/terrain_wall_n_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dmdw5yu8tl5bo" +path="res://.godot/imported/terrain_wall_n_underdark.png-67d67be8ad6c55a96d817a8401a23591.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_n_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_n_underdark.png-67d67be8ad6c55a96d817a8401a23591.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_nw_deep_underdark.png b/game/assets/art/board/terrain_wall_nw_deep_underdark.png new file mode 100644 index 00000000..02d6e498 Binary files /dev/null and b/game/assets/art/board/terrain_wall_nw_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_nw_deep_underdark.png.import b/game/assets/art/board/terrain_wall_nw_deep_underdark.png.import new file mode 100644 index 00000000..a318cb9c --- /dev/null +++ b/game/assets/art/board/terrain_wall_nw_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cc4ywbm165883" +path="res://.godot/imported/terrain_wall_nw_deep_underdark.png-79c09758be59b4560293fecb9c18ed05.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_nw_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_nw_deep_underdark.png-79c09758be59b4560293fecb9c18ed05.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_nw_dungeon.png b/game/assets/art/board/terrain_wall_nw_dungeon.png new file mode 100644 index 00000000..bcb184e8 Binary files /dev/null and b/game/assets/art/board/terrain_wall_nw_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_nw_dungeon.png.import b/game/assets/art/board/terrain_wall_nw_dungeon.png.import new file mode 100644 index 00000000..4b90ce8b --- /dev/null +++ b/game/assets/art/board/terrain_wall_nw_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bg8eus334o3xy" +path="res://.godot/imported/terrain_wall_nw_dungeon.png-6b9f23693b836a61cdad264892b98848.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_nw_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_nw_dungeon.png-6b9f23693b836a61cdad264892b98848.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_nw_underdark.png b/game/assets/art/board/terrain_wall_nw_underdark.png new file mode 100644 index 00000000..208d3fd1 Binary files /dev/null and b/game/assets/art/board/terrain_wall_nw_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_nw_underdark.png.import b/game/assets/art/board/terrain_wall_nw_underdark.png.import new file mode 100644 index 00000000..f64fd9ed --- /dev/null +++ b/game/assets/art/board/terrain_wall_nw_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dglh6jyhow4c" +path="res://.godot/imported/terrain_wall_nw_underdark.png-fe712f58bebec5aa7c5efd1227f781c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_nw_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_nw_underdark.png-fe712f58bebec5aa7c5efd1227f781c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_s_deep_underdark.png b/game/assets/art/board/terrain_wall_s_deep_underdark.png new file mode 100644 index 00000000..71a223db Binary files /dev/null and b/game/assets/art/board/terrain_wall_s_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_s_deep_underdark.png.import b/game/assets/art/board/terrain_wall_s_deep_underdark.png.import new file mode 100644 index 00000000..cc153f56 --- /dev/null +++ b/game/assets/art/board/terrain_wall_s_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c438agpdmwdu3" +path="res://.godot/imported/terrain_wall_s_deep_underdark.png-3c678f69636db64772c2edc372d7c317.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_s_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_s_deep_underdark.png-3c678f69636db64772c2edc372d7c317.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_s_dungeon.png b/game/assets/art/board/terrain_wall_s_dungeon.png new file mode 100644 index 00000000..5071d740 Binary files /dev/null and b/game/assets/art/board/terrain_wall_s_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_s_dungeon.png.import b/game/assets/art/board/terrain_wall_s_dungeon.png.import new file mode 100644 index 00000000..4c862571 --- /dev/null +++ b/game/assets/art/board/terrain_wall_s_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://domdlufjlq6nq" +path="res://.godot/imported/terrain_wall_s_dungeon.png-037dfed667cc59ec4668e80a9794926a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_s_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_s_dungeon.png-037dfed667cc59ec4668e80a9794926a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_s_underdark.png b/game/assets/art/board/terrain_wall_s_underdark.png new file mode 100644 index 00000000..34467477 Binary files /dev/null and b/game/assets/art/board/terrain_wall_s_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_s_underdark.png.import b/game/assets/art/board/terrain_wall_s_underdark.png.import new file mode 100644 index 00000000..701b0721 --- /dev/null +++ b/game/assets/art/board/terrain_wall_s_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b0ql3ac3up57t" +path="res://.godot/imported/terrain_wall_s_underdark.png-0100fbd8ef0488e937caa72aa208a5fd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_s_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_s_underdark.png-0100fbd8ef0488e937caa72aa208a5fd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_sw_deep_underdark.png b/game/assets/art/board/terrain_wall_sw_deep_underdark.png new file mode 100644 index 00000000..e3b8cda0 Binary files /dev/null and b/game/assets/art/board/terrain_wall_sw_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_sw_deep_underdark.png.import b/game/assets/art/board/terrain_wall_sw_deep_underdark.png.import new file mode 100644 index 00000000..e0172639 --- /dev/null +++ b/game/assets/art/board/terrain_wall_sw_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2ystne6fwwdq" +path="res://.godot/imported/terrain_wall_sw_deep_underdark.png-2f4d16adc4d6eb4d0098ac25924a2ef3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_sw_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_sw_deep_underdark.png-2f4d16adc4d6eb4d0098ac25924a2ef3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_sw_dungeon.png b/game/assets/art/board/terrain_wall_sw_dungeon.png new file mode 100644 index 00000000..73499d81 Binary files /dev/null and b/game/assets/art/board/terrain_wall_sw_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_sw_dungeon.png.import b/game/assets/art/board/terrain_wall_sw_dungeon.png.import new file mode 100644 index 00000000..8f709c27 --- /dev/null +++ b/game/assets/art/board/terrain_wall_sw_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dq8peiidl8sat" +path="res://.godot/imported/terrain_wall_sw_dungeon.png-330552816fb6b7432c219d2680a44e5c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_sw_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_sw_dungeon.png-330552816fb6b7432c219d2680a44e5c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_sw_underdark.png b/game/assets/art/board/terrain_wall_sw_underdark.png new file mode 100644 index 00000000..0b371a34 Binary files /dev/null and b/game/assets/art/board/terrain_wall_sw_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_sw_underdark.png.import b/game/assets/art/board/terrain_wall_sw_underdark.png.import new file mode 100644 index 00000000..5c1ba34c --- /dev/null +++ b/game/assets/art/board/terrain_wall_sw_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://6sm7bqq31s7s" +path="res://.godot/imported/terrain_wall_sw_underdark.png-f36c6615ac6d087a1c50a968712353a2.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_sw_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_sw_underdark.png-f36c6615ac6d087a1c50a968712353a2.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_vrun_deep_underdark.png b/game/assets/art/board/terrain_wall_vrun_deep_underdark.png new file mode 100644 index 00000000..a75fe6a8 Binary files /dev/null and b/game/assets/art/board/terrain_wall_vrun_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_vrun_deep_underdark.png.import b/game/assets/art/board/terrain_wall_vrun_deep_underdark.png.import new file mode 100644 index 00000000..b3e269f5 --- /dev/null +++ b/game/assets/art/board/terrain_wall_vrun_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://xaxgkg0r3au8" +path="res://.godot/imported/terrain_wall_vrun_deep_underdark.png-152a77f4602a5de6311c4f3dbff9d2cb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_vrun_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_vrun_deep_underdark.png-152a77f4602a5de6311c4f3dbff9d2cb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_vrun_dungeon.png b/game/assets/art/board/terrain_wall_vrun_dungeon.png new file mode 100644 index 00000000..7864db33 Binary files /dev/null and b/game/assets/art/board/terrain_wall_vrun_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_vrun_dungeon.png.import b/game/assets/art/board/terrain_wall_vrun_dungeon.png.import new file mode 100644 index 00000000..a0bf908a --- /dev/null +++ b/game/assets/art/board/terrain_wall_vrun_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://mx6jscg4t428" +path="res://.godot/imported/terrain_wall_vrun_dungeon.png-e4381c9b2b4333532791f5d6ab9b27b8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_vrun_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_vrun_dungeon.png-e4381c9b2b4333532791f5d6ab9b27b8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_vrun_underdark.png b/game/assets/art/board/terrain_wall_vrun_underdark.png new file mode 100644 index 00000000..31ad5d66 Binary files /dev/null and b/game/assets/art/board/terrain_wall_vrun_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_vrun_underdark.png.import b/game/assets/art/board/terrain_wall_vrun_underdark.png.import new file mode 100644 index 00000000..6fd7bbf7 --- /dev/null +++ b/game/assets/art/board/terrain_wall_vrun_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cu6160orutoe6" +path="res://.godot/imported/terrain_wall_vrun_underdark.png-210db3728378ea55e8051118f89e1acf.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_vrun_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_vrun_underdark.png-210db3728378ea55e8051118f89e1acf.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_w_deep_underdark.png b/game/assets/art/board/terrain_wall_w_deep_underdark.png new file mode 100644 index 00000000..3ab8db0e Binary files /dev/null and b/game/assets/art/board/terrain_wall_w_deep_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_w_deep_underdark.png.import b/game/assets/art/board/terrain_wall_w_deep_underdark.png.import new file mode 100644 index 00000000..d252c609 --- /dev/null +++ b/game/assets/art/board/terrain_wall_w_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cryoqyhs63ppr" +path="res://.godot/imported/terrain_wall_w_deep_underdark.png-721382e265020c9dcd3b916ab1b0a301.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_w_deep_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_w_deep_underdark.png-721382e265020c9dcd3b916ab1b0a301.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_w_dungeon.png b/game/assets/art/board/terrain_wall_w_dungeon.png new file mode 100644 index 00000000..a793e266 Binary files /dev/null and b/game/assets/art/board/terrain_wall_w_dungeon.png differ diff --git a/game/assets/art/board/terrain_wall_w_dungeon.png.import b/game/assets/art/board/terrain_wall_w_dungeon.png.import new file mode 100644 index 00000000..135d7a21 --- /dev/null +++ b/game/assets/art/board/terrain_wall_w_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://k5utfbnxinx4" +path="res://.godot/imported/terrain_wall_w_dungeon.png-8efa491fd34ca8272667bd9d146f8bcd.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_w_dungeon.png" +dest_files=["res://.godot/imported/terrain_wall_w_dungeon.png-8efa491fd34ca8272667bd9d146f8bcd.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/board/terrain_wall_w_underdark.png b/game/assets/art/board/terrain_wall_w_underdark.png new file mode 100644 index 00000000..63432296 Binary files /dev/null and b/game/assets/art/board/terrain_wall_w_underdark.png differ diff --git a/game/assets/art/board/terrain_wall_w_underdark.png.import b/game/assets/art/board/terrain_wall_w_underdark.png.import new file mode 100644 index 00000000..42ca69e4 --- /dev/null +++ b/game/assets/art/board/terrain_wall_w_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://2iu5pro6yicu" +path="res://.godot/imported/terrain_wall_w_underdark.png-b4cc22a68fe225462ee73f38fb2f59bc.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/board/terrain_wall_w_underdark.png" +dest_files=["res://.godot/imported/terrain_wall_w_underdark.png-b4cc22a68fe225462ee73f38fb2f59bc.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_bugbear.png b/game/assets/art/character/character_enemy_bugbear.png new file mode 100644 index 00000000..1459c927 Binary files /dev/null and b/game/assets/art/character/character_enemy_bugbear.png differ diff --git a/game/assets/art/character/character_enemy_bugbear.png.import b/game/assets/art/character/character_enemy_bugbear.png.import new file mode 100644 index 00000000..c5cd3bd7 --- /dev/null +++ b/game/assets/art/character/character_enemy_bugbear.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1aq8my5rbybh" +path="res://.godot/imported/character_enemy_bugbear.png-b85db95696ea170d3a02378c1b9cb1b6.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_bugbear.png" +dest_files=["res://.godot/imported/character_enemy_bugbear.png-b85db95696ea170d3a02378c1b9cb1b6.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_drider.png b/game/assets/art/character/character_enemy_drider.png new file mode 100644 index 00000000..eacafc76 Binary files /dev/null and b/game/assets/art/character/character_enemy_drider.png differ diff --git a/game/assets/art/character/character_enemy_drider.png.import b/game/assets/art/character/character_enemy_drider.png.import new file mode 100644 index 00000000..c9b344ca --- /dev/null +++ b/game/assets/art/character/character_enemy_drider.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d1b3u3566fv0o" +path="res://.godot/imported/character_enemy_drider.png-89d380427d47e32b55ca302d2158beaa.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_drider.png" +dest_files=["res://.godot/imported/character_enemy_drider.png-89d380427d47e32b55ca302d2158beaa.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_duergar.png b/game/assets/art/character/character_enemy_duergar.png new file mode 100644 index 00000000..fa34b7e9 Binary files /dev/null and b/game/assets/art/character/character_enemy_duergar.png differ diff --git a/game/assets/art/character/character_enemy_duergar.png.import b/game/assets/art/character/character_enemy_duergar.png.import new file mode 100644 index 00000000..33bb84b1 --- /dev/null +++ b/game/assets/art/character/character_enemy_duergar.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dspobuhs2bv1o" +path="res://.godot/imported/character_enemy_duergar.png-c02537548b2ea756962fa93566ead2ca.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_duergar.png" +dest_files=["res://.godot/imported/character_enemy_duergar.png-c02537548b2ea756962fa93566ead2ca.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_duergar_king.png b/game/assets/art/character/character_enemy_duergar_king.png new file mode 100644 index 00000000..b70daebd Binary files /dev/null and b/game/assets/art/character/character_enemy_duergar_king.png differ diff --git a/game/assets/art/character/character_enemy_duergar_king.png.import b/game/assets/art/character/character_enemy_duergar_king.png.import new file mode 100644 index 00000000..6ba43975 --- /dev/null +++ b/game/assets/art/character/character_enemy_duergar_king.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://7uvsmevmehhj" +path="res://.godot/imported/character_enemy_duergar_king.png-d5e31106329eda800f48ca51edbed39d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_duergar_king.png" +dest_files=["res://.godot/imported/character_enemy_duergar_king.png-d5e31106329eda800f48ca51edbed39d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_giant_spider.png b/game/assets/art/character/character_enemy_giant_spider.png new file mode 100644 index 00000000..6dca3321 Binary files /dev/null and b/game/assets/art/character/character_enemy_giant_spider.png differ diff --git a/game/assets/art/character/character_enemy_giant_spider.png.import b/game/assets/art/character/character_enemy_giant_spider.png.import new file mode 100644 index 00000000..1e792c0b --- /dev/null +++ b/game/assets/art/character/character_enemy_giant_spider.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dpyl4psib7iq3" +path="res://.godot/imported/character_enemy_giant_spider.png-172e2248fcfbc494429b6e3fd1b999c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_giant_spider.png" +dest_files=["res://.godot/imported/character_enemy_giant_spider.png-172e2248fcfbc494429b6e3fd1b999c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_goblin.png b/game/assets/art/character/character_enemy_goblin.png new file mode 100644 index 00000000..c6630f68 Binary files /dev/null and b/game/assets/art/character/character_enemy_goblin.png differ diff --git a/game/assets/art/character/character_enemy_goblin.png.import b/game/assets/art/character/character_enemy_goblin.png.import new file mode 100644 index 00000000..fbf9332c --- /dev/null +++ b/game/assets/art/character/character_enemy_goblin.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://btp31vbcf4cqo" +path="res://.godot/imported/character_enemy_goblin.png-61e8f4ed9f4bde5c1ebad41aac0e2b30.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_goblin.png" +dest_files=["res://.godot/imported/character_enemy_goblin.png-61e8f4ed9f4bde5c1ebad41aac0e2b30.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_hook_horror.png b/game/assets/art/character/character_enemy_hook_horror.png new file mode 100644 index 00000000..fb11697a Binary files /dev/null and b/game/assets/art/character/character_enemy_hook_horror.png differ diff --git a/game/assets/art/character/character_enemy_hook_horror.png.import b/game/assets/art/character/character_enemy_hook_horror.png.import new file mode 100644 index 00000000..452f714f --- /dev/null +++ b/game/assets/art/character/character_enemy_hook_horror.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b2a3gq6llqrbl" +path="res://.godot/imported/character_enemy_hook_horror.png-f7f019e67911860bfd4be9701c6e9b2c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_hook_horror.png" +dest_files=["res://.godot/imported/character_enemy_hook_horror.png-f7f019e67911860bfd4be9701c6e9b2c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_enemy_ogre_chief.png b/game/assets/art/character/character_enemy_ogre_chief.png new file mode 100644 index 00000000..c1d89406 Binary files /dev/null and b/game/assets/art/character/character_enemy_ogre_chief.png differ diff --git a/game/assets/art/character/character_enemy_ogre_chief.png.import b/game/assets/art/character/character_enemy_ogre_chief.png.import new file mode 100644 index 00000000..b7418654 --- /dev/null +++ b/game/assets/art/character/character_enemy_ogre_chief.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cxhjjf70qlwko" +path="res://.godot/imported/character_enemy_ogre_chief.png-cb3ca5d1a22cd8e99e0e0e67f0f8798c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_enemy_ogre_chief.png" +dest_files=["res://.godot/imported/character_enemy_ogre_chief.png-cb3ca5d1a22cd8e99e0e0e67f0f8798c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_hero_attack.png b/game/assets/art/character/character_hero_attack.png new file mode 100644 index 00000000..b3ede0cf Binary files /dev/null and b/game/assets/art/character/character_hero_attack.png differ diff --git a/game/assets/art/character/character_hero_attack.png.import b/game/assets/art/character/character_hero_attack.png.import new file mode 100644 index 00000000..91f91984 --- /dev/null +++ b/game/assets/art/character/character_hero_attack.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bntadl65b6jnw" +path="res://.godot/imported/character_hero_attack.png-93b08f476e3a009b89e396fa1e46e6df.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_hero_attack.png" +dest_files=["res://.godot/imported/character_hero_attack.png-93b08f476e3a009b89e396fa1e46e6df.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_hero_hit.png b/game/assets/art/character/character_hero_hit.png new file mode 100644 index 00000000..ba469fae Binary files /dev/null and b/game/assets/art/character/character_hero_hit.png differ diff --git a/game/assets/art/character/character_hero_hit.png.import b/game/assets/art/character/character_hero_hit.png.import new file mode 100644 index 00000000..af346185 --- /dev/null +++ b/game/assets/art/character/character_hero_hit.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dimscg7o8etlj" +path="res://.godot/imported/character_hero_hit.png-568efe9a72fdfea9f5ec51af2bef96cb.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_hero_hit.png" +dest_files=["res://.godot/imported/character_hero_hit.png-568efe9a72fdfea9f5ec51af2bef96cb.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_hero_idle.png b/game/assets/art/character/character_hero_idle.png new file mode 100644 index 00000000..70262f12 Binary files /dev/null and b/game/assets/art/character/character_hero_idle.png differ diff --git a/game/assets/art/character/character_hero_idle.png.import b/game/assets/art/character/character_hero_idle.png.import new file mode 100644 index 00000000..cb027532 --- /dev/null +++ b/game/assets/art/character/character_hero_idle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://blpk0rxpsdbbp" +path="res://.godot/imported/character_hero_idle.png-3fa7e01fe1358bc085b861a92c27385a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_hero_idle.png" +dest_files=["res://.godot/imported/character_hero_idle.png-3fa7e01fe1358bc085b861a92c27385a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_hero_portrait.png b/game/assets/art/character/character_hero_portrait.png new file mode 100644 index 00000000..2f0c44ba Binary files /dev/null and b/game/assets/art/character/character_hero_portrait.png differ diff --git a/game/assets/art/character/character_hero_portrait.png.import b/game/assets/art/character/character_hero_portrait.png.import new file mode 100644 index 00000000..0cd3a046 --- /dev/null +++ b/game/assets/art/character/character_hero_portrait.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://2cqiu1gqhpdl" +path="res://.godot/imported/character_hero_portrait.png-c00d1ca3a380578f93578e5f57469a61.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_hero_portrait.png" +dest_files=["res://.godot/imported/character_hero_portrait.png-c00d1ca3a380578f93578e5f57469a61.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/character/character_hero_walk.png b/game/assets/art/character/character_hero_walk.png new file mode 100644 index 00000000..44be323c Binary files /dev/null and b/game/assets/art/character/character_hero_walk.png differ diff --git a/game/assets/art/character/character_hero_walk.png.import b/game/assets/art/character/character_hero_walk.png.import new file mode 100644 index 00000000..404633a3 --- /dev/null +++ b/game/assets/art/character/character_hero_walk.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bkatg15m1umhu" +path="res://.godot/imported/character_hero_walk.png-4b44c893a70d6eb9fcf8e28d1af6b0b7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/character/character_hero_walk.png" +dest_files=["res://.godot/imported/character_hero_walk.png-4b44c893a70d6eb9fcf8e28d1af6b0b7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/fx/shovel_swing_f0.png b/game/assets/art/fx/shovel_swing_f0.png new file mode 100644 index 00000000..ab3b0b38 Binary files /dev/null and b/game/assets/art/fx/shovel_swing_f0.png differ diff --git a/game/assets/art/fx/shovel_swing_f0.png.import b/game/assets/art/fx/shovel_swing_f0.png.import new file mode 100644 index 00000000..35b86297 --- /dev/null +++ b/game/assets/art/fx/shovel_swing_f0.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bci06atuxoj2l" +path="res://.godot/imported/shovel_swing_f0.png-06ffc540d3ad1550957a48f3c889b190.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/fx/shovel_swing_f0.png" +dest_files=["res://.godot/imported/shovel_swing_f0.png-06ffc540d3ad1550957a48f3c889b190.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/fx/shovel_swing_f1.png b/game/assets/art/fx/shovel_swing_f1.png new file mode 100644 index 00000000..769e539d Binary files /dev/null and b/game/assets/art/fx/shovel_swing_f1.png differ diff --git a/game/assets/art/fx/shovel_swing_f1.png.import b/game/assets/art/fx/shovel_swing_f1.png.import new file mode 100644 index 00000000..c571bb92 --- /dev/null +++ b/game/assets/art/fx/shovel_swing_f1.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://e47aiashtdlk" +path="res://.godot/imported/shovel_swing_f1.png-84017c125b45d615a7dc6035e51c95ad.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/fx/shovel_swing_f1.png" +dest_files=["res://.godot/imported/shovel_swing_f1.png-84017c125b45d615a7dc6035e51c95ad.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/fx/shovel_swing_f2.png b/game/assets/art/fx/shovel_swing_f2.png new file mode 100644 index 00000000..278353ed Binary files /dev/null and b/game/assets/art/fx/shovel_swing_f2.png differ diff --git a/game/assets/art/fx/shovel_swing_f2.png.import b/game/assets/art/fx/shovel_swing_f2.png.import new file mode 100644 index 00000000..801f740f --- /dev/null +++ b/game/assets/art/fx/shovel_swing_f2.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cesyve5qba48u" +path="res://.godot/imported/shovel_swing_f2.png-90ae5cc0450c344e9a910083341d2225.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/fx/shovel_swing_f2.png" +dest_files=["res://.godot/imported/shovel_swing_f2.png-90ae5cc0450c344e9a910083341d2225.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_bg.png b/game/assets/art/scene/scene_camp_bg.png new file mode 100644 index 00000000..00d5b88c Binary files /dev/null and b/game/assets/art/scene/scene_camp_bg.png differ diff --git a/game/assets/art/scene/scene_camp_bg.png.import b/game/assets/art/scene/scene_camp_bg.png.import new file mode 100644 index 00000000..58570a8b --- /dev/null +++ b/game/assets/art/scene/scene_camp_bg.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bwlimsh4e0u1r" +path="res://.godot/imported/scene_camp_bg.png-ad8da76e848772a9fd1f8364d5ec2282.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_bg.png" +dest_files=["res://.godot/imported/scene_camp_bg.png-ad8da76e848772a9fd1f8364d5ec2282.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_board.png b/game/assets/art/scene/scene_camp_board.png new file mode 100644 index 00000000..ee9c77dc Binary files /dev/null and b/game/assets/art/scene/scene_camp_board.png differ diff --git a/game/assets/art/scene/scene_camp_board.png.import b/game/assets/art/scene/scene_camp_board.png.import new file mode 100644 index 00000000..d902dcad --- /dev/null +++ b/game/assets/art/scene/scene_camp_board.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://biak0b4j1om3a" +path="res://.godot/imported/scene_camp_board.png-2bb40fea7e76e8223a8ee46d42f0564d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_board.png" +dest_files=["res://.godot/imported/scene_camp_board.png-2bb40fea7e76e8223a8ee46d42f0564d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_campfire.png b/game/assets/art/scene/scene_camp_campfire.png new file mode 100644 index 00000000..b7adeaf0 Binary files /dev/null and b/game/assets/art/scene/scene_camp_campfire.png differ diff --git a/game/assets/art/scene/scene_camp_campfire.png.import b/game/assets/art/scene/scene_camp_campfire.png.import new file mode 100644 index 00000000..5767ac0d --- /dev/null +++ b/game/assets/art/scene/scene_camp_campfire.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ndp0uhptgqky" +path="res://.godot/imported/scene_camp_campfire.png-de9b03b71e5cc4cdda7044a26e14a348.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_campfire.png" +dest_files=["res://.godot/imported/scene_camp_campfire.png-de9b03b71e5cc4cdda7044a26e14a348.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_chest.png b/game/assets/art/scene/scene_camp_chest.png new file mode 100644 index 00000000..84713a52 Binary files /dev/null and b/game/assets/art/scene/scene_camp_chest.png differ diff --git a/game/assets/art/scene/scene_camp_chest.png.import b/game/assets/art/scene/scene_camp_chest.png.import new file mode 100644 index 00000000..764c758b --- /dev/null +++ b/game/assets/art/scene/scene_camp_chest.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://k7p13sma4aov" +path="res://.godot/imported/scene_camp_chest.png-8a14e84aaa4c16615ee7c9caf7907074.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_chest.png" +dest_files=["res://.godot/imported/scene_camp_chest.png-8a14e84aaa4c16615ee7c9caf7907074.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_dummy.png b/game/assets/art/scene/scene_camp_dummy.png new file mode 100644 index 00000000..277a206e Binary files /dev/null and b/game/assets/art/scene/scene_camp_dummy.png differ diff --git a/game/assets/art/scene/scene_camp_dummy.png.import b/game/assets/art/scene/scene_camp_dummy.png.import new file mode 100644 index 00000000..a2ead307 --- /dev/null +++ b/game/assets/art/scene/scene_camp_dummy.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bvx11fd8xi8v6" +path="res://.godot/imported/scene_camp_dummy.png-0e3dc9dfccca2e79204bfe1b987ba3ca.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_dummy.png" +dest_files=["res://.godot/imported/scene_camp_dummy.png-0e3dc9dfccca2e79204bfe1b987ba3ca.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_forge.png b/game/assets/art/scene/scene_camp_forge.png new file mode 100644 index 00000000..817fb421 Binary files /dev/null and b/game/assets/art/scene/scene_camp_forge.png differ diff --git a/game/assets/art/scene/scene_camp_forge.png.import b/game/assets/art/scene/scene_camp_forge.png.import new file mode 100644 index 00000000..f4f0661f --- /dev/null +++ b/game/assets/art/scene/scene_camp_forge.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bho1gh7ryoeno" +path="res://.godot/imported/scene_camp_forge.png-bdd0ec03bb168d33247f81916c3e43c3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_forge.png" +dest_files=["res://.godot/imported/scene_camp_forge.png-bdd0ec03bb168d33247f81916c3e43c3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_gate.png b/game/assets/art/scene/scene_camp_gate.png new file mode 100644 index 00000000..7ea25a60 Binary files /dev/null and b/game/assets/art/scene/scene_camp_gate.png differ diff --git a/game/assets/art/scene/scene_camp_gate.png.import b/game/assets/art/scene/scene_camp_gate.png.import new file mode 100644 index 00000000..ec103ad2 --- /dev/null +++ b/game/assets/art/scene/scene_camp_gate.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cjpvw3h4jb5ot" +path="res://.godot/imported/scene_camp_gate.png-26be8f559f484cb9444347d25ce1dd04.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_gate.png" +dest_files=["res://.godot/imported/scene_camp_gate.png-26be8f559f484cb9444347d25ce1dd04.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_notebook.png b/game/assets/art/scene/scene_camp_notebook.png new file mode 100644 index 00000000..34ffacf2 Binary files /dev/null and b/game/assets/art/scene/scene_camp_notebook.png differ diff --git a/game/assets/art/scene/scene_camp_notebook.png.import b/game/assets/art/scene/scene_camp_notebook.png.import new file mode 100644 index 00000000..7dfbb7ab --- /dev/null +++ b/game/assets/art/scene/scene_camp_notebook.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b13td544tmy2k" +path="res://.godot/imported/scene_camp_notebook.png-ee3dec562e61ac5cee5ec0c7acb3a357.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_notebook.png" +dest_files=["res://.godot/imported/scene_camp_notebook.png-ee3dec562e61ac5cee5ec0c7acb3a357.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_tent.png b/game/assets/art/scene/scene_camp_tent.png new file mode 100644 index 00000000..006d1f3b Binary files /dev/null and b/game/assets/art/scene/scene_camp_tent.png differ diff --git a/game/assets/art/scene/scene_camp_tent.png.import b/game/assets/art/scene/scene_camp_tent.png.import new file mode 100644 index 00000000..73a19401 --- /dev/null +++ b/game/assets/art/scene/scene_camp_tent.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8xj8p66yfvwv" +path="res://.godot/imported/scene_camp_tent.png-b89dbea860fc9c6d13bdb53af0533642.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_tent.png" +dest_files=["res://.godot/imported/scene_camp_tent.png-b89dbea860fc9c6d13bdb53af0533642.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_camp_wall.png b/game/assets/art/scene/scene_camp_wall.png new file mode 100644 index 00000000..0fff6eba Binary files /dev/null and b/game/assets/art/scene/scene_camp_wall.png differ diff --git a/game/assets/art/scene/scene_camp_wall.png.import b/game/assets/art/scene/scene_camp_wall.png.import new file mode 100644 index 00000000..8a50f5f5 --- /dev/null +++ b/game/assets/art/scene/scene_camp_wall.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b4o3do4bo7sum" +path="res://.godot/imported/scene_camp_wall.png-e8871b9de80a29c065a661f23f1a8cd4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_camp_wall.png" +dest_files=["res://.godot/imported/scene_camp_wall.png-e8871b9de80a29c065a661f23f1a8cd4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_stage_bg_deep_underdark.png b/game/assets/art/scene/scene_stage_bg_deep_underdark.png new file mode 100644 index 00000000..7ccae636 Binary files /dev/null and b/game/assets/art/scene/scene_stage_bg_deep_underdark.png differ diff --git a/game/assets/art/scene/scene_stage_bg_deep_underdark.png.import b/game/assets/art/scene/scene_stage_bg_deep_underdark.png.import new file mode 100644 index 00000000..3e238d75 --- /dev/null +++ b/game/assets/art/scene/scene_stage_bg_deep_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://80psivqtlgsp" +path="res://.godot/imported/scene_stage_bg_deep_underdark.png-40c9b4152fe37e62dc2d2b61533b0383.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_stage_bg_deep_underdark.png" +dest_files=["res://.godot/imported/scene_stage_bg_deep_underdark.png-40c9b4152fe37e62dc2d2b61533b0383.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_stage_bg_dungeon.png b/game/assets/art/scene/scene_stage_bg_dungeon.png new file mode 100644 index 00000000..44c42148 Binary files /dev/null and b/game/assets/art/scene/scene_stage_bg_dungeon.png differ diff --git a/game/assets/art/scene/scene_stage_bg_dungeon.png.import b/game/assets/art/scene/scene_stage_bg_dungeon.png.import new file mode 100644 index 00000000..9be40600 --- /dev/null +++ b/game/assets/art/scene/scene_stage_bg_dungeon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://v6708buxtf2k" +path="res://.godot/imported/scene_stage_bg_dungeon.png-5db883a26511a741d808c2594192d27a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_stage_bg_dungeon.png" +dest_files=["res://.godot/imported/scene_stage_bg_dungeon.png-5db883a26511a741d808c2594192d27a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_stage_bg_underdark.png b/game/assets/art/scene/scene_stage_bg_underdark.png new file mode 100644 index 00000000..3cad45e7 Binary files /dev/null and b/game/assets/art/scene/scene_stage_bg_underdark.png differ diff --git a/game/assets/art/scene/scene_stage_bg_underdark.png.import b/game/assets/art/scene/scene_stage_bg_underdark.png.import new file mode 100644 index 00000000..2c30034f --- /dev/null +++ b/game/assets/art/scene/scene_stage_bg_underdark.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bsjqmmc10n67o" +path="res://.godot/imported/scene_stage_bg_underdark.png-ac0b308b34e6bf255f0748a026ef267d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_stage_bg_underdark.png" +dest_files=["res://.godot/imported/scene_stage_bg_underdark.png-ac0b308b34e6bf255f0748a026ef267d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_stage_ground.png b/game/assets/art/scene/scene_stage_ground.png new file mode 100644 index 00000000..8f4803d3 Binary files /dev/null and b/game/assets/art/scene/scene_stage_ground.png differ diff --git a/game/assets/art/scene/scene_stage_ground.png.import b/game/assets/art/scene/scene_stage_ground.png.import new file mode 100644 index 00000000..2fca347b --- /dev/null +++ b/game/assets/art/scene/scene_stage_ground.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b3aaglen1alw7" +path="res://.godot/imported/scene_stage_ground.png-a11b6a0430bcb2f048b2a1eb9cdf66ea.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_stage_ground.png" +dest_files=["res://.godot/imported/scene_stage_ground.png-a11b6a0430bcb2f048b2a1eb9cdf66ea.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/scene/scene_stage_plaza.png b/game/assets/art/scene/scene_stage_plaza.png new file mode 100644 index 00000000..c2f89c2b Binary files /dev/null and b/game/assets/art/scene/scene_stage_plaza.png differ diff --git a/game/assets/art/scene/scene_stage_plaza.png.import b/game/assets/art/scene/scene_stage_plaza.png.import new file mode 100644 index 00000000..105de203 --- /dev/null +++ b/game/assets/art/scene/scene_stage_plaza.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://oyw1sn4cupc0" +path="res://.godot/imported/scene_stage_plaza.png-a3a92d12d3b121d65eed1a1d2d147f31.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/scene/scene_stage_plaza.png" +dest_files=["res://.godot/imported/scene_stage_plaza.png-a3a92d12d3b121d65eed1a1d2d147f31.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/backdrop_stone.png b/game/assets/art/ui/backdrop_stone.png new file mode 100644 index 00000000..e79fd30d Binary files /dev/null and b/game/assets/art/ui/backdrop_stone.png differ diff --git a/game/assets/art/ui/backdrop_stone.png.import b/game/assets/art/ui/backdrop_stone.png.import new file mode 100644 index 00000000..b4012907 --- /dev/null +++ b/game/assets/art/ui/backdrop_stone.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c2tfcanic03yg" +path="res://.godot/imported/backdrop_stone.png-799c672c1695c92b2aff3adcca263380.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/backdrop_stone.png" +dest_files=["res://.godot/imported/backdrop_stone.png-799c672c1695c92b2aff3adcca263380.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/border_stone.png b/game/assets/art/ui/border_stone.png new file mode 100644 index 00000000..26427291 Binary files /dev/null and b/game/assets/art/ui/border_stone.png differ diff --git a/game/assets/art/ui/border_stone.png.import b/game/assets/art/ui/border_stone.png.import new file mode 100644 index 00000000..b5a66ea3 --- /dev/null +++ b/game/assets/art/ui/border_stone.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfeovg2xhf4ox" +path="res://.godot/imported/border_stone.png-12d43548af52199d50f7855c5e304610.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/border_stone.png" +dest_files=["res://.godot/imported/border_stone.png-12d43548af52199d50f7855c5e304610.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/button_stone.png b/game/assets/art/ui/button_stone.png new file mode 100644 index 00000000..904f1497 Binary files /dev/null and b/game/assets/art/ui/button_stone.png differ diff --git a/game/assets/art/ui/button_stone.png.import b/game/assets/art/ui/button_stone.png.import new file mode 100644 index 00000000..54a4bc38 --- /dev/null +++ b/game/assets/art/ui/button_stone.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://70c70lsrbc8v" +path="res://.godot/imported/button_stone.png-5ab18541546b60db4ddb0a308313bba7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/button_stone.png" +dest_files=["res://.godot/imported/button_stone.png-5ab18541546b60db4ddb0a308313bba7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/button_stone_pressed.png b/game/assets/art/ui/button_stone_pressed.png new file mode 100644 index 00000000..161091a4 Binary files /dev/null and b/game/assets/art/ui/button_stone_pressed.png differ diff --git a/game/assets/art/ui/button_stone_pressed.png.import b/game/assets/art/ui/button_stone_pressed.png.import new file mode 100644 index 00000000..855f5b1d --- /dev/null +++ b/game/assets/art/ui/button_stone_pressed.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tprx4ie1y2nv" +path="res://.godot/imported/button_stone_pressed.png-8dc823b8e3152939770cc37218c41a97.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/button_stone_pressed.png" +dest_files=["res://.godot/imported/button_stone_pressed.png-8dc823b8e3152939770cc37218c41a97.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/checkbox_checked.png b/game/assets/art/ui/gen/checkbox_checked.png new file mode 100644 index 00000000..8bf34558 Binary files /dev/null and b/game/assets/art/ui/gen/checkbox_checked.png differ diff --git a/game/assets/art/ui/gen/checkbox_checked.png.import b/game/assets/art/ui/gen/checkbox_checked.png.import new file mode 100644 index 00000000..db693b2b --- /dev/null +++ b/game/assets/art/ui/gen/checkbox_checked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c37vc3lk8j46e" +path="res://.godot/imported/checkbox_checked.png-be331ca5bc2f4bbbb7d8d9f3f5aa38c7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/checkbox_checked.png" +dest_files=["res://.godot/imported/checkbox_checked.png-be331ca5bc2f4bbbb7d8d9f3f5aa38c7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/checkbox_checked_disabled.png b/game/assets/art/ui/gen/checkbox_checked_disabled.png new file mode 100644 index 00000000..76862b12 Binary files /dev/null and b/game/assets/art/ui/gen/checkbox_checked_disabled.png differ diff --git a/game/assets/art/ui/gen/checkbox_checked_disabled.png.import b/game/assets/art/ui/gen/checkbox_checked_disabled.png.import new file mode 100644 index 00000000..f75394a6 --- /dev/null +++ b/game/assets/art/ui/gen/checkbox_checked_disabled.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://csa8nxs5rd1ck" +path="res://.godot/imported/checkbox_checked_disabled.png-0b82c6a6e76a9ac90dc9e7aab1acb5d0.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/checkbox_checked_disabled.png" +dest_files=["res://.godot/imported/checkbox_checked_disabled.png-0b82c6a6e76a9ac90dc9e7aab1acb5d0.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/checkbox_unchecked.png b/game/assets/art/ui/gen/checkbox_unchecked.png new file mode 100644 index 00000000..080ab93c Binary files /dev/null and b/game/assets/art/ui/gen/checkbox_unchecked.png differ diff --git a/game/assets/art/ui/gen/checkbox_unchecked.png.import b/game/assets/art/ui/gen/checkbox_unchecked.png.import new file mode 100644 index 00000000..474e5cfc --- /dev/null +++ b/game/assets/art/ui/gen/checkbox_unchecked.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcxl6v0tuddrx" +path="res://.godot/imported/checkbox_unchecked.png-2d74fc2e148b67340bc15301d7ab0b20.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/checkbox_unchecked.png" +dest_files=["res://.godot/imported/checkbox_unchecked.png-2d74fc2e148b67340bc15301d7ab0b20.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/checkbox_unchecked_disabled.png b/game/assets/art/ui/gen/checkbox_unchecked_disabled.png new file mode 100644 index 00000000..93d0bc6d Binary files /dev/null and b/game/assets/art/ui/gen/checkbox_unchecked_disabled.png differ diff --git a/game/assets/art/ui/gen/checkbox_unchecked_disabled.png.import b/game/assets/art/ui/gen/checkbox_unchecked_disabled.png.import new file mode 100644 index 00000000..d59d97d7 --- /dev/null +++ b/game/assets/art/ui/gen/checkbox_unchecked_disabled.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://s77irc8u3vb5" +path="res://.godot/imported/checkbox_unchecked_disabled.png-3a548f6b94bb18167d5607a58a16bf4a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/checkbox_unchecked_disabled.png" +dest_files=["res://.godot/imported/checkbox_unchecked_disabled.png-3a548f6b94bb18167d5607a58a16bf4a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/option_arrow.png b/game/assets/art/ui/gen/option_arrow.png new file mode 100644 index 00000000..25bae828 Binary files /dev/null and b/game/assets/art/ui/gen/option_arrow.png differ diff --git a/game/assets/art/ui/gen/option_arrow.png.import b/game/assets/art/ui/gen/option_arrow.png.import new file mode 100644 index 00000000..88c9efb6 --- /dev/null +++ b/game/assets/art/ui/gen/option_arrow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bcxxt11qv3dbk" +path="res://.godot/imported/option_arrow.png-137be1c806e28e86518a9ef13f8aeee9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/option_arrow.png" +dest_files=["res://.godot/imported/option_arrow.png-137be1c806e28e86518a9ef13f8aeee9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/slider_grabber.png b/game/assets/art/ui/gen/slider_grabber.png new file mode 100644 index 00000000..f53cba64 Binary files /dev/null and b/game/assets/art/ui/gen/slider_grabber.png differ diff --git a/game/assets/art/ui/gen/slider_grabber.png.import b/game/assets/art/ui/gen/slider_grabber.png.import new file mode 100644 index 00000000..124fdd5d --- /dev/null +++ b/game/assets/art/ui/gen/slider_grabber.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bntrcomrrsrwd" +path="res://.godot/imported/slider_grabber.png-ba18ff33ac0e2d8187625d5afd88333a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/slider_grabber.png" +dest_files=["res://.godot/imported/slider_grabber.png-ba18ff33ac0e2d8187625d5afd88333a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/slider_grabber_disabled.png b/game/assets/art/ui/gen/slider_grabber_disabled.png new file mode 100644 index 00000000..8e53ce84 Binary files /dev/null and b/game/assets/art/ui/gen/slider_grabber_disabled.png differ diff --git a/game/assets/art/ui/gen/slider_grabber_disabled.png.import b/game/assets/art/ui/gen/slider_grabber_disabled.png.import new file mode 100644 index 00000000..05ed4cc5 --- /dev/null +++ b/game/assets/art/ui/gen/slider_grabber_disabled.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bld83qtfy5cx6" +path="res://.godot/imported/slider_grabber_disabled.png-9f95594f132d7e05607289d40168c3b3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/slider_grabber_disabled.png" +dest_files=["res://.godot/imported/slider_grabber_disabled.png-9f95594f132d7e05607289d40168c3b3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/slider_grabber_highlight.png b/game/assets/art/ui/gen/slider_grabber_highlight.png new file mode 100644 index 00000000..2f485579 Binary files /dev/null and b/game/assets/art/ui/gen/slider_grabber_highlight.png differ diff --git a/game/assets/art/ui/gen/slider_grabber_highlight.png.import b/game/assets/art/ui/gen/slider_grabber_highlight.png.import new file mode 100644 index 00000000..251c2072 --- /dev/null +++ b/game/assets/art/ui/gen/slider_grabber_highlight.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvwgrhmraj2pi" +path="res://.godot/imported/slider_grabber_highlight.png-3fac15354c02e1eabb51c47480b8ba38.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/slider_grabber_highlight.png" +dest_files=["res://.godot/imported/slider_grabber_highlight.png-3fac15354c02e1eabb51c47480b8ba38.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/gen/spinbox_updown.png b/game/assets/art/ui/gen/spinbox_updown.png new file mode 100644 index 00000000..0c8080ce Binary files /dev/null and b/game/assets/art/ui/gen/spinbox_updown.png differ diff --git a/game/assets/art/ui/gen/spinbox_updown.png.import b/game/assets/art/ui/gen/spinbox_updown.png.import new file mode 100644 index 00000000..42944b62 --- /dev/null +++ b/game/assets/art/ui/gen/spinbox_updown.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://brppa0ay8e8nr" +path="res://.godot/imported/spinbox_updown.png-79ed9fabd79d665c14c0e5d154733884.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/gen/spinbox_updown.png" +dest_files=["res://.godot/imported/spinbox_updown.png-79ed9fabd79d665c14c0e5d154733884.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_atlas.png b/game/assets/art/ui/icon_atlas.png new file mode 100644 index 00000000..eafff2c6 Binary files /dev/null and b/game/assets/art/ui/icon_atlas.png differ diff --git a/game/assets/art/ui/icon_atlas.png.import b/game/assets/art/ui/icon_atlas.png.import new file mode 100644 index 00000000..21f89db1 --- /dev/null +++ b/game/assets/art/ui/icon_atlas.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://doev3s0bcuq5j" +path="res://.godot/imported/icon_atlas.png-e89438fa2c7f3a01bc829f65a369f953.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_atlas.png" +dest_files=["res://.godot/imported/icon_atlas.png-e89438fa2c7f3a01bc829f65a369f953.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_blast.png b/game/assets/art/ui/icon_blast.png new file mode 100644 index 00000000..712cdd9d Binary files /dev/null and b/game/assets/art/ui/icon_blast.png differ diff --git a/game/assets/art/ui/icon_blast.png.import b/game/assets/art/ui/icon_blast.png.import new file mode 100644 index 00000000..48e5f76a --- /dev/null +++ b/game/assets/art/ui/icon_blast.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://o58nlv6hk2ys" +path="res://.godot/imported/icon_blast.png-04064899f4aae105e5ecc26444884109.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_blast.png" +dest_files=["res://.godot/imported/icon_blast.png-04064899f4aae105e5ecc26444884109.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_coinbag.png b/game/assets/art/ui/icon_coinbag.png new file mode 100644 index 00000000..336fe7d0 Binary files /dev/null and b/game/assets/art/ui/icon_coinbag.png differ diff --git a/game/assets/art/ui/icon_coinbag.png.import b/game/assets/art/ui/icon_coinbag.png.import new file mode 100644 index 00000000..a239604c --- /dev/null +++ b/game/assets/art/ui/icon_coinbag.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dullklfv7xlte" +path="res://.godot/imported/icon_coinbag.png-7ad26f492e92674b894095789857da38.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_coinbag.png" +dest_files=["res://.godot/imported/icon_coinbag.png-7ad26f492e92674b894095789857da38.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_forge.png b/game/assets/art/ui/icon_forge.png new file mode 100644 index 00000000..a3877919 Binary files /dev/null and b/game/assets/art/ui/icon_forge.png differ diff --git a/game/assets/art/ui/icon_forge.png.import b/game/assets/art/ui/icon_forge.png.import new file mode 100644 index 00000000..af40eaf3 --- /dev/null +++ b/game/assets/art/ui/icon_forge.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cq67vqun43yjs" +path="res://.godot/imported/icon_forge.png-9a2687f121c998d16d7dec611e0254a9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_forge.png" +dest_files=["res://.godot/imported/icon_forge.png-9a2687f121c998d16d7dec611e0254a9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_heart.png b/game/assets/art/ui/icon_heart.png new file mode 100644 index 00000000..e1316523 Binary files /dev/null and b/game/assets/art/ui/icon_heart.png differ diff --git a/game/assets/art/ui/icon_heart.png.import b/game/assets/art/ui/icon_heart.png.import new file mode 100644 index 00000000..50c6a7a5 --- /dev/null +++ b/game/assets/art/ui/icon_heart.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://tnxge3jvk55l" +path="res://.godot/imported/icon_heart.png-3e14321ca98d58e5ee89247177dc2a6c.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_heart.png" +dest_files=["res://.godot/imported/icon_heart.png-3e14321ca98d58e5ee89247177dc2a6c.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_keyshard.png b/game/assets/art/ui/icon_keyshard.png new file mode 100644 index 00000000..7654bd4d Binary files /dev/null and b/game/assets/art/ui/icon_keyshard.png differ diff --git a/game/assets/art/ui/icon_keyshard.png.import b/game/assets/art/ui/icon_keyshard.png.import new file mode 100644 index 00000000..778da476 --- /dev/null +++ b/game/assets/art/ui/icon_keyshard.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b5e36vq0sakx3" +path="res://.godot/imported/icon_keyshard.png-cff99cec261e28b5051430eb78979325.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_keyshard.png" +dest_files=["res://.godot/imported/icon_keyshard.png-cff99cec261e28b5051430eb78979325.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_keystone.png b/game/assets/art/ui/icon_keystone.png new file mode 100644 index 00000000..97b85243 Binary files /dev/null and b/game/assets/art/ui/icon_keystone.png differ diff --git a/game/assets/art/ui/icon_keystone.png.import b/game/assets/art/ui/icon_keystone.png.import new file mode 100644 index 00000000..51cca41c --- /dev/null +++ b/game/assets/art/ui/icon_keystone.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnd8yq1gwlsn" +path="res://.godot/imported/icon_keystone.png-3763a32207cfae2f45fe72d471b0bb5f.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_keystone.png" +dest_files=["res://.godot/imported/icon_keystone.png-3763a32207cfae2f45fe72d471b0bb5f.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_medkit.png b/game/assets/art/ui/icon_medkit.png new file mode 100644 index 00000000..424985e0 Binary files /dev/null and b/game/assets/art/ui/icon_medkit.png differ diff --git a/game/assets/art/ui/icon_medkit.png.import b/game/assets/art/ui/icon_medkit.png.import new file mode 100644 index 00000000..c43fe502 --- /dev/null +++ b/game/assets/art/ui/icon_medkit.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://jefpnnfyt2f" +path="res://.godot/imported/icon_medkit.png-08fc2ed48cfc456328d0642154fa4558.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_medkit.png" +dest_files=["res://.godot/imported/icon_medkit.png-08fc2ed48cfc456328d0642154fa4558.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_pouch.png b/game/assets/art/ui/icon_pouch.png new file mode 100644 index 00000000..38d8a71a Binary files /dev/null and b/game/assets/art/ui/icon_pouch.png differ diff --git a/game/assets/art/ui/icon_pouch.png.import b/game/assets/art/ui/icon_pouch.png.import new file mode 100644 index 00000000..1f0bb711 --- /dev/null +++ b/game/assets/art/ui/icon_pouch.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dvrxoke5t81od" +path="res://.godot/imported/icon_pouch.png-7af57e57864a8872b241790365ccc158.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_pouch.png" +dest_files=["res://.godot/imported/icon_pouch.png-7af57e57864a8872b241790365ccc158.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_refresh.png b/game/assets/art/ui/icon_refresh.png new file mode 100644 index 00000000..bc4ffc0f Binary files /dev/null and b/game/assets/art/ui/icon_refresh.png differ diff --git a/game/assets/art/ui/icon_refresh.png.import b/game/assets/art/ui/icon_refresh.png.import new file mode 100644 index 00000000..2beda547 --- /dev/null +++ b/game/assets/art/ui/icon_refresh.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c5fpw50gjad0u" +path="res://.godot/imported/icon_refresh.png-84a3ff5804304796b72f7b94b917da31.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_refresh.png" +dest_files=["res://.godot/imported/icon_refresh.png-84a3ff5804304796b72f7b94b917da31.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_shield.png b/game/assets/art/ui/icon_shield.png new file mode 100644 index 00000000..c016682f Binary files /dev/null and b/game/assets/art/ui/icon_shield.png differ diff --git a/game/assets/art/ui/icon_shield.png.import b/game/assets/art/ui/icon_shield.png.import new file mode 100644 index 00000000..8d3a42f8 --- /dev/null +++ b/game/assets/art/ui/icon_shield.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://cqjotgk88l820" +path="res://.godot/imported/icon_shield.png-5447c9828a7dde676476f6e9d9c0b8af.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_shield.png" +dest_files=["res://.godot/imported/icon_shield.png-5447c9828a7dde676476f6e9d9c0b8af.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_shovel.png b/game/assets/art/ui/icon_shovel.png new file mode 100644 index 00000000..e40b213e Binary files /dev/null and b/game/assets/art/ui/icon_shovel.png differ diff --git a/game/assets/art/ui/icon_shovel.png.import b/game/assets/art/ui/icon_shovel.png.import new file mode 100644 index 00000000..340e40d2 --- /dev/null +++ b/game/assets/art/ui/icon_shovel.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://clajmy4i3l0pf" +path="res://.godot/imported/icon_shovel.png-64bba6b461a2f5988db8da6e65838ed7.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_shovel.png" +dest_files=["res://.godot/imported/icon_shovel.png-64bba6b461a2f5988db8da6e65838ed7.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_sword.png b/game/assets/art/ui/icon_sword.png new file mode 100644 index 00000000..35143e66 Binary files /dev/null and b/game/assets/art/ui/icon_sword.png differ diff --git a/game/assets/art/ui/icon_sword.png.import b/game/assets/art/ui/icon_sword.png.import new file mode 100644 index 00000000..3d9d12ad --- /dev/null +++ b/game/assets/art/ui/icon_sword.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dd8y2oo0wv0j6" +path="res://.godot/imported/icon_sword.png-9cb55873ed9a5812cea24af598654bae.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_sword.png" +dest_files=["res://.godot/imported/icon_sword.png-9cb55873ed9a5812cea24af598654bae.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/icon_workshop.png b/game/assets/art/ui/icon_workshop.png new file mode 100644 index 00000000..c5ba3fd2 Binary files /dev/null and b/game/assets/art/ui/icon_workshop.png differ diff --git a/game/assets/art/ui/icon_workshop.png.import b/game/assets/art/ui/icon_workshop.png.import new file mode 100644 index 00000000..64406186 --- /dev/null +++ b/game/assets/art/ui/icon_workshop.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bfpqjx3j80p52" +path="res://.godot/imported/icon_workshop.png-c0b603c8dccfe9069e64d051eabd765e.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/icon_workshop.png" +dest_files=["res://.godot/imported/icon_workshop.png-c0b603c8dccfe9069e64d051eabd765e.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/node_battle.png b/game/assets/art/ui/node_battle.png new file mode 100644 index 00000000..18884e94 Binary files /dev/null and b/game/assets/art/ui/node_battle.png differ diff --git a/game/assets/art/ui/node_battle.png.import b/game/assets/art/ui/node_battle.png.import new file mode 100644 index 00000000..11dea5c4 --- /dev/null +++ b/game/assets/art/ui/node_battle.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c4tyr775ctd3r" +path="res://.godot/imported/node_battle.png-d897da35cd6e09c738bf96a9e2ebf5c8.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/node_battle.png" +dest_files=["res://.godot/imported/node_battle.png-d897da35cd6e09c738bf96a9e2ebf5c8.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/node_boss.png b/game/assets/art/ui/node_boss.png new file mode 100644 index 00000000..170482cd Binary files /dev/null and b/game/assets/art/ui/node_boss.png differ diff --git a/game/assets/art/ui/node_boss.png.import b/game/assets/art/ui/node_boss.png.import new file mode 100644 index 00000000..8e9e3246 --- /dev/null +++ b/game/assets/art/ui/node_boss.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ckhdl5ow2ilwy" +path="res://.godot/imported/node_boss.png-880d6b2c5f9777b66597a0689ca9db6a.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/node_boss.png" +dest_files=["res://.godot/imported/node_boss.png-880d6b2c5f9777b66597a0689ca9db6a.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/node_rest.png b/game/assets/art/ui/node_rest.png new file mode 100644 index 00000000..972d5c89 Binary files /dev/null and b/game/assets/art/ui/node_rest.png differ diff --git a/game/assets/art/ui/node_rest.png.import b/game/assets/art/ui/node_rest.png.import new file mode 100644 index 00000000..6159e0b7 --- /dev/null +++ b/game/assets/art/ui/node_rest.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://8mab8uhryjga" +path="res://.godot/imported/node_rest.png-87d435876759d192ffecf3a9464f3fba.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/node_rest.png" +dest_files=["res://.godot/imported/node_rest.png-87d435876759d192ffecf3a9464f3fba.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/node_reward.png b/game/assets/art/ui/node_reward.png new file mode 100644 index 00000000..4a26c963 Binary files /dev/null and b/game/assets/art/ui/node_reward.png differ diff --git a/game/assets/art/ui/node_reward.png.import b/game/assets/art/ui/node_reward.png.import new file mode 100644 index 00000000..2418b791 --- /dev/null +++ b/game/assets/art/ui/node_reward.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://0nr8gywio7vn" +path="res://.godot/imported/node_reward.png-5668979de50aef5ad53942d485aafdff.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/node_reward.png" +dest_files=["res://.godot/imported/node_reward.png-5668979de50aef5ad53942d485aafdff.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/node_shop.png b/game/assets/art/ui/node_shop.png new file mode 100644 index 00000000..c6bbe080 Binary files /dev/null and b/game/assets/art/ui/node_shop.png differ diff --git a/game/assets/art/ui/node_shop.png.import b/game/assets/art/ui/node_shop.png.import new file mode 100644 index 00000000..a1756609 --- /dev/null +++ b/game/assets/art/ui/node_shop.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://dakv3mhvwv1h2" +path="res://.godot/imported/node_shop.png-5ab84123a44a3396f6201cd801023df4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/node_shop.png" +dest_files=["res://.godot/imported/node_shop.png-5ab84123a44a3396f6201cd801023df4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/assets/art/ui/panel_stone.png b/game/assets/art/ui/panel_stone.png new file mode 100644 index 00000000..14417b61 Binary files /dev/null and b/game/assets/art/ui/panel_stone.png differ diff --git a/game/assets/art/ui/panel_stone.png.import b/game/assets/art/ui/panel_stone.png.import new file mode 100644 index 00000000..5adbff2d --- /dev/null +++ b/game/assets/art/ui/panel_stone.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://ffg6ebgqxxdy" +path="res://.godot/imported/panel_stone.png-c2b330d1b6552bd7ff83ec9fcce267d9.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://assets/art/ui/panel_stone.png" +dest_files=["res://.godot/imported/panel_stone.png-c2b330d1b6552bd7ff83ec9fcce267d9.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/game/core/adventure_runner.gd b/game/core/adventure_runner.gd index b7cc5b57..2ee4af3d 100644 --- a/game/core/adventure_runner.gd +++ b/game/core/adventure_runner.gd @@ -36,7 +36,7 @@ var _refresh_bank: RefreshBank = null # 刷新账件(_init 建——三源 var _loadout: AttuneLoadout = null # 同调配装件(_init 建;充能单源化——物化行唯一事实源) var _bp_store: BreakpointStore # 断点组装与回放件(_init 建;六成员+27 键登记单源+读写侧机组随件——宿主读写经件) var adventure_inventory: TreasureItems # 冒险级秘宝库存(单源化——同调未用行跨关存活的承载面;关卡域语义归 level 使用面) -var _tree_rules: Array[Variant] = [] # 树 rule 键集(Fork 激活侧 effects 透传;下传 enc.tree_rules 消费面) +var _tree_rules: Dictionary[Variant, Variant] = {} # 树规则参数字典(Fork 激活侧 effects_* 透传;下传 enc.tree_rules 消费面) var _shop_mode := "legacy" # 商店模式("legacy"=固定三件加定价〔off 对照承载,缺省零扰动〕/"pool"=共享池抽 3 买 1 重抽;真实入口 main.gd 显式投 pool) @@ -123,7 +123,7 @@ func start_adventure(seed_val: int, params: Dictionary = {}) -> bool: # 特性开局注入(MC 成型画像载体:params.relics_seed 非空=开局持有集〔七流派 # 各 2 件档一成型——测量面专用;真实局规则改写型特性只走局内随机渠道,红线②层级纪律不破〕) var feat_seed: Array[Variant] = params.get("relics_seed", []) if params.get("relics_seed", []) is Array else [] - _tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集 + _tree_rules = params.get("tree_rules", {}) if params.get("tree_rules", {}) is Dictionary else {} # A/B 面规则键参数字典 if not feat_seed.is_empty(): for r in feat_seed: _stats.feats.append(String(r)) @@ -163,7 +163,7 @@ func resume_adventure(bp: Dictionary, params: Dictionary = {}) -> bool: _attunement = params.get("attunement", []) if params.get("attunement", []) is Array else [] # additive _refresh_bank.set_refresh_x_cap(_attune_refresh_x_total()) # 刷新账 X 基数预算注入(续玩参数面同 start) _bp_store.restore_stop_bank_fragments(bp, params) # 停点件/刷新账片段回填(件侧——凭证计数;刷新存量/Y 池缺键回退参数面) - _tree_rules = params.get("tree_rules", []) if params.get("tree_rules", []) is Array else [] # A/B 面 rule 键集(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传) + _tree_rules = params.get("tree_rules", {}) if params.get("tree_rules", {}) is Dictionary else {} # A/B 面规则键参数字典(续玩参数面同 start_adventure 同式,消费面 set_tree_rules 下传) # 续玩计数器回填(adventure 域计数——缺键 = 旧版断点防御空集;_reset_curio_state 已清零在先——桶归宿主字典) var bp_counters = _bp_store._bp_read("adventure_counters", bp) if bp_counters is Dictionary: diff --git a/game/core/battle_fx_host.gd b/game/core/battle_fx_host.gd index 22eaa6b9..67478a1d 100644 --- a/game/core/battle_fx_host.gd +++ b/game/core/battle_fx_host.gd @@ -179,7 +179,7 @@ func flush_pending_reveals(ri: int) -> void: # 效果引擎订阅重建(每战从持有集/装备集重建,中途获得下一战生效;树规则合成订阅同面装载。 # 装载序=feats 表序→treasure 表序→树规则,写死保种子复放。计数器四桶经 counters_ctx 引用宿主容器,无回灌时点) -func rebuild_subscriptions(feats: Array, attunement: Array, tree_rules: Array) -> void: +func rebuild_subscriptions(feats: Array, attunement: Array, tree_rules: Dictionary[Variant, Variant]) -> void: engine.rebuild(feats, attunement, tree_rules) diff --git a/game/core/battle_orchestrator.gd b/game/core/battle_orchestrator.gd index 19b2ed3f..56e121e0 100644 --- a/game/core/battle_orchestrator.gd +++ b/game/core/battle_orchestrator.gd @@ -348,7 +348,7 @@ func _begin_battle_fx_state(enc: Dictionary) -> void: # 树 rule 键消费(Fork 激活侧效果=效果引擎合成 # 订阅(rebuild 注入——round_start 首轮 AP+1 / damage_taken 减伤+1)。其余 rule 键 # =stat 面投影不入战斗(T8 表内容 provisional 口径披露)) - var tree_rules: Array[Variant] = enc.get("tree_rules", []) as Array + var tree_rules: Dictionary[Variant, Variant] = enc.get("tree_rules", {}) as Dictionary # 效果引擎订阅重建(装载序=feats 表序→treasure 表序→树规则,写死保种子复放——细则见 battle_fx_host.rebuild_subscriptions) _fx_host.rebuild_subscriptions(feat_ids, _facts.attunement, tree_rules) @@ -647,8 +647,9 @@ func _roll_gp_drop() -> int: # 财富之道·聚(战斗掉落 GP 乘算·入账前:只乘战斗掉落两成分〔掷定+关卡加成〕, # 店内 escrow 不乘〔范围=仅战斗掉落渠道〕;本函数返回值=结算框「GP 增量」行 # 显示源,落在入账前=显示即实得;乘数 provisional 150%) - if (_facts.encounter.get("tree_rules", []) as Array).has("economy_hoard"): - drop = int(round(float(drop) * 1.5)) + var hoard: Dictionary[Variant, Variant] = (_facts.encounter.get("tree_rules", {}) as Dictionary).get("economy_hoard", {}) + if not hoard.is_empty(): + drop = int(round(float(drop) * float(hoard["mult_pct"]) / 100.0)) # 乘数读规则键参数 mult_pct return drop + _reward_grant.escrow @@ -667,14 +668,6 @@ func presentation_snapshot() -> Dictionary: var alive: bool = bool(u.get("alive", false)) var pending: String = String(u.get("pending_skill", "")) if alive else "" var pending_row: Dictionary[Variant, Variant] = db.combat.skill.row_by_id(pending) - # 敌方增益逐卡投影(呈现各读各的——增益状态 v1.5):每卡只读它自己名下实例 - # (蓄力/硬化/削弱/暴走 percent 行 + 警觉 ac 行;死亡个体桶已清,投影恒空) - var own_effects: Array[Dictionary] = [] - if alive: - for eff in _status_effects.query_active(StatusContainer.owner_key_for(u)): - var erow: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(eff.effect_id) - if not erow.is_empty() and String(erow.unit) in ["percent", "ac"]: - own_effects.append({"effect_id": eff.effect_id, "display_name": String(eff.display_name), "layers": int(eff.layers)}) enemies.append({ "order": i + 1, "display_name": String(u.display_name), @@ -687,7 +680,7 @@ func presentation_snapshot() -> Dictionary: "pending_skill": pending, "pending_type": String(pending_row.get("type", "")) if not pending_row.is_empty() else "", "pending_name": String(pending_row.get("display_name", "")) if not pending_row.is_empty() else "", - "effects": own_effects, + "effects": _enemy_own_effects(u), "alive": alive, }) var hero := {} @@ -711,6 +704,18 @@ func presentation_snapshot() -> Dictionary: } +# 敌方增益逐卡投影(呈现各读各的——增益状态 v1.5):每卡只读它自己名下实例 +# (蓄力/硬化/削弱/暴走 percent 行 + 警觉 ac 行;死亡个体桶已清,投影恒空) +func _enemy_own_effects(u: Dictionary) -> Array[Dictionary]: + var own: Array[Dictionary] = [] + if bool(u.get("alive", false)): + for eff in _status_effects.query_active(StatusContainer.owner_key_for(u)): + var erow: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(eff.effect_id) + if not erow.is_empty() and String(erow.unit) in ["percent", "ac"]: + own.append({"effect_id": eff.effect_id, "display_name": String(eff.display_name), "layers": int(eff.layers)}) + return own + + # 战斗进行中谓词(hero 在场且存活)——组合根高频相位判断的低成本面 func is_battle_live() -> bool: return battle_result == "" and not _enemy_system.hero_unit().is_empty() diff --git a/game/core/board_data.gd b/game/core/board_data.gd index c1539e62..fa7651b9 100644 --- a/game/core/board_data.gd +++ b/game/core/board_data.gd @@ -4,9 +4,7 @@ class_name BoardData extends RefCounted -# 出生 3×3 区格数(W×H−9 布雷域基的双落点收口单点: -# 棋盘生成 _attempt/_finalize 与 BoardGenTable 布雷数下限校验共用;与 spawn_cells() 的 3×3 几何同源) -const SPAWN_AREA_CELLS := 9 +var spawn_edge: int = 0 # 出生区边长(奇数——表头 board_gen.spawn_edge 经生成器注入;spawn_cells() 几何与 W×H−edge² 布雷域基同源导出) var width: int = 0 var height: int = 0 @@ -69,9 +67,10 @@ func neighbors8(c: Vector2i) -> Array: func spawn_cells() -> Array: + var half := spawn_edge / 2 var out: Array[Vector2i] = [] - for dy in range(-1, 2): - for dx in range(-1, 2): + for dy in range(-half, half + 1): + for dx in range(-half, half + 1): var c := Vector2i(spawn_center.x + dx, spawn_center.y + dy) if in_bounds(c): out.append(c) diff --git a/game/core/board_generator.gd b/game/core/board_generator.gd index accfd631..e6d17342 100644 --- a/game/core/board_generator.gd +++ b/game/core/board_generator.gd @@ -14,26 +14,26 @@ var fatal_handler: Callable = Callable() # ==================== 生成入口 ==================== -# 目标雷数 N = round(d×(W×H−9))(params 快照键:density/w/h)——布雷定数与产物断言两处的单点公式 +# 目标雷数 N = round(d×(W×H−edge²))(params 快照键:density/w/h/spawn_edge)——布雷定数与产物断言两处的单点公式 static func target_mine_count(params: Dictionary) -> int: - return int(round(float(params.density) * float(int(params.w) * int(params.h) - BoardData.SPAWN_AREA_CELLS))) + return int(round(float(params.density) * float(int(params.w) * int(params.h) - int(params.spawn_edge) * int(params.spawn_edge)))) # rng_stream = 一次 open_stream("board_gen") 实例(一张图的全部取数;重试共用——attempt 边界非 scope 事件)。 -# db 参数不标具体类型原因:冒烟工具注入 FakeDB 桩(experiment_density.gd 等)——typed 形参会拒收桩表 +# db 参数不标具体类型原因:冒烟工具注入 FakeDB 桩(calibrate_boards.gd 等)——typed 形参会拒收桩表 func generate(L: int, D: int, rng_stream: RandomNumberGenerator, db) -> BoardData: # type-hint: ignore(测量面 _BoardRowStub 冒烟夹具鸭子型注入——typed 参拒 stub) var row: Dictionary[Variant, Variant] = db.combat.board_gen.resolve_row(L, D) if row.is_empty(): _fatal("BoardGen 行缺失 (%d,%d)" % [L, D]) return null - var params := _board_params(row, L, D) + var params := _board_params(row, L, D, int(db.combat.board_gen.spawn_edge)) # 表头直索引——缺成员即崩=fail-fast var outcome := _attempt_loop(params, rng_stream, db) _finalize(outcome["board"], params, outcome["attempts"], outcome["mode"], outcome["r0_rej"]) return outcome["board"] ## 生成期表值单次读取快照(防热重载撕裂 attempt 语义;零 RNG) -func _board_params(row: Dictionary, L: int, D: int) -> Dictionary: +func _board_params(row: Dictionary, L: int, D: int, spawn_edge: int) -> Dictionary: return { "L": L, "D": D, "w": int(row.board_width), "h": int(row.board_height), @@ -47,6 +47,7 @@ func _board_params(row: Dictionary, L: int, D: int) -> Dictionary: # r0_cap=0 闸关。正式表行全行投值,加载期校验在 BoardGenTable.validate) "shave_r": int(row.get("shave_ring_radius", 1)), "r0_cap": int(row.get("r0_max_cap", 0)), + "spawn_edge": spawn_edge, # 出生区边长(表头单点——经 generate 直索引注入) } @@ -100,11 +101,12 @@ func _attempt(params: Dictionary, rng_stream: RandomNumberGenerator, db) -> Boar var b := BoardData.new() b.width = params.w b.height = params.h + b.spawn_edge = int(params.spawn_edge) b.spawn_center = Vector2i(int((params.w - 1) / 2.0), int((params.h - 1) / 2.0)) b.board_type = str(params.board_type) b.level_index = params.L b.node_depth = params.D - # ② 布雷:N = round(d×(W×H−9)) 先定数;域 = 全格 − 出生 3×3;均匀布雷后"先布后删"(_populate_mines 承载——Fisher-Yates 取号段整体随迁) + # ② 布雷:N = round(d×(W×H−edge²)) 先定数;域 = 全格 − 出生区(edge²);均匀布雷后"先布后删"(_populate_mines 承载——Fisher-Yates 取号段整体随迁) var removed := _populate_mines(b, params, rng_stream) # ③ 变异布置(地形变异 框架面):布置约束与算法随首批变异类型定稿(生成期布置接口结构位), # 定稿前零布置——清单为空是合法态(P0),静默产出无变异图。 @@ -148,7 +150,7 @@ func _populate_mines(b: BoardData, params: Dictionary, rng_stream: RandomNumberG func _shave_ring(b: BoardData, shave_r: int) -> int: var ring := {} for c in b.all_cells(): - if maxi(absi(c.x - b.spawn_center.x), absi(c.y - b.spawn_center.y)) <= 1 + shave_r: + if maxi(absi(c.x - b.spawn_center.x), absi(c.y - b.spawn_center.y)) <= (int(b.spawn_edge) - 1) / 2 + shave_r: ring[c] = true var removed := 0 for c in ring.keys(): diff --git a/game/core/board_installer.gd b/game/core/board_installer.gd index f8b7c123..88c300d8 100644 --- a/game/core/board_installer.gd +++ b/game/core/board_installer.gd @@ -122,7 +122,7 @@ func _place_variants(board: BoardData) -> void: func _place_variants_apply(board: BoardData, candidates: Array[Vector2i], stream: RandomNumberGenerator, vrow: Dictionary, total: int) -> void: var placed := {} while placed.size() < total: - var pack_size: int = mini(total - placed.size(), stream.randi_range(2, int(db.combat.variation.pack_max))) + var pack_size: int = mini(total - placed.size(), stream.randi_range(int(db.combat.variation.pack_min), int(db.combat.variation.pack_max))) var free: Array[Vector2i] = [] for c in candidates: # 新片种子/生长格禁与其他已布片 8 邻贴接——否则两片粘连成一个超 pack_max 的连通分量 diff --git a/game/core/config_db_resolver.gd b/game/core/config_db_resolver.gd index 37668e44..f8fb164b 100644 --- a/game/core/config_db_resolver.gd +++ b/game/core/config_db_resolver.gd @@ -107,7 +107,8 @@ func _warn_resolve(table_id: String, category: String, message: String, l_key: i _diag.call("warning", category, message) -# 难度曲线与Boss DifficultyTable 消费面(敌人系统.spawn 调用):(L,D) 精确匹配修饰乘数,越域值 clamp(GDD Edge Case——组合事故非配置事故) +# 难度曲线与Boss DifficultyTable 消费面(敌人系统.spawn 调用):(L,D) 精确匹配修饰乘数, +# 越域值 clamp(域常量读 DifficultyTable.MULT_MIN/MULT_MAX 单点——使用时 clamp 口径,warning 预登记同族) func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary: var dt = _difficulty_get.call() if dt == null: @@ -115,8 +116,8 @@ func resolve_difficulty_modifiers(L: int, D: int) -> Dictionary: for row in dt.rows: if int(row.get("level_index", 0)) == L and int(row.get("node_depth", -1)) == D: return { - "hp_mult": clampf(float(row.get("hp_mult", 1.0)), 0.1, 10.0), - "atk_mult": clampf(float(row.get("atk_mult", 1.0)), 0.1, 10.0), + "hp_mult": clampf(float(row.get("hp_mult", 1.0)), DifficultyTable.MULT_MIN, DifficultyTable.MULT_MAX), + "atk_mult": clampf(float(row.get("atk_mult", 1.0)), DifficultyTable.MULT_MIN, DifficultyTable.MULT_MAX), } return {} diff --git a/game/core/curio_deck.gd b/game/core/curio_deck.gd index 4df30645..d7cb9623 100644 --- a/game/core/curio_deck.gd +++ b/game/core/curio_deck.gd @@ -95,14 +95,15 @@ func consume_deck_token(kind: String, tier: String, feat_id: String = "") -> boo # 已拿上限判定(身份维度:kind 走 curio_cap、relic 走 feats 行 cap 字段; # taken 只在兑付时 +1〔拿了才计〕、刷出不计)。缺省放行=白名单外 kind 兜底。 func cap_ok(kind: String, feat_id: String = "") -> bool: - var cap := 99 if kind == "relic": var row: Dictionary[Variant, Variant] = db.crossrun.feats.row_by_id(feat_id) - cap = int(row.get("cap", 1)) if not row.is_empty() else 1 - return taken.get("relic:" + feat_id, 0) < cap + if row.is_empty(): + push_error("[奇物牌库] relic 令牌引用悬空 feat_id=%s(feats 表无此行——配置违约,禁兜底)" % feat_id) + return false + return taken.get("relic:" + feat_id, 0) < int(row["cap"]) # 直索引——schema 必填保证在键 if not db.run.adventure.CURIO_KINDS.has(kind): return true - cap = int(db.run.adventure.curio_cap(kind)) + var cap: int = int(db.run.adventure.curio_cap(kind)) return int(taken.get(kind, 0)) < cap diff --git a/game/core/damage_controller.gd b/game/core/damage_controller.gd index 2f8ab4eb..09f9344c 100644 --- a/game/core/damage_controller.gd +++ b/game/core/damage_controller.gd @@ -117,8 +117,8 @@ func submit(t: Dictionary) -> int: # board_flat_hero 的键并入行载荷,board_blast/board_typed 带 batch_key 供未生效行归因)。 # attack_commit 发射点(我方伤害单据开立入口; # 命中挂单据 context 随行——施加面在 _resolve_hero_attack 管线算术前逐族施加,开单时点=主角攻击 -# 单恒为段内首单(结账现场与开单现场一致)。载荷 atk/target/matchup + 挖掘 streak 事实〔连珠矢/ -# 执拗订阅面〕+巢穴控制数〔猎巢人〕) +# 单恒为段内首单(结账现场与开单现场一致)。载荷 atk/target/matchup + 挖掘 streak 事实 +# (执拗订阅面)+巢穴控制数〔猎巢人〕) # spec 束必载键(缺载 assert——fail-fast):channel/base_value/target_rule/ri; # 可选键缺省=原形缺省:origin_cell=(-1,-1)/attack_type=""/rider=0/highlight=""/context={}。 # 返回单据字典九栏目+requires/source_unit 逐字不变(契约 8)。 @@ -358,7 +358,7 @@ func _hero_attack_base_value(t: Dictionary, ctx: Dictionary) -> float: # 掷点兑现(rolled 分量/payload 归因/池乘区/下限/英雄侧 rider 族/落地扣血/击杀标记; -# 击杀发射面拆 _kill_overflow)。返回 {landed, payload, hero_rider}(交付步消费)。 +# 击杀发射面拆 _kill_effects)。返回 {landed, payload, hero_rider}(交付步消费)。 func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> Dictionary: var target = ctx["target"] var ac_hits: Array[Variant] = ctx["ac_hits"] @@ -393,31 +393,16 @@ func _hero_attack_redeem(t: Dictionary, ctx: Dictionary, base_value: float) -> D units.apply_damage(target, landed) payload["killed"] = not target.alive # 击杀标记在扣血后取 if bool(payload.get("killed", false)): - _kill_overflow(t, target, landed) + _kill_effects(t, target) return {"landed": landed, "payload": payload, "hero_rider": hero_rider} -# kill 发射+施加(敌人死亡确认;贯穿射击溢出弹射订阅面:对下一存活敌 -# 以穿刺类型弹射本体 value%〔无次敌=弹射落空〕。B 模式入队段尾 flush 兑现;A 模式递归 resolve) -func _kill_overflow(t: Dictionary, target: Dictionary, landed: int) -> void: - for h in _fx_hits("kill", {"target": String(target.display_name), "target_id": String(target.get("enemy_id", "")), "overflow": landed}): +# kill 发射+施加(敌人死亡确认;订阅面=reveal_tiles 击杀揭示——斩首学者通道。 +# B 模式入队段尾 flush 兑现;A 模式端口路由宿主入队) +func _kill_effects(t: Dictionary, target: Dictionary) -> void: + for h in _fx_hits("kill", {"target": String(target.display_name), "target_id": String(target.get("enemy_id", ""))}): if String(h.get("kind", "")) == "reveal_tiles": _port.reveal_effect() # 斩首学者——击杀揭示 1 格(端口路由宿主入队,授予段末兑付) - continue - if String(h.get("kind", "")) != "overflow": - continue - var next_target = units.default_target() - if next_target != null: - # source_class 用中性名(fx_overflow 泛用通道,不挂件名) - var of_t := open_ticket(DamageController.KIND_BOARD_TYPED, "fx_overflow", "贯穿弹射", - {"channel": "board", - "base_value": maxi(int(db.combat.settlement.damage_roll.damage_floor), int(landed * int(h.get("value", 50)) / 100)), - "target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": int(t.round_index), - "origin_cell": t.origin_cell, "attack_type": "pierce"}) - if policy == POLICY_SEGMENT: - _queue.append(of_t) - else: - _resolve(of_t) # 交付:即时执行族(碎甲震击打重甲附加「护甲裂解」——复用 off_guard,零新状态行; diff --git a/game/core/effect_engine.gd b/game/core/effect_engine.gd index c2d3a331..5b66a023 100644 --- a/game/core/effect_engine.gd +++ b/game/core/effect_engine.gd @@ -26,7 +26,7 @@ const MINE_EVENTS := ["dig_reveal", "dig_paid", "seal_hit", "mine_hit", "seal_se ## grant_gp/bias_wallet、counter_add/counter_spend、reveal_tiles/remove_mines/refresh_pick、 ## bias_rewards/preview。需求卡片标题计 23 系笔误,落码按行内容全收——commit 留痕) const EFFECT_KINDS := ["add_ticket_rider", "modify_ticket", "damage_mult", "open_ticket", - "attack_bonus", "attack_segments", "yield_mult", "spawn_spell", "overflow", "grant_material", + "attack_bonus", "yield_mult", "spawn_spell", "grant_material", "grant_ap", "grant_gp", "bias_wallet", "shield", "spike", "apply_status", "counter_add", "counter_spend", "reveal_tiles", "remove_mines", "refresh_pick", "bias_rewards", "preview", @@ -100,7 +100,7 @@ func _init(db_ref: ConfigDb = null) -> void: ## 集过滤)→树规则合成行。同一数据行的多个 trigger 按行内数组序。计数器四桶由宿主持有、随宿主生灭, ## 本方法只重建订阅集(运行时可变状态=零;battle/level 桶的清零由宿主 new 天然承载,round 桶清零 ## 在 dispatch 的 round_start 特判内整桶 clear,adventure 桶随宿主字典引用跨战持久——无副本搬运)。 -func rebuild(feat_ids: Array, item_ids: Array, tree_rules: Array = []) -> void: +func rebuild(feat_ids: Array, item_ids: Array, tree_rules: Dictionary[Variant, Variant] = {}) -> void: _triggers.clear() _load_feat_triggers(feat_ids) _load_attune_triggers(item_ids) @@ -134,42 +134,42 @@ func _load_attune_triggers(item_ids: Array) -> void: ## sense 两组=每关限次〔level 桶计数器 lt 上限闸 + 命中即 counter_add 记账双行——同事件双行同 ## 预评估态见闸同记,序=装载序确定;寿命随 LevelRunner.level_counters 每关 new 天然按关清零〕; ## economy 两组零订阅行——消费点=GP 掉落乘算与商品计价折价的 tree_rules 成员判定。数值全 provisional 归 M4。 -func _push_tree_rules(tree_rules: Array) -> void: - for key_v in tree_rules: - _push_tree_rule(String(key_v)) +func _push_tree_rules(tree_rules: Dictionary[Variant, Variant]) -> void: + for rk in tree_rules: + _push_tree_rule(String(rk), tree_rules[rk] as Dictionary) -## 单树规则合成行 push(六键 match 分派——词表封闭,未知键零订阅零扰动) -func _push_tree_rule(rk: String) -> void: +## 单树规则合成行 push(六键 match 分派——词表封闭,未知键零订阅零扰动;参数全读规则键参数字典) +func _push_tree_rule(rk: String, params: Dictionary[Variant, Variant]) -> void: match rk: "posture_first_strike": _push_trigger("tree", rk, {"on": "round_start", "when": [{"fact": "battle_index", "op": "eq", "value": 1}, {"fact": "round_index", "op": "eq", "value": 1}], - "do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {}) + "do": {"kind": "grant_ap", "mode": "queue", "value": int(params["ap_bonus"])}}, {}) "posture_bulwark": _push_trigger("tree", rk, {"on": "damage_taken", "when": [{"fact": "battle_index", "op": "eq", "value": 1}, {"fact": "round_index", "op": "eq", "value": 1}], - "do": {"kind": "shield", "value": 1}}, {}) + "do": {"kind": "shield", "value": int(params["shield"])}}, {}) "sense_sharp": # 锋=踩中危险格伤害减半·每关限次——mine_hit 挂点现成(damage_mult 收集→halve_pct, # 与惊巢反制同路径;伤害在结算战斗段末兑付,探索阶段零改动) _push_trigger("tree", rk, {"on": "mine_hit", - "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}], - "do": {"kind": "damage_mult", "value": 50}}, {}) + "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": int(params["cap_per_level"])}], + "do": {"kind": "damage_mult", "value": int(params["halve_pct"])}}, {}) _push_trigger("tree", rk, {"on": "mine_hit", - "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": int(params["cap_per_level"])}], "do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_sharp_used", "value": 1}}, {}) "sense_guard": # 守=标错返还行动点·每关限次(契约 12 失误补偿——seal_missed 由 _judge_seal 去重闸后 # 发射;queue=下一轮开轮兑现,末轮判出无下一轮=合法作废自动成立) _push_trigger("tree", rk, {"on": "seal_missed", - "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}], - "do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, {}) + "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": int(params["cap_per_level"])}], + "do": {"kind": "grant_ap", "mode": "queue", "value": int(params["ap_refund"])}}, {}) _push_trigger("tree", rk, {"on": "seal_missed", - "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": int(params["cap_per_level"])}], "do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_guard_used", "value": 1}}, {}) @@ -461,7 +461,7 @@ static func _vtr_from_refs(rid: String, tr: Dictionary, row: Dictionary, errs: A ## 加法族(数值相加,可交换):add_ticket_rider、attack_bonus、grant_material、grant_ap(非 transfer)、 ## counter_add、shield——发射点逐条累加收集器。 ## 乘法族(收集后一次施加,正负分桶):yield_mult、damage_mult。 -## 任意真族(任一命中即生效):spawn_spell、overflow、spike。 +## 任意真族(任一命中即生效):spawn_spell、spike。 ## 按序改写族(收集→单点按声明序施加):modify_ticket 及一切改写窗口效果。 ## 按序消耗族(按订阅序消耗共享资源,耗尽即止;可选 fallback):counter_spend。 ## 即时执行族(每条命中各自执行一次,执行序=声明序):open_ticket、grant_ap(mode:transfer)、 diff --git a/game/core/level_runner.gd b/game/core/level_runner.gd index 502c6d0a..46619890 100644 --- a/game/core/level_runner.gd +++ b/game/core/level_runner.gd @@ -91,7 +91,7 @@ func _init(db_ref: ConfigDb, rng_ref: RngServiceCore, ctx: Dictionary = {}) -> v treasure_inventory = ctx["treasure_inventory"] # 冒险级库存共享(同调未用行跨关存活——单源化承载面) _treasure_unlocked = ctx.get("treasure_unlocked", {}) if ctx.get("treasure_unlocked", {}) is Dictionary else {} _attunement = ctx.get("attunement", []) if ctx.get("attunement", []) is Array else [] # 活引用注入(契约 6——禁快照) - _tree_rules = ctx.get("tree_rules", []) if ctx.get("tree_rules", []) is Array else [] # 活引用注入 + _tree_rules = ctx.get("tree_rules", {}) if ctx.get("tree_rules", {}) is Dictionary else {} # 活引用注入(规则键参数字典) _hero_attack_type = String(ctx.get("hero_attack_type", "")) _hero_armor_type = String(ctx.get("hero_armor_type", "")) _voucher_rap = maxi(0, int(ctx.get("voucher_rap", 0))) @@ -661,8 +661,8 @@ func current_battle() -> Variant: # 特性持有集(begin_battle enc.feats 求值面;**共享引用不复制**——关内奖励节点兑付的 relic 须当关生效) var _feats: Array[Variant] = [] -# 树 rule 键集(begin_battle enc.tree_rules 携带;Fork 激活侧 effects——构造注入活引用) -var _tree_rules: Array[Variant] = [] +# 树规则参数字典(begin_battle enc.tree_rules 携带;Fork 激活侧 effects_*——构造注入活引用) +var _tree_rules: Dictionary[Variant, Variant] = {} # 特性持有集读面(HUD C 区特性段数据源) diff --git a/game/core/meta_progression.gd b/game/core/meta_progression.gd index e762d613..abf252ff 100644 --- a/game/core/meta_progression.gd +++ b/game/core/meta_progression.gd @@ -384,9 +384,10 @@ func tree_buy_fork(node_id: String) -> bool: return save.write_to_disk() -# 树规则键投影(激活 Fork 侧 effects 并入) -func tree_rule_keys() -> Array: - var out: Array[Variant] = [] +# 树规则参数字典投影(激活 Fork 侧 effects_* 参数字典并入——键=规则键,值=该键参数字典原样透传; +# 首遇优先去重语义保持) +func tree_rules() -> Dictionary[Variant, Variant]: + var out: Dictionary[Variant, Variant] = {} var sides: Dictionary[Variant, Variant] = meta_state.tree_state().fork_sides for row in db.crossrun.meta_tree.rows: if String(row.get("kind", "")) != "fork": @@ -397,7 +398,7 @@ func tree_rule_keys() -> Array: var fx: Dictionary[Variant, Variant] = row.get("effects_" + active, {}) as Dictionary for k in fx.keys(): if not out.has(String(k)): - out.append(String(k)) + out[String(k)] = fx[k] return out diff --git a/game/core/minesweeper_rules.gd b/game/core/minesweeper_rules.gd index 24163cf8..c6c4b518 100644 --- a/game/core/minesweeper_rules.gd +++ b/game/core/minesweeper_rules.gd @@ -368,6 +368,19 @@ func fx_reveal(c: Vector2i) -> bool: return true +## 挖一格→真实翻开集只读预览(sim 信息面出口——与 _classify 同口径:本格封印视为已隐式退役、 +## 其余封印照挡;复用 _chain_closure 闭包;零状态写入、零事件发射) +func preview_reveal(c: Vector2i) -> Array[Vector2i]: + var sim_seals := _seals.duplicate() + sim_seals.erase(c) + var closure := _chain_closure(c, sim_seals) + var out: Array[Vector2i] = [] + for cc in closure.keys(): + if cc != c and not _revealed.has(cc): + out.append(cc) # F2 口径:新开格集——不含首格与已开格 + return out + + # 连锁闭包:μX.[O∪{c} ⊆ X ∧ ∀x∈X:(x∈S ∧ num(x)=0 ⇒ N₈(x)∩S∖G ⊆ X)]——连锁不越封印 func _chain_closure(start: Vector2i, seal_set: Dictionary) -> Dictionary: var chain := {start: true} diff --git a/game/core/node_map_generator.gd b/game/core/node_map_generator.gd index e07c67a5..bcf3044e 100644 --- a/game/core/node_map_generator.gd +++ b/game/core/node_map_generator.gd @@ -9,7 +9,6 @@ extends RefCounted const NODE_TYPES := ["battle", "boss", "reward", "rest", "shop"] const FILLER_TYPES := ["battle", "reward", "rest"] -const MAX_WIDTH := 4 # 产出约束·宽度=分叉生长(同深度节点数由岔口长出 1→2→3→4,禁摆平行层) # 多终点图的支路互斥段必须各有独有类型(契约 5③),而商店与 Boss 是每条支路共有件, # 独有类型只能来自 battle/reward/rest 三件 ⇒ 归属大岔口各区域的填充节点取互斥签名类型。 # 入口与主链段(大岔口上游,不属任何支路互斥段)恒为战斗节点——承载战斗保底与岔口下一跳互异基准形。 @@ -66,40 +65,11 @@ func _setup_and_build(params: Dictionary, seed_val: int) -> Dictionary: ## 参数档可行性判定(执行边界③:参数无解=配置错误)——域校验外加深度预算与战斗保底可放性。 static func check_params(params: Dictionary) -> Array: - var errs: Array[Dictionary] = _cp_domain(params) - if not errs.is_empty(): - return errs + # 域校验并单点(R7——参数档域合法性唯一在 LevelGenParams.validate 加载期; + # 生成器只留可行性判定 _cp_structural——schema 无此判定的生成器职责) return _cp_structural(params) -## 域校验段:五旋钮值域/岔口去路数锁值/配比四类合计 1 -static func _cp_domain(params: Dictionary) -> Array[Dictionary]: - var errs: Array[Dictionary] = [] - var L := int(params.get("layers", 0)) - var e := int(params.get("exit_count", 0)) - var en := int(params.get("entry_count", 0)) - var bf := int(params.get("battle_floor", -1)) - var bd := float(params.get("branch_density", -1.0)) - if L < 6: - errs.append({"category": "domain", "message": "层深 %d 低于下限 6(层高已锁)" % L}) - if e < 1 or e > 3: - errs.append({"category": "domain", "message": "出口个数 %d 越域 [1,3]" % e}) - if en < 1: - errs.append({"category": "domain", "message": "入口个数 %d 越域(≥1)" % en}) - if bd < 0.0 or bd > 1.0: - errs.append({"category": "domain", "message": "分支密度 %f 越域 [0,1]" % bd}) - if bf < 0: - errs.append({"category": "domain", "message": "战斗保底 %d 越域(≥0)" % bf}) - if int(params.get("fork_ways_min", 2)) < 2 or int(params.get("fork_ways_max", 3)) > 3: - errs.append({"category": "domain", "message": "岔口去路数域越界(锁 2/3)"}) - var sum := 0.0 - for rk in ["battle_ratio", "reward_ratio", "rest_ratio", "shop_ratio"]: - sum += float(params.get(rk, -1.0)) - if not is_equal_approx(sum, 1.0): - errs.append({"category": "domain", "message": "配比四类合计 %f ≠ 1" % sum}) - return errs - - ## 结构可行性段:主链段容量与战斗保底可放性(多出口按区域签名累加可放槽) static func _cp_structural(params: Dictionary) -> Array[Dictionary]: var errs: Array[Dictionary] = [] @@ -271,8 +241,8 @@ func _vo_width_rules(nodes: Array, errs: Array) -> void: var d2: int = int(n["depth"]) per_depth[d2] = int(per_depth.get(d2, 0)) + 1 for d3 in per_depth: - if int(per_depth[d3]) > MAX_WIDTH: - errs.append({"category": "structural", "message": "深度 %d 节点数 %d 超分叉生长上限 4(平行层摆放)" % [d3, per_depth[d3]]}) + if int(per_depth[d3]) > int(_params["width_max"]): + errs.append({"category": "structural", "message": "深度 %d 节点数 %d 超分叉生长上限(width_max 表值——平行层摆放)" % [d3, per_depth[d3]]}) ## 计数规则③·参数档对照:战斗保底/入口数/终点数/层高(params 为空时跳过) @@ -358,7 +328,7 @@ func _build_region(r: int, e: int, L: int, prevs: Array, terminals: Array) -> vo ## 侧支节点与主去路同深(保持「深度=自入口最长路径步数」不变量),两支各自汇入主去路的后继, ## 于是支路互斥段={主去路节点} 与 {侧支节点}——两支类型互异即同时满足「下一跳互异」与「各有独有类型」。 func _add_forks(rg: RandomNumberGenerator, L: int, t: int, e: int) -> void: - var wanted := clampi(int(round(float(_params["branch_density"]) * float(L))), 1, 24) + var wanted := maxi(int(round(float(_params["branch_density"]) * float(L))), 1) # 死上限 24 删(不可达值——下限 1 保留防零岔口) var anchors := _collect_fork_anchors(L, t, e) anchors = _shuffle(rg, anchors) var selected := {} # 已扩岔节点 id @@ -377,10 +347,10 @@ func _add_forks(rg: RandomNumberGenerator, L: int, t: int, e: int) -> void: continue if (v["next"] as Array).is_empty(): continue # 主去路无后继(无可汇入点)——该锚点弃用 - var extra := 2 if (int(_params["fork_ways_max"]) >= 3 and rg.randf() < 0.35) else 1 + var extra := 2 if (int(_params["fork_ways_max"]) >= 3 and rg.randf() < float(_params["fork_third_way_pct"]) / 100.0) else 1 var made := 0 for k in range(extra): - if _depth_count(int(v["depth"])) >= MAX_WIDTH: + if _depth_count(int(v["depth"])) >= int(_params["width_max"]): break var wid := _mk("battle", int(v["depth"])) (a["next"] as Array).append(wid) @@ -452,7 +422,7 @@ func _fill_ratios(rg: RandomNumberGenerator) -> void: free = _shuffle(rg, free) var want := int(round(float(_pool.size()) * (float(_params["reward_ratio"]) + float(_params["rest_ratio"])))) var floor_n := int(_params["battle_floor"]) - var rest_first := rg.randf() < 0.5 + var rest_first := rg.randf() < float(_params["fill_rest_first_pct"]) / 100.0 for i in range(want): if i >= free.size(): break diff --git a/game/core/settlement/combat_segment.gd b/game/core/settlement/combat_segment.gd index 1dd34c59..0c047327 100644 --- a/game/core/settlement/combat_segment.gd +++ b/game/core/settlement/combat_segment.gd @@ -1,8 +1,8 @@ class_name CombatSegment extends RefCounted # ③ 战斗段 Segment(P1-1 结算巨函数拆段;段边界=审计五段〔裁点③甲〕)。 -# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak/连珠矢段数)+ 蓄力层注入 + 主角先手 -# (含连珠矢分段开单)+ 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。 +# 职责:案C 闸复位 + 本轮扫雷事实推导(执拗 streak 取数面)+ 蓄力层注入 + 主角先手 +# + 敌方行动兑现 + 地图伤害账殿后(补提交统一结账)。 # 调用序:主角先手 → 逐敌还手 → 地图伤害账殿后〔案C〕;hero hp=0 短路。 # 行发射/开单单点归宿主(oc)——案C 暂存闸归 RewardGrant(段持三引用直调,零转发壳)—— # 本函数体为原编排器 _combat_segment 逐字搬运(🔴 零行为变更契约)。 @@ -21,7 +21,7 @@ func _init(host, fx_host_ref: BattleFxHost, reward_grant_ref: RewardGrant) -> vo func run(ri: int) -> void: reward_grant.defer["on"] = false # 案C 闸复位——战斗段起开单即入队(主角/敌方/补提交的地图单按开单序统一结账) - # 本轮扫雷事实推导(执拗 streak/连珠矢段数共用取数面):本轮 safe 事件数= + # 本轮扫雷事实推导(执拗 streak 取数面):本轮 safe 事件数= # streak;踩雷即清零(mine_hit 监听同口——执拗「踩雷清零」承载)。攻击在战斗段=授予段后 # 同一次 _settle 内,facts 已定。 var round_safe := 0 @@ -56,18 +56,6 @@ func _hero_strike(ri: int) -> void: "ri": ri, "origin_cell": Vector2i(-1, -1), "attack_type": String(oc._enemy_system.hero_unit().get("attack_type", "normal"))}) oc._damage.submit(ht) - # 连珠矢(内容数据驱动扩展批 T3——attack_commit 订阅施加面:开格节奏变攻击段数,本轮每开 - # ~3 安全格 +1 段穿刺附加(上限 cap;段值 provisional 固定值不乘 atk——台账披露)。施加位 - # 紧随主角攻击单开立=迁移前开单位(先于敌方行动单入队——flush 兑现序逐位一致) - for h in oc._hits_of(ht.context.get("fx_attack_hits", []) as Array, "attack_segments"): - var segments := mini(int(h.get("cap", 2)), int(oc.fx_host().facts_inject["dig_streak"]) / maxi(1, int(h.get("divisor", 3)))) - for s in segments: - # source_class 用中性名;attack_segments 兑付通道保留泛用面 - var ct := oc._damage.open_ticket(DamageController.KIND_BOARD_TYPED, "fx_streak_segments", "连击分段", - {"channel": "board", "base_value": int(h.get("value", 0)), - "target_rule": DamageController.TARGET_FRONT_ALIVE, "ri": ri, - "origin_cell": oc.minesweeper.hero_pos(), "attack_type": "pierce"}) - oc._damage.submit(ct) # 敌方行动(敌人技能池批——本拍动作开轮已摇定,此处按技能行兑现;行序 = 生成序)。 diff --git a/game/core/shop_session.gd b/game/core/shop_session.gd index be90aa47..8c1ceaac 100644 --- a/game/core/shop_session.gd +++ b/game/core/shop_session.gd @@ -53,12 +53,14 @@ func current_price(prow: Dictionary) -> int: + int(prow.get("price_increment", 0)) * purchased_tier(String(prow.get("product_id", "")))) -# 散折价单点(财富之道·散 economy_spend 激活时商品购买价 ×spend_discount〔表 knob,缺省 0.8〕, +# 散折价单点(财富之道·散 economy_spend 激活时商品购买价 ×discount_pct/100〔规则键参数字典—— +# 折率读 economy_spend 参数,零表字段〕, # floor 取整偏向玩家、下限 1 防 1 元商品折成 0;范围=商品标价定义边界〔legacy 商品面 current_price # + 池货架面 pool_price 同折〕——商店刷新价 shop_refresh_cost 与三选一付费刷新不折) func _spend_discounted(price: int) -> int: if _rules().has("economy_spend"): - return maxi(1, int(floor(float(price) * float(db.run.shop.spend_discount)))) + var spend_params: Dictionary[Variant, Variant] = _rules()["economy_spend"] as Dictionary + return maxi(1, int(floor(float(price) * float(spend_params["discount_pct"]) / 100.0))) return price @@ -152,7 +154,7 @@ func _draw_shelf() -> void: continue seen.append(k) shelf.append(tok) - if shelf.size() >= 3: + if shelf.size() >= int(db.run.shop.shelf_size): break # T5 专长凭证注入(非 Boss 渠道唯一专长入口——货架与节点共享保底计数;判定含 pity 记账,替换末位占一席。 # 取数复用本函数 shop_draw scope 的 stream——零新 scope,确定性序列不漂移) @@ -329,8 +331,8 @@ func _level_index() -> int: return int((_refs["level_row"] as Dictionary).get("level_index", 0)) -func _rules() -> Array: - return _refs["tree_rules"] as Array +func _rules() -> Dictionary[Variant, Variant]: + return _refs["tree_rules"] as Dictionary func _attunement() -> Array: diff --git a/game/core/status_container.gd b/game/core/status_container.gd index 462ef7f9..cac4f904 100644 --- a/game/core/status_container.gd +++ b/game/core/status_container.gd @@ -46,25 +46,47 @@ func apply_effect(effect_id: String, owner_key: String, source: Dictionary = {}, if owner_key == "": push_warning("[增益状态] 空 owner_key 授予(个体归属违约)") return false + return _apply_open(effect_id, owner_key, source, duration_override) + + +# 开域授予主路径(守卫已过——未知行警告与三臂受理;单出口布尔受理) +func _apply_open(effect_id: String, owner_key: String, source: Dictionary, duration_override: int) -> bool: + var accepted := false var row: Dictionary[Variant, Variant] = db.combat.status_effects.row_by_id(effect_id) if row.is_empty(): push_warning("[增益状态] 未知 effect_id(EC2): %s" % effect_id) return false var policy: String = row.stack_policy var dur: int = int(row.duration_settlements) if duration_override < 1 else duration_override - var max_stack: int = int(row.max_stack) var snap := _grant_snapshot(row, source) var bucket: Array[Dictionary] = _bucket_of(owner_key) + var hit: Dictionary = {} + for inst in bucket: + if inst.effect_id == effect_id: + hit = inst + break match policy: "independent": bucket.append(_new_instance(effect_id, 1, dur, source, snap)) - return true + accepted = true "accumulate": - return _apply_accumulate(bucket, effect_id, dur, snap, source) + # 命中加时长未命中追加(R += D 取本次授予生效时长;L 不变;快照沿首授) + if hit.is_empty(): + bucket.append(_new_instance(effect_id, 1, dur, source, snap)) + else: + hit.remaining = int(hit.remaining) + dur + accepted = true "stack": - return _apply_stack(bucket, effect_id, dur, snap, source, max_stack) - push_warning("[增益状态] 未知 stack_policy") - return false + # 满层拒零写入·未满加层·未命中追加(stack 授予不触碰 R;拒收由战斗结算记"奖励未生效"行) + if hit.is_empty(): + bucket.append(_new_instance(effect_id, 1, dur, source, snap)) + accepted = true + elif int(hit.layers) < int(row.max_stack): + hit.layers = int(hit.layers) + 1 + accepted = true + _: + push_warning("[增益状态] 未知 stack_policy") + return accepted ## 授予时快照域(抗表重载——EC9;封印溢价贯穿——迭代14② T4 percent 形态同乘快照、缺省路径零扰动) @@ -84,29 +106,6 @@ func _bucket_of(owner_key: String) -> Array[Dictionary]: return _buckets[owner_key] -## 累加臂:命中加时长未命中追加(R += D 取本次授予生效时长——覆盖值 ≥1 按覆盖值;L 不变;快照沿首授;每次授予必有剩余次数变化) -func _apply_accumulate(bucket: Array[Dictionary], effect_id: String, dur: int, snap: Dictionary, source: Dictionary) -> bool: - for inst in bucket: - if inst.effect_id == effect_id: - inst.remaining = int(inst.remaining) + dur - return true - bucket.append(_new_instance(effect_id, 1, dur, source, snap)) - return true - - -## stack 臂:满层拒零写入·未满加层·未命中追加(stack 授予不触碰 R——spec 决议; -## 满层拒绝原子——容器零写入,拒收由战斗结算记"奖励未生效"行) -func _apply_stack(bucket: Array[Dictionary], effect_id: String, dur: int, snap: Dictionary, source: Dictionary, max_stack: int) -> bool: - for inst in bucket: - if inst.effect_id == effect_id: - if int(inst.layers) >= max_stack: - return false - inst.layers = int(inst.layers) + 1 - return true - bucket.append(_new_instance(effect_id, 1, dur, source, snap)) - return true - - func _new_instance(effect_id: String, layers: int, dur: int, source: Dictionary, snap: Dictionary) -> Dictionary: return { "effect_id": effect_id, diff --git a/game/data/schemas/action_economy_table.gd b/game/data/schemas/action_economy_table.gd index 9621c45b..c51819c6 100644 --- a/game/data/schemas/action_economy_table.gd +++ b/game/data/schemas/action_economy_table.gd @@ -4,7 +4,7 @@ class_name ActionEconomyTable extends Resource -@export var ap_per_round: int = 6 # [1,32] knob(默认值 = 现行表值基线,随 AP 压速调整) +@export var ap_per_round: int # [1,32] knob(零哨兵——表行缺字段越域即加载致命) @export var dig_ap_cost: int = 1 # [1,8] knob(域下限 1 = 零预算系统失效拒绝) @export var seal_place_ap_cost: int = 1 # [0,8] knob(无 seal_pull_ap_cost 字段——拔印不占 AP) @export var rap_refresh_cost: int = 6 # [1,64] knob(默认值随 AP 压速调整) diff --git a/game/data/schemas/board_gen_table.gd b/game/data/schemas/board_gen_table.gd index a8322475..7212dc2d 100644 --- a/game/data/schemas/board_gen_table.gd +++ b/game/data/schemas/board_gen_table.gd @@ -5,6 +5,7 @@ class_name BoardGenTable extends Resource @export var rows: Array[Dictionary] = [] # Array[Dictionary] +@export var spawn_edge: int = 0 # 出生区边长(奇数 [3,5]——零哨兵:表头缺字段=加载期域校验拒) const SOLVER_RULES := ["single", "single_subset"] const COST_BUDGET := 16384 # F2c provisional 量级护栏(禁墙钟逃生) const TARGET_ABS_FLOOR := 0.85 # 战斗结算 修订——target 域下限(P1 绝对锚) @@ -13,6 +14,8 @@ const BOARD_TYPE_VOCABULARY := ["dungeon", "underdark", "deep_underdark"] # func validate() -> Array: var errs: Array[Dictionary] = [] + if spawn_edge < 3 or spawn_edge > 5 or spawn_edge % 2 == 0: + errs.append({"category": "domain", "message": "spawn_edge 越域(奇数 [3,5]——零缺省): %d" % spawn_edge}) if rows.is_empty(): errs.append({"category": "empty_table", "message": "BoardGenTable 为空"}) return errs @@ -73,8 +76,8 @@ func _validate_row_constraints(row: Dictionary) -> Array[Dictionary]: var att: int = int(row.get("max_gen_attempts", 0)) if att < 1 or att > 32: errs.append({"category": "domain", "message": "(%d,%d) max_gen_attempts 越域 [1,32]" % [L, D]}) - # 布雷数下限(R3)——零雷板拒绝(−9 = 出生区格数,常量单点 BoardData.SPAWN_AREA_CELLS) - var n_mines := int(round(d * float(w * h - BoardData.SPAWN_AREA_CELLS))) + # 布雷数下限(R3)——零雷板拒绝(−spawn_edge² = 出生区格数,表头单点导出) + var n_mines := int(round(d * float(w * h - spawn_edge * spawn_edge))) if n_mines < 1: errs.append({"category": "cross_field", "message": "(%d,%d) 布雷数下限违例 round(d×(W×H−9)) ≥ 1" % [L, D]}) # F2b min_safe 可行性 @@ -102,9 +105,9 @@ func _validate_shave_params(row: Dictionary) -> Array[Dictionary]: errs.append({"category": "domain", "message": "(%d,%d) shave_ring_radius 越域 [0,2]: %d" % [L, D, sr]}) var r0_cap: int = int(row.get("r0_max_cap", 0)) if r0_cap > 0: - # 护栏(需求卡片 §二):cap ≥ 出生格数 9(BoardData.SPAWN_AREA_CELLS)、cap ≤ 板格数一半 - if r0_cap < BoardData.SPAWN_AREA_CELLS: - errs.append({"category": "cross_field", "message": "(%d,%d) r0_max_cap 低于出生保障面 %d: %d" % [L, D, BoardData.SPAWN_AREA_CELLS, r0_cap]}) + # 护栏(需求卡片 §二):cap ≥ 出生格数(spawn_edge² 表头导出)、cap ≤ 板格数一半 + if r0_cap < spawn_edge * spawn_edge: + errs.append({"category": "cross_field", "message": "(%d,%d) r0_max_cap 低于出生保障面 %d: %d" % [L, D, spawn_edge * spawn_edge, r0_cap]}) if r0_cap > (w * h) / 2: errs.append({"category": "cross_field", "message": "(%d,%d) r0_max_cap 超板格数一半 %d: %d" % [L, D, (w * h) / 2, r0_cap]}) return errs diff --git a/game/data/schemas/difficulty_table.gd b/game/data/schemas/difficulty_table.gd index f7ff7cf5..6b4a4e7d 100644 --- a/game/data/schemas/difficulty_table.gd +++ b/game/data/schemas/difficulty_table.gd @@ -22,7 +22,7 @@ func validate() -> Array: seen[key] = true if L < 1 or D < 0: errs.append({"category": "domain", "message": "难度键越域 (%d,%d)" % [L, D]}) - # 乘数越域 = warning 级(使用时 clamp——组合事故非配置事故,难度曲线与Boss GDD Edge Case 预登记) + # 乘数越域 = warning 级(使用时 clamp 到 MULT_MIN/MULT_MAX——组合事故非配置事故,warning 预登记) for k in ["hp_mult", "atk_mult"]: var v: float = float(row.get(k, 1.0)) if v < MULT_MIN or v > MULT_MAX: diff --git a/game/data/schemas/feat_table.gd b/game/data/schemas/feat_table.gd index 4aa81b2e..1b36ad62 100644 --- a/game/data/schemas/feat_table.gd +++ b/game/data/schemas/feat_table.gd @@ -1,4 +1,5 @@ ## VERTICAL SLICE - NOT FOR PRODUCTION + ## 专长(奇物·Feat)定义表 schema:逐 feat_id 行订阅式效果声明的类型化读取(空表合法态;垂直切片验证:订阅行经效果引擎统一解释执行)。 ## 🔴 层级纪律:专长只走局内随机渠道(Boss 三选一/凭证开启)——本表行永不入同调位表/局外树。 ## triggers 订阅行四词表封闭校验单点在 EffectEngine.validate_trigger_row——本表不自建第二套校验。 @@ -36,8 +37,7 @@ func validate() -> Array: errs.append({"category": "domain", "message": "%s display_name 空" % rid}) if not RARITIES.has(String(row.get("rarity", ""))): errs.append({"category": "enum", "message": "%s rarity 出域 %s" % [rid, str(RARITIES)]}) - if row.has("cap") and int(row.get("cap", 1)) < 1: - errs.append({"category": "domain", "message": "%s cap 出域 [1,∞): %d" % [rid, int(row.get("cap", 1))]}) + errs.append_array(_validate_cap(row, rid)) if not row.has("triggers"): errs.append({"category": "domain", "message": "%s 缺 triggers(订阅形态唯一合法行形)" % rid}) else: @@ -48,6 +48,16 @@ func validate() -> Array: return errs +## cap 域(必填零兜底——缺行拒、越域 [1,99] 拒;域=config_db_cross_validator 在册口径) +func _validate_cap(row: Dictionary, rid: String) -> Array[Dictionary]: + var errs: Array[Dictionary] = [] + if not row.has("cap"): + errs.append({"category": "domain", "message": "%s 缺 cap(必填——零兜底)" % rid}) + elif int(row.get("cap", 0)) < 1 or int(row.get("cap", 0)) > 99: + errs.append({"category": "domain", "message": "%s cap 越域 [1,99]: %d" % [rid, int(row.get("cap", 0))]}) + return errs + + func row_by_id(feat_id: String) -> Dictionary: for row in rows: if String(row.get("feat_id", "")) == feat_id: diff --git a/game/data/schemas/level_gen_params.gd b/game/data/schemas/level_gen_params.gd index 53284bcd..40563cc5 100644 --- a/game/data/schemas/level_gen_params.gd +++ b/game/data/schemas/level_gen_params.gd @@ -7,7 +7,8 @@ extends Resource const RATIO_KEYS := ["battle_ratio", "reward_ratio", "rest_ratio", "shop_ratio"] const ROW_KEYS := ["level_index", "layers", "branch_density", "entry_count", "exit_count", - "battle_floor", "fork_ways_min", "fork_ways_max"] + RATIO_KEYS + "battle_floor", "fork_ways_min", "fork_ways_max", + "fork_third_way_pct", "fill_rest_first_pct", "width_max"] + RATIO_KEYS @export var rows: Array[Dictionary] = [] # 每行 = ROW_KEYS 键集(level_index 唯一正整数) @@ -65,6 +66,16 @@ func _validate_row_shape(row: Dictionary, L: int, key: String, layers_by_level: var bd: float = float(row.get("branch_density", -1.0)) if bd < 0.0 or bd > 1.0: errs.append({"category": "domain", "message": "%s 分支密度 %f 越域 [0,1]" % [key, bd]}) + # 域并单点(关卡节点图生成.md v1.4 调参表——生成器侧域校验退役,唯此一处) + var ftw: float = float(row.get("fork_third_way_pct", -1.0)) + if ftw < 0.0 or ftw > 100.0: + errs.append({"category": "domain", "message": "%s fork_third_way_pct %f 越域 [0,100]" % [key, ftw]}) + var frf: float = float(row.get("fill_rest_first_pct", -1.0)) + if frf < 0.0 or frf > 100.0: + errs.append({"category": "domain", "message": "%s fill_rest_first_pct %f 越域 [0,100]" % [key, frf]}) + var wmax: int = int(row.get("width_max", 0)) + if wmax < 2 or wmax > 6: + errs.append({"category": "domain", "message": "%s width_max %d 越域 [2,6]" % [key, wmax]}) var ratio_sum := 0.0 for rk in RATIO_KEYS: var v: float = float(row.get(rk, -1.0)) diff --git a/game/data/schemas/meta_tree_table.gd b/game/data/schemas/meta_tree_table.gd index e88ceae2..352264c7 100644 --- a/game/data/schemas/meta_tree_table.gd +++ b/game/data/schemas/meta_tree_table.gd @@ -23,6 +23,16 @@ const SPINE_ROW_GATE := 4 ## 无 fg_mastery 克制专精组——类型表达归锻造工坊) const FORK_GROUPS := ["fg_posture", "fg_sense", "fg_economy"] +## 规则键参数域表(键集与取值域逐字=天赋树.md v0.12 规则键参数表——文档定域、schema 执行) +const RULE_KEY_PARAMS := { + "posture_first_strike": {"ap_bonus": [1, 4]}, + "posture_bulwark": {"shield": [1, 9]}, + "sense_sharp": {"halve_pct": [1, 99], "cap_per_level": [1, 9]}, + "sense_guard": {"ap_refund": [1, 4], "cap_per_level": [1, 9]}, + "economy_hoard": {"mult_pct": [101, 300]}, + "economy_spend": {"discount_pct": [1, 99]}, +} + func validate() -> Array: var errs: Array[Variant] = [] @@ -42,6 +52,7 @@ func validate() -> Array: errs.append_array(_validate_row_head(row, nid)) errs.append_array(_validate_row_numbers(row, nid)) errs.append_array(_validate_row_effects(row, nid)) + errs.append_array(_validate_rule_key_params(row, nid)) errs.append_array(_validate_row_fork(row, nid, fork_seen)) errs.append_array(_validate_requires_dangling(rows, seen)) errs.append_array(_validate_requires_level(rows, seen)) @@ -101,6 +112,43 @@ func _validate_row_effects(row: Dictionary, nid: String) -> Array[Variant]: return errs +## 规则键参数域校验:effects/effects_a/effects_b 中凡 RULE_KEYS 成员的值必须是 Dictionary、 +## 键集恰等 RULE_KEY_PARAMS[键] 的键集(缺参数/多参数即拒)、每值 int 且在闭域内(零缺省——缺字段=加载期报错) +func _validate_rule_key_params(row: Dictionary, nid: String) -> Array[Variant]: + var errs: Array[Variant] = [] + for fx_key in ["effects", "effects_a", "effects_b"]: + var fx: Dictionary[Variant, Variant] = row.get(fx_key, {}) as Dictionary + for rk in fx.keys(): + if not RULE_KEYS.has(String(rk)): + continue + errs.append_array(_validate_one_rule_key_params(nid, fx_key, String(rk), fx[rk])) + return errs + + +## 单规则键参数字典校验(值须为 Dictionary、键集恰等参数域表、每值 int 且在闭域内) +func _validate_one_rule_key_params(nid: String, fx_key: String, rk: String, params_v: Variant) -> Array[Variant]: + var errs: Array[Variant] = [] + if not (params_v is Dictionary): + errs.append({"category": "domain", "message": "%s %s.%s 缺参数(规则键值须为参数字典——零缺省)" % [nid, fx_key, rk]}) + return errs + var params: Dictionary[Variant, Variant] = params_v + var expect: Dictionary = RULE_KEY_PARAMS[rk] + for pk in params.keys(): + if not expect.has(String(pk)): + errs.append({"category": "domain", "message": "%s %s.%s 多参数: %s(参数域表封闭)" % [nid, fx_key, rk, String(pk)]}) + elif not (params[pk] is int): + errs.append({"category": "domain", "message": "%s %s.%s.%s 越域(须 int)" % [nid, fx_key, rk, String(pk)]}) + else: + var dom: Array = expect[String(pk)] + var v := int(params[pk]) + if v < int(dom[0]) or v > int(dom[1]): + errs.append({"category": "domain", "message": "%s %s.%s.%s 越域 [%d,%d]: %d" % [nid, fx_key, rk, String(pk), int(dom[0]), int(dom[1]), v]}) + for pk in expect.keys(): + if not params.has(String(pk)): + errs.append({"category": "domain", "message": "%s %s.%s 缺参数: %s(参数域表封闭)" % [nid, fx_key, rk, String(pk)]}) + return errs + + ## Fork 结构域:一节点双态结构判据(fork_group 词表/组唯一/主 effects 须空/a/b 双键规则面) func _validate_row_fork(row: Dictionary, nid: String, fork_seen: Dictionary) -> Array[Variant]: var errs: Array[Variant] = [] diff --git a/game/data/schemas/shop_table.gd b/game/data/schemas/shop_table.gd index 768493e2..973979c0 100644 --- a/game/data/schemas/shop_table.gd +++ b/game/data/schemas/shop_table.gd @@ -13,7 +13,7 @@ extends Resource @export var price_a: float = 0.0 # 进店已购次数爬价因子(provisional——0 = 未投值即致命域) @export var price_b: float = 0.0 # 跨关递增因子(provisional) @export var relic_base_price: float = 15.0 # relic 保底卡基价(provisional——池令牌非货架种,词表外专枝) -@export var spend_discount: float = 0.8 # 财富之道·散 economy_spend 激活时的购买折扣(provisional) +@export var shelf_size: int = 0 # 货架位数(零哨兵——缺字段越域 [1,6] 即加载致命) const GP_TIERS := ["normal", "elite", "boss"] const PRODUCT_ATTRS := ["atk", "max_hp", "armor_class"] @@ -131,8 +131,8 @@ func _validate_price_factors(errs: Array[Dictionary]) -> void: errs.append({"category": "domain", "message": "price_b 越域 [1.0,2.0]: %f" % price_b}) if relic_base_price < 1.0 or relic_base_price > 99.0: errs.append({"category": "domain", "message": "relic_base_price 越域 [1.0,99.0]: %f" % relic_base_price}) - if spend_discount <= 0.0 or spend_discount > 1.0: - errs.append({"category": "domain", "message": "spend_discount 越域 (0.0,1.0]: %f" % spend_discount}) + if shelf_size < 1 or shelf_size > 6: + errs.append({"category": "domain", "message": "shelf_size 越域 [1,6]: %d" % shelf_size}) # ---- 行族访问器(自 ConfigDB 归位——行集解析单点;消费面 db.run.shop. 直连)---- diff --git a/game/data/schemas/variation_table.gd b/game/data/schemas/variation_table.gd index 1b4af099..537fa9bf 100644 --- a/game/data/schemas/variation_table.gd +++ b/game/data/schemas/variation_table.gd @@ -9,9 +9,10 @@ extends Resource # 布置参数(表头字段;数量公式 = round(候选池格数 × percent_of_cells / 100) + (关卡序号 − 1) × per_level_bonus, # 连通片生长、单片 ≤ pack_max——图规模百分比 × 关卡递增,全连通、单片不超 4; # 百分比基数 = 候选池(安全格 − 初始展开区,即实际可挖格)而非总格数) -@export var percent_of_cells: int = 5 -@export var per_level_bonus: int = 2 -@export var pack_max: int = 4 +@export var percent_of_cells: int = 0 +@export var per_level_bonus: int = 0 +@export var pack_max: int = 0 +@export var pack_min: int = 0 # 成包下界(零哨兵——缺字段越域即加载致命) const SCOPES := ["region", "global"] # resource_face = 资源面偏斜(全局作用域变异——整图修摇号分布:生存族按 effect_value% @@ -24,6 +25,7 @@ const PLACEMENTS := ["closed_only", "any"] # 布置约束 func validate() -> Array: # 空清单是合法状态(地形变异 P0)——零行即零变体格,配置加载零错误、零警告 var errs: Array[Dictionary] = [] + errs.append_array(_validate_header_domains()) var seen := {} for row in rows: var head := _validate_row(row, seen) @@ -36,6 +38,20 @@ func validate() -> Array: return errs +## 表头域校验单点(地形变异.md v1.2 调参表 owner 域——零缺省,缺字段/越域即拒) +func _validate_header_domains() -> Array[Dictionary]: + var errs: Array[Dictionary] = [] + if percent_of_cells < 0 or percent_of_cells > 50: + errs.append({"category": "domain", "message": "percent_of_cells 越域 [0,50]: %d" % percent_of_cells}) + if per_level_bonus < 0 or per_level_bonus > 9: + errs.append({"category": "domain", "message": "per_level_bonus 越域 [0,9]: %d" % per_level_bonus}) + if pack_min < 1 or pack_min > pack_max: + errs.append({"category": "domain", "message": "pack_min 越域 [1,pack_max=%d]: %d" % [pack_max, pack_min]}) + if pack_max < pack_min or pack_max > 9: + errs.append({"category": "domain", "message": "pack_max 越域 [pack_min=%d,9]: %d" % [pack_min, pack_max]}) + return errs + + ## 行域:variant_id 非空与查重/display_name/unlock_level/四词表(scope/effect/trigger/placement) func _validate_row(row: Dictionary, seen: Dictionary) -> Dictionary: var errs: Array[Dictionary] = [] diff --git a/game/data/tables/board_gen.tres b/game/data/tables/board_gen.tres index 15d9393b..6154d94b 100644 --- a/game/data/tables/board_gen.tres +++ b/game/data/tables/board_gen.tres @@ -4,6 +4,7 @@ [resource] script = ExtResource("1") +spawn_edge = 3 rows = [{ "level_index": 1, "node_depth": 0, @@ -411,7 +412,7 @@ rows = [{ ; 切片调参登记(SLICE-TUNED provisional,调参所有权仍归 C3): ; A1 GDD 参考行(d0.10/0.12)经"先布后删"环内移除后仅幸存 2-3 雷、R0/|S|约0.97、残余挖掘 2-4 格—— -; 掘近退化板(A2 反例 B 机制的低密度放大)。实验数据(8 种子均值,smoke/experiment_density.gd): +; 掘近退化板(A2 反例 B 机制的低密度放大)。实验数据(8 种子均值,出处=退役件 experiment_density.gd(git 历史可考)): ; d0.25 → L1 幸存雷 11/残余 13/inf 0.925/att 2.9 · L2 雷 19/残余 21/inf 0.908/att 7.9(seed42 实测 inf 0.889→target 定 0.88 首过),零降级告警。 ; target 收窄至 0.88/0.88(不低于 0.85 绝对锚)为密度上移的对价——B1 条款③⑤对账重跑义务随调参触发。 ; 2026-09-11 关卡扩展批:L1 增 D1–D4 行(Boss=Dmax=4)。 diff --git a/game/data/tables/feats.tres b/game/data/tables/feats.tres index 6bb2bf5c..28687a22 100644 --- a/game/data/tables/feats.tres +++ b/game/data/tables/feats.tres @@ -20,6 +20,7 @@ rows = [ "feat_id": "twin_pick", "display_name": "双管镐", "rarity": "common", + "cap": 1, "desc": "每第 2 次付费开格返 1 AP", "triggers": [ { @@ -52,6 +53,7 @@ rows = [ "feat_id": "geologist", "display_name": "地质学家", "rarity": "common", + "cap": 1, "desc": "每首开一种新类型资源格集 1 点,满 4 种→揭示 1 格", "triggers": [ { @@ -106,6 +108,7 @@ rows = [ "feat_id": "hard_hat", "display_name": "安全帽", "rarity": "excellent", + "cap": 1, "desc": "开安全格攒 1 层;触陷阱耗层,每层减 1 伤害(一次清零)", "triggers": [ { @@ -145,6 +148,7 @@ rows = [ "feat_id": "skip_mining", "display_name": "跳格采掘", "rarity": "excellent", + "cap": 1, "desc": "挖开空白格攒 1 点,3 点→揭示 1 格", "triggers": [ { @@ -199,6 +203,7 @@ rows = [ "feat_id": "blast_accounting", "display_name": "爆破会计", "rarity": "common", + "cap": 1, "desc": "轮末:本轮开格花费 AP≥5 → +1 GP", "triggers": [ { @@ -231,6 +236,7 @@ rows = [ "feat_id": "frontier_miner", "display_name": "边陲矿工", "rarity": "common", + "cap": 1, "desc": "挖开棋盘边缘格:产出 +1", "triggers": [ { @@ -253,6 +259,7 @@ rows = [ "feat_id": "seal_echo", "display_name": "封印回响", "rarity": "excellent", + "cap": 1, "desc": "封印命中计数;轮末计数≥2 → 返 1 AP(下轮)", "triggers": [ { @@ -301,6 +308,7 @@ rows = [ "feat_id": "warning_beacon", "display_name": "示警烟火", "rarity": "common", + "cap": 1, "desc": "封印命中:揭示周围 3×3 的资源类型(不揭安危)", "triggers": [ { @@ -316,6 +324,7 @@ rows = [ "feat_id": "seal_smith", "display_name": "封印匠", "rarity": "rare", + "cap": 1, "desc": "封印命中每第 3 次 → 该危险格中立移除", "triggers": [ { @@ -348,6 +357,7 @@ rows = [ "feat_id": "seal_stake", "display_name": "封印桩", "rarity": "common", + "cap": 1, "desc": "封印命中:本轮 AC +1", "triggers": [ { @@ -364,6 +374,7 @@ rows = [ "feat_id": "pain_memory", "display_name": "疼痛记忆", "rarity": "rare", + "cap": 1, "desc": "触陷阱攒 1 层;下次攻击附加层值伤害,用后清零", "triggers": [ { @@ -411,6 +422,7 @@ rows = [ "feat_id": "fuse_wire", "display_name": "保险丝", "rarity": "rare", + "cap": 1, "desc": "单次受伤 ≥5 攒 1 点,2 点→护盾 1 层", "triggers": [ { @@ -465,6 +477,7 @@ rows = [ "feat_id": "blood_insight", "display_name": "见血开窍", "rarity": "common", + "cap": 1, "desc": "触陷阱:揭示 1 格", "triggers": [ { @@ -480,6 +493,7 @@ rows = [ "feat_id": "trap_instinct", "display_name": "陷阱本能", "rarity": "excellent", + "cap": 1, "desc": "触陷阱:本轮攻击 +2(走攻击面板乘区)", "triggers": [ { @@ -503,6 +517,7 @@ rows = [ "feat_id": "chain_traps", "display_name": "连环陷阱", "rarity": "excellent", + "cap": 1, "desc": "同轮第 2 次及以后的触陷阱:每次额外 +1 层(疼痛记忆同账)", "triggers": [ { @@ -535,6 +550,7 @@ rows = [ "feat_id": "trap_burst", "display_name": "陷阱爆发", "rarity": "rare", + "cap": 1, "desc": "触陷阱:全体存活敌受陷阱伤×1.5 直伤", "triggers": [ { @@ -550,6 +566,7 @@ rows = [ "feat_id": "blood_honey", "display_name": "血蜜", "rarity": "common", + "cap": 1, "desc": "触陷阱:回复 2 生命(陷阱伤仍净亏血)", "triggers": [ { @@ -565,6 +582,7 @@ rows = [ "feat_id": "stone_skin", "display_name": "石肤", "rarity": "excellent", + "cap": 1, "desc": "受伤:AC +1(本场累计,上限 3)", "triggers": [ { @@ -582,6 +600,7 @@ rows = [ "feat_id": "last_stand", "display_name": "背水", "rarity": "rare", + "cap": 1, "desc": "生命≤50%:受伤额外 −2", "triggers": [ { @@ -604,6 +623,7 @@ rows = [ "feat_id": "breaker_strike", "display_name": "破封一击", "rarity": "excellent", + "cap": 1, "desc": "挖开带封印的危险格:+1 层(疼痛记忆同账)", "triggers": [ { @@ -627,6 +647,7 @@ rows = [ "feat_id": "map_exchange", "display_name": "弃图兑换", "rarity": "excellent", + "cap": 1, "desc": "停手:剩余 AP≥3 → 折为攻击伤害", "triggers": [ { @@ -682,6 +703,7 @@ rows = [ "feat_id": "last_drop", "display_name": "一滴不剩", "rarity": "common", + "cap": 1, "desc": "上轮 AP 耗尽 → +1 AP", "triggers": [ { @@ -705,6 +727,7 @@ rows = [ "feat_id": "market_close", "display_name": "收盘", "rarity": "common", + "cap": 1, "desc": "轮末:本轮开格 ≥8 → +1 GP", "triggers": [ { @@ -736,6 +759,7 @@ rows = [ "feat_id": "execution_scholar", "display_name": "斩首学者", "rarity": "common", + "cap": 1, "desc": "击杀:揭示 1 格", "triggers": [ { @@ -751,6 +775,7 @@ rows = [ "feat_id": "executioner", "display_name": "处刑台", "rarity": "excellent", + "cap": 1, "desc": "敌血量≤30%:主角对其伤害 +2", "triggers": [ { @@ -773,6 +798,7 @@ rows = [ "feat_id": "field_dressing", "display_name": "战地包扎", "rarity": "common", + "cap": 1, "desc": "战斗胜利:回复 3 生命", "triggers": [ { @@ -788,6 +814,7 @@ rows = [ "feat_id": "blood_tax", "display_name": "血税", "rarity": "rare", + "cap": 1, "desc": "本局每触一次陷阱,最大生命 −2;每封印命中一次,攻击 +1(下一战起生效)", "triggers": [ { @@ -812,6 +839,7 @@ rows = [ "feat_id": "venom_edge", "display_name": "淬毒刃纹", "rarity": "rare", + "cap": 1, "desc": "克制命中追加 +2 伤害", "triggers": [ { @@ -834,6 +862,7 @@ rows = [ "feat_id": "seal_burst", "display_name": "封印爆裂", "rarity": "rare", + "cap": 1, "desc": "封印命中:全体存活敌 3 点群伤", "triggers": [ { @@ -850,6 +879,7 @@ rows = [ "feat_id": "perfect_insurance", "display_name": "完美保险", "rarity": "rare", + "cap": 1, "desc": "本场首次封印落空,不计入完美封印判定", "triggers": [ { @@ -891,6 +921,7 @@ rows = [ "display_name": "省力周转", "rarity": "excellent", "cap": 1, + "cap": 1, "desc": "刷新半价:弃图刷新成本为基线 50%", "triggers": [ { @@ -908,6 +939,7 @@ rows = [ "display_name": "崩壁冲击", "rarity": "excellent", "cap": 1, + "cap": 1, "desc": "刷后群伤:成功刷新后全体存活敌各受 2 点", "triggers": [ { @@ -925,6 +957,7 @@ rows = [ "display_name": "矿道红利", "rarity": "rare", "cap": 1, + "cap": 1, "desc": "刷后GP:每次成功刷新固定 +1 GP", "triggers": [ { @@ -941,6 +974,7 @@ rows = [ "display_name": "石层缓冲", "rarity": "common", "cap": 1, + "cap": 1, "desc": "当轮减伤:本轮成功刷新后,每次受伤固定 −2(多刷不叠加)", "triggers": [ { @@ -973,6 +1007,7 @@ rows = [ "display_name": "蓄力周转", "rarity": "excellent", "cap": 1, + "cap": 1, "desc": "刷后蓄力:每次刷新 +1 层(上限 3),下一次攻击消费全部层数", "triggers": [ { @@ -1029,6 +1064,7 @@ rows = [ "display_name": "坑道小憩", "rarity": "common", "cap": 1, + "cap": 1, "desc": "当轮恢复:每次刷新累计 1 点(上限 3),本轮轮末合计回复", "triggers": [ { diff --git a/game/data/tables/level_gen_params.tres b/game/data/tables/level_gen_params.tres index 2aa43701..6245764b 100644 --- a/game/data/tables/level_gen_params.tres +++ b/game/data/tables/level_gen_params.tres @@ -16,7 +16,10 @@ rows = [{ "exit_count": 1, "battle_floor": 4, "fork_ways_min": 2, -"fork_ways_max": 3 +"fork_ways_max": 3, +"fork_third_way_pct": 35, +"fill_rest_first_pct": 50, +"width_max": 4 }, { "level_index": 2, "layers": 9, @@ -29,7 +32,10 @@ rows = [{ "exit_count": 2, "battle_floor": 6, "fork_ways_min": 2, -"fork_ways_max": 3 +"fork_ways_max": 3, +"fork_third_way_pct": 35, +"fill_rest_first_pct": 50, +"width_max": 4 }, { "level_index": 3, "layers": 13, @@ -42,7 +48,10 @@ rows = [{ "exit_count": 3, "battle_floor": 8, "fork_ways_min": 2, -"fork_ways_max": 3 +"fork_ways_max": 3, +"fork_third_way_pct": 35, +"fill_rest_first_pct": 50, +"width_max": 4 }] ; 节点图生成器核心批 卡① §8.1 参数档 provisional 取值表逐行填列(定值批落终值,数值全 provisional)。 diff --git a/game/data/tables/meta_tree.tres b/game/data/tables/meta_tree.tres index e91f16f5..27e4d049 100644 --- a/game/data/tables/meta_tree.tres +++ b/game/data/tables/meta_tree.tres @@ -113,10 +113,14 @@ rows = [{ "name_a": "先手压制", "name_b": "稳健拓荒", "effects_a": { - "posture_first_strike": 1 + "posture_first_strike": { + "ap_bonus": 1 + } }, "effects_b": { - "posture_bulwark": 1 + "posture_bulwark": { + "shield": 1 + } } }, { "node_id": "fork_sense", @@ -131,10 +135,16 @@ rows = [{ "name_a": "锋", "name_b": "守", "effects_a": { - "sense_sharp": 1 + "sense_sharp": { + "halve_pct": 50, + "cap_per_level": 2 + } }, "effects_b": { - "sense_guard": 1 + "sense_guard": { + "ap_refund": 1, + "cap_per_level": 2 + } } }, { "node_id": "fork_economy", @@ -149,10 +159,14 @@ rows = [{ "name_a": "聚", "name_b": "散", "effects_a": { - "economy_hoard": 1 + "economy_hoard": { + "mult_pct": 150 + } }, "effects_b": { - "economy_spend": 1 + "economy_spend": { + "discount_pct": 80 + } } }] diff --git a/game/data/tables/shop.tres b/game/data/tables/shop.tres index 60b6f6a1..c6afa528 100644 --- a/game/data/tables/shop.tres +++ b/game/data/tables/shop.tres @@ -4,6 +4,7 @@ [resource] script = ExtResource("1") +shelf_size = 3 gp_drops = [{ "tier": "normal", "display_name": "普通战斗", @@ -93,7 +94,6 @@ pool_shelf = [{ price_a = 1.8 price_b = 1.1 relic_base_price = 15.0 -spend_discount = 0.8 ; GP+商店房实施批(2026-09-20 用户拍板,设计权威 design/research/proposal-gp-shop-room-2026-09-20.md 裁决记录): ; 掉落=均值±方差掷定(gp_drop 封闭流)+关级加值;精英行为预留行(当前无消费方,随精英节点内容批启用); diff --git a/game/data/tables/variation.tres b/game/data/tables/variation.tres index 5f7b2636..d3bc68b6 100644 --- a/game/data/tables/variation.tres +++ b/game/data/tables/variation.tres @@ -7,6 +7,7 @@ script = ExtResource("1") percent_of_cells = 5 per_level_bonus = 0 pack_max = 4 +pack_min = 2 rows = [{ "variant_id": "thorn", "display_name": "荆棘丛", diff --git a/game/presentation/main.gd b/game/presentation/main.gd index 41c9713c..051ecd60 100644 --- a/game/presentation/main.gd +++ b/game/presentation/main.gd @@ -182,7 +182,7 @@ func _wire_adventure() -> void: # 冒险开局参数面构造(主菜单开新/续玩两入口共用:同调三键随开局下传。 # 取值口径与营地投影完全同源:装配集=save.attunement_equipped(save_manager.gd 访问器)、Y 刷新池= -# meta.attune_refresh_charges(当前等级行表值)、树 rule 键集=meta.tree_rule_keys(Fork 激活侧 effects); +# meta.attune_refresh_charges(当前等级行表值)、树规则参数字典=meta.tree_rules(Fork 激活侧 effects_*); # hero_override 沿用既有 meta 现算口径。static 形态=冒烟直呼单测本函数,免实例化整菜单场景)。 # 两根类型轴随开局下传(attack_type/armor_type——契约 2 所有权移交+契约 8 冒险开始快照; # 取值=meta.hero_type_axes()=存档选中路线类型轴(attack/armor 两键),未解锁/异常选中态回退锻造配置默认值)。 @@ -195,7 +195,7 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref: "armor_type": meta_ref.hero_type_axes()["armor"] if meta_ref != null else "", "attunement": save_manager_ref.meta_state.attune_state().equipped if save_manager_ref != null else [], "attune_refresh_y": meta_ref.attune_refresh_charges() if meta_ref != null else 0, - "tree_rules": meta_ref.tree_rule_keys() if meta_ref != null else [], + "tree_rules": meta_ref.tree_rules() if meta_ref != null else {}, } params.merge(extra, true) return params @@ -630,10 +630,10 @@ func _on_refresh_confirmed() -> void: var result := "rejected_rap" if rap < cost else "rejected_cap" logger.log_ux("ux_refresh_attempt", {"result": result, "round_refresh_index": _refresh_used_snapshot() + 1}) if result == "rejected_rap": - ui.battle.board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 8)" % (cost - rap), "warn") + ui.battle.board_zone.toast.show_toast("RAP 不足 · 还差 %d 点(弃图刷新需 %d)" % [cost - rap, cost], "warn") ui.battle.board_zone.ap_bar.pulse_rap() else: - ui.battle.board_zone.toast.show_toast("本轮刷新次数已用完(每轮 2 次)", "warn") + ui.battle.board_zone.toast.show_toast("本轮刷新次数已用完(每轮 %d 次)" % int(db.run.action_economy.refresh_cap_per_round), "warn") ui.battle.board_zone.board_view.refresh() _refresh_presentations() diff --git a/game/presentation/playtest_logger.gd b/game/presentation/playtest_logger.gd index 4182051f..b8fb64b4 100644 --- a/game/presentation/playtest_logger.gd +++ b/game/presentation/playtest_logger.gd @@ -13,12 +13,15 @@ var _seed := 0 var _recent_steps: Array[Dictionary] = [] # 环形缓冲:最近结算行 var _recent_actions: Array[Dictionary] = [] # 环形缓冲:最近玩家动作 var _snap_count := 0 -var ap_per_round := 8 # 组合根注入(AP 消耗口径分母——行动经济 无推送信号) +var ap_per_round: int = 0 # 必填注入(零缺省——组合根 start_session 前显式设定,守卫拒未注入) var _ux_inputs := 0 # 本结算轮原始输入动作数(log_action 计数,快照后归零) # 开启会话(每次 start_level/start_battle 调用——按种子分文件)。返回日志路径。 func start_session(seed_val: int) -> String: + if ap_per_round < 1 or ap_per_round > 32: + push_error("[试玩日志器] ap_per_round 未注入或越域: %d——组合根必填注入" % ap_per_round) + return "" _seed = seed_val _recent_steps.clear() _recent_actions.clear() diff --git a/game/sim/adventure_monte_carlo.gd b/game/sim/adventure_monte_carlo.gd index 64277218..a669679d 100644 --- a/game/sim/adventure_monte_carlo.gd +++ b/game/sim/adventure_monte_carlo.gd @@ -47,6 +47,11 @@ func _initialize() -> void: var feat_mode := "off" if args.size() >= 7 and args[6] in ["off", "formed", "ab_a", "ab_b", "rules"]: feat_mode = args[6] + var db = load("res://core/config_db.gd").new() + if not db.load_tables("res://data/tables", false): + print("表加载失败") + quit(1) + return var feat_profile := {} if feat_mode == "formed": # 成型画像=五事件族覆盖 14 件 @@ -58,16 +63,11 @@ func _initialize() -> void: "last_drop", "market_close", "execution_scholar"]} elif feat_mode == "ab_a": - feat_profile = {"mode": "off", "tree_rules": ["posture_first_strike"]} + feat_profile = {"mode": "off", "tree_rules": _tree_rule_dict(db, ["posture_first_strike"])} elif feat_mode == "ab_b": - feat_profile = {"mode": "off", "tree_rules": ["posture_bulwark"]} + feat_profile = {"mode": "off", "tree_rules": _tree_rule_dict(db, ["posture_bulwark"])} elif feat_mode == "rules": - feat_profile = {"mode": "off", "tree_rules": Array(String(args[7] if args.size() >= 8 else "").split(",", false))} # V19——任意键集配对注入(Array 包裹防 PackedStringArray 被 adventure_runner is Array 守卫整组丢弃) - var db = load("res://core/config_db.gd").new() - if not db.load_tables("res://data/tables", false): - print("表加载失败") - quit(1) - return + feat_profile = {"mode": "off", "tree_rules": _tree_rule_dict(db, Array(String(args[7] if args.size() >= 8 else "").split(",", false)))} # V19——任意键集配对注入(规则键参数字典形态) var attune_profile := {} if attune_mode == "on": # 贪心画像:Slots=3(槽位判定源=树同调位节点点亮数,模拟画像=三节点 @@ -153,3 +153,21 @@ func _initialize() -> void: print("BATCH %s N=%d 终局计数=%s 日志=%s" % [policy, n, JSON.stringify(tally), log_path]) print("(口径注:零加成 = atk/hp 天赋格百分比均 0;关间大奖励(HP 上限提升+回满)按冒险结构设计自动兑付,未额外剥离 · 全程日志见 user://logs/ · 总耗时 %.1f 秒)" % [float(Time.get_ticks_msec() - t0) / 1000.0]) quit(0) + + +## 树规则参数字典构造(A/B 面注入单点——扫 meta_tree Fork 行 effects_a/b,命中 keys 集的键取其 +## 参数字典原样透传;db 参鸭子型沿 board_generator 先例——sim 面不在 S9 扫描面) +static func _tree_rule_dict(db, keys: Array[Variant]) -> Dictionary[Variant, Variant]: + var out: Dictionary[Variant, Variant] = {} + var keyset := {} + for k in keys: + keyset[String(k)] = true + for row in db.crossrun.meta_tree.rows: + if String(row.get("kind", "")) != "fork": + continue + for skey in ["effects_a", "effects_b"]: + var fx: Dictionary = row.get(skey, {}) as Dictionary + for rk in fx.keys(): + if keyset.has(String(rk)) and not out.has(String(rk)): + out[String(rk)] = fx[rk] + return out diff --git a/game/sim/auto_player.gd b/game/sim/auto_player.gd index a2d3b5b6..377dd8cd 100644 --- a/game/sim/auto_player.gd +++ b/game/sim/auto_player.gd @@ -146,6 +146,16 @@ static func _deduce(minesweeper) -> Dictionary: if not mines.has(c): mines[c] = true changed = true + # 0 格连锁算子(架构整改批补齐——与棋盘生成 _apply_chain_rule 对位):safe 集中未揭示格 + # 挖开必触发 0 格连锁(连锁只经安全格扩散=闭包集全安全,真实玩家挖 c 免费获得的信息面), + # 经 preview_reveal 真实闭包取新开格集并入 safe——双口径显式登记:unknown 不含 safe_set oracle + # 〔口径差异=board_generator 版含 oracle,本版 no-peek——:80-83 注释在账〕 + for c in (safe.keys() as Array).duplicate(): + if board.is_safe(c) and not minesweeper.is_revealed(c): + for rc in minesweeper.preview_reveal(c): + if not safe.has(rc): + safe[rc] = true + changed = true return {"safe": _row_major(safe), "mines": _row_major(mines)} diff --git a/game/sim/battle_driver.gd b/game/sim/battle_driver.gd index 9e0a908a..5081ac47 100644 --- a/game/sim/battle_driver.gd +++ b/game/sim/battle_driver.gd @@ -11,26 +11,84 @@ const AutoPlayer := preload("res://sim/auto_player.gd") const GUARD_ACTIONS := 400 # 单轮动作步防挂死上限(被收口探针原值逐一同值) -# 驱动一整轮挖掘。still_running = 调用方战斗活性谓词(缺省恒真): +# 驱动一整轮(驱动循环单点——通用签名)。still_running = 调用方战斗活性谓词(缺省恒真): # monte_carlo / skill_rhythm 传 `func(): return out.result == ""`(battle_finished 已发的轮内不再动手), # damage_probe 关卡驱动传 runner 绑定 + battle_result 谓词,battle 级探针传空即可。 -static func drive_round(settlement, still_running: Callable = Callable()) -> void: +# act_provider 契约(策略回调——func(settlement) -> Dictionary): +# {"kind":"stop"}/空字典 = 停手出环;单步 {"kind":"move"|"dig"|"seal"|"refresh","cell":V}(refresh 无 cell); +# 合步 {"kind":"move","cell":X,"next":{"kind":"dig","cell":Y}}——move 提交后查相位/轮次、变更即断弃 next。 +# on_action 契约(日志回调——func(submit_kind:String, cell:Vector2i, ok:bool, round_index:int)): +# submit_kind ∈ {move, dig, seal_place, refresh, stop}——轮末统一 stop 也经此回调(accept 取 outcomes[0].accepted); +# 空 Callable = 不记日志。 +# max_rejects:策略回调被拒连续上限(≤0 = 不设防——缺省形态零防线语义保持);ok 清零否则累加。 +# 缺省两参形态(act_provider 空)= AutoPlayer.choose_dig 原语义逐位:guard 400、异格先移、合步 dig、 +# 移位后相位/轮次离场断弃 next、轮末 stop——四调用方(dump_battle/monte_carlo/damage_probe/skill_rhythm)两参零改动。 +static func drive_round(settlement, still_running: Callable = Callable(), + act_provider: Callable = Callable(), on_action: Callable = Callable(), + max_rejects: int = 0) -> void: var start_round: int = settlement.round_index var minesweeper = settlement.minesweeper var guard := GUARD_ACTIONS + var rejects := 0 + var using_provider: bool = not act_provider.is_null() while _alive(still_running) and settlement.action_economy.phase == "Active" \ - and settlement.round_index == start_round and guard > 0: + and settlement.round_index == start_round and guard > 0 \ + and (max_rejects <= 0 or rejects < max_rejects): guard -= 1 - var plan := AutoPlayer.choose_dig(minesweeper) - if plan.is_empty(): + var act: Dictionary = act_provider.call(settlement) if using_provider else _default_act(minesweeper) + var kind := String(act.get("kind", "stop")) + if kind == "stop" or act.is_empty(): break - if plan.hero_cell != minesweeper.hero_pos(): - minesweeper.settle_batch([{"action": "move", "cell": plan.hero_cell}]) - if settlement.action_economy.phase != "Active" or settlement.round_index != start_round: - break - minesweeper.settle_batch([{"action": "dig", "cell": plan.dig_cell}]) + if _submit_act(settlement, act, on_action, start_round): + rejects = 0 + else: + rejects += 1 if _alive(still_running) and settlement.action_economy.phase == "Active" and settlement.round_index == start_round: - minesweeper.settle_batch([{"action": "stop"}]) + var out_s: Array = settlement.minesweeper.settle_batch([{"action": "stop"}]) + _on(on_action, "stop", Vector2i(-1, -1), _accepted(out_s), start_round) + + +# 缺省策略(AutoPlayer.choose_dig 原生 plan 专用):同位直接 dig、异位先移合步 next=dig +static func _default_act(minesweeper) -> Dictionary: + var plan := AutoPlayer.choose_dig(minesweeper) + if plan.is_empty(): + return {"kind": "stop"} + if plan.hero_cell == minesweeper.hero_pos(): + return {"kind": "dig", "cell": plan.dig_cell} + return {"kind": "move", "cell": plan.hero_cell, "next": {"kind": "dig", "cell": plan.dig_cell}} + + +# 单步提交分派(move/dig/seal→settle_batch——seal 提交动作名 seal_place;refresh→player_request_refresh; +# 合步 move→next 递归提交——相位/轮次离场断弃,与原内联逐位同) +static func _submit_act(settlement, act: Dictionary, on_action: Callable, ri: int) -> bool: + var kind := String(act.get("kind", "stop")) + var cell: Vector2i = act.get("cell", Vector2i(-1, -1)) + var ok := false + if kind == "refresh": + ok = bool(settlement.player_request_refresh()) + _on(on_action, "refresh", Vector2i(-1, -1), ok, ri) + return ok + var action_name := "seal_place" if kind == "seal" else kind + var out_s: Array = settlement.minesweeper.settle_batch([{"action": action_name, "cell": cell}]) + ok = _accepted(out_s) + _on(on_action, action_name, cell, ok, ri) + if kind == "move": + var next: Dictionary = act.get("next", {}) + if next.is_empty(): + return ok + if settlement.action_economy.phase != "Active" or settlement.round_index != ri: + return ok # 移位后相位/轮次离场——断弃 next + _submit_act(settlement, next, on_action, ri) + return ok + + +static func _on(on_action: Callable, kind: String, cell: Vector2i, ok: bool, ri: int) -> void: + if not on_action.is_null(): + on_action.call(kind, cell, ok, ri) + + +static func _accepted(outcomes: Array) -> bool: + return not outcomes.is_empty() and bool(outcomes[0].accepted) static func _alive(still_running: Callable) -> bool: diff --git a/game/sim/calibrate_boards.gd b/game/sim/calibrate_boards.gd index 71daea40..86c04a8c 100644 --- a/game/sim/calibrate_boards.gd +++ b/game/sim/calibrate_boards.gd @@ -11,6 +11,7 @@ extends SceneTree class FakeDB: class _FakeBoardGen: var row: Dictionary + var spawn_edge: int = 0 # 表头成员(generate 直索引面——真值由 _initialize 逐行注入) func resolve_row(_L: int, _D: int) -> Dictionary: return row class _FakeCombat: @@ -42,9 +43,11 @@ func _initialize() -> void: # 初始展开收窄两参数透传(.get 缺省与生成器防御口径一致) "shave_ring_radius": int(row.get("shave_ring_radius", 1)), "r0_max_cap": int(row.get("r0_max_cap", 0)), + "spawn_edge": int(db.combat.board_gen.spawn_edge), # 出生区边长透传(读真表头、零字面量——组E FakeDB 模式不动) } var fdb := FakeDB.new() fdb.row = rd + fdb.combat.board_gen.spawn_edge = int(db.combat.board_gen.spawn_edge) # 表头注入(零字面量——读真表头) var mines: Array = [] var safes: Array = [] var r0s: Array = [] diff --git a/game/sim/experiment_density.gd b/game/sim/experiment_density.gd deleted file mode 100644 index 26879000..00000000 --- a/game/sim/experiment_density.gd +++ /dev/null @@ -1,72 +0,0 @@ -# VERTICAL SLICE - NOT FOR PRODUCTION -# 一次性实验:不同 mine_density 下的 R₀ 占比 / 幸存雷数 / 残余挖掘量 / 推理率——为切片临时调参提供数据。 -extends SceneTree - -func _initialize() -> void: - var db = load("res://core/config_db.gd").new() - db.load_tables("res://data/tables", true) - var board_generator = load("res://core/board_generator.gd").new() - print("=== 密度实验(L1 10×10 target0.88 / L2 12×12 target0.90,各 8 种子)===") - print("d\t|M|/N\tR₀\tR₀/|S|\t残余挖掘\tinf\tatt") - for d in [0.22, 0.25, 0.28, 0.30, 0.33]: - for cfg in [[10, 10, 0.88, 50], [12, 12, 0.90, 60]]: - var w: int = cfg[0] - var h: int = cfg[1] - var t: float = cfg[2] - var ms: int = cfg[3] - var agg := {"m": 0, "r0": 0, "rem": 0, "inf": 0.0, "att": 0, "n": 0} - for s in range(8): - var row := { - "level_index": 1, "node_depth": 0, "board_width": w, "board_height": h, - "mine_density": d, "inferability_target": t, "solver_rules": "single_subset", - "min_safe_count": ms, "max_gen_attempts": 16, - "board_type": "dungeon", - } - _patch_row(row, d) - var params := _params_from(row) - var rg := RandomNumberGenerator.new() - rg.seed = 1000 + s - var b = board_generator.generate(1, 0, rg, _db(db, row)) - if b == null: - continue - agg.m += b.mines.size() - agg.r0 += int(b.metrics.r0_count) - agg.rem += b.safe_cells().size() - int(b.metrics.r0_count) - agg.inf += float(b.metrics.inferability) - agg.att += int(b.metrics.attempts_used) - agg.n += 1 - if agg.n > 0: - var n := float(agg.n) - var n_placed: int = int(round(d * float(w * h - 9))) - print("%.2f %dx%d\t%d/%d\t%d\t%.2f\t%d\t%.3f\t%.1f" % [d, w, h, int(agg.m / n + 0.5), n_placed, int(agg.r0 / n + 0.5), agg.r0 / n / (w * h - int(agg.m / n + 0.5)), int(agg.rem / n + 0.5), agg.inf / n, agg.att / n]) - quit(0) - -# --- 简易 db 替身:只供给本实验的行 --- -var _row_override: Dictionary = {} - -func _patch_row(row: Dictionary, _d: float) -> void: - _row_override = row - -func _params_from(row: Dictionary) -> Dictionary: - return { - "L": 1, "D": 0, "w": int(row.board_width), "h": int(row.board_height), - "density": float(row.mine_density), "target": float(row.inferability_target), - "solver_rules": "single_subset", "min_safe": int(row.min_safe_count), - "max_attempts": int(row.max_gen_attempts), "board_type": "dungeon", - } - -class FakeDB: - class _FakeBoardGen: - var row: Dictionary - func resolve_row(_L: int, _D: int) -> Dictionary: - return row - class _FakeCombat: - var board_gen := _FakeBoardGen.new() - var combat := _FakeCombat.new() - func status_effect_row(_id: String) -> Dictionary: - return {} - -func _db(_real, row: Dictionary): - var f := FakeDB.new() - f.combat.board_gen.row = row - return f diff --git a/game/sim/experiment_density.gd.uid b/game/sim/experiment_density.gd.uid deleted file mode 100644 index 87cad61f..00000000 --- a/game/sim/experiment_density.gd.uid +++ /dev/null @@ -1 +0,0 @@ -uid://cg7jkrsc03rar diff --git a/game/sim/level_autoplay.gd b/game/sim/level_autoplay.gd index 60f23e6b..13f34021 100644 --- a/game/sim/level_autoplay.gd +++ b/game/sim/level_autoplay.gd @@ -20,9 +20,8 @@ # 运行前提:db 已 load_tables;run_level/run_adventure 均须全新 RngService(同种子 ⇒ 全程逐步可回放)。 # 实现注:信号处理体全部为静态方法 + bind 传状态——GDScript 多行 lambda 内嵌 match/控制流的解析不可靠。 extends RefCounted - const AutoPlayer := preload("res://sim/auto_player.gd") # 自动玩家策略单一实现 - +const BattleDriver := preload("res://sim/battle_driver.gd") # 庚案收敛:驱动循环单点(策略仍归 _decide_act 单点分派) # ── 普通档 v3 失误注入参数(踩雷期望=完美+2/板、每板少挖几个正确格)── # 标雷→误挖转化率:主踩雷源=卡死赌挖(不确定性概率命中),误挖只作偶发真手滑—— # 真人几乎不挖自己封印的格(p 过高会让首轮已证封印变保底命中,非失误形态) @@ -32,14 +31,12 @@ const NORMAL_SKIP_DIG_P := 0.05 # 空闲提前赌挖率:已证安全格在手时小概率先赌一格——完美系把不确定性攒到残局 # (残局 hazard→1=扫雷),真人早赌滚信息(低 hazard 期吃掉不确定性);p×~40 安全 AP/板 ≈ 4 次 const NORMAL_IDLE_GAMBLE_P := 0.10 - - static func run_level(db, seed_val: int, level_index: int, atk_pct: int, hp_pct: int, collect_rows: bool = false, logger = null, policy: String = "normal", route: String = "min") -> Dictionary: var own_session := logger == null if own_session: logger = load("res://presentation/playtest_logger.gd").new() + logger.ap_per_round = int(db.run.action_economy.ap_per_round) # 注入序前置(守卫拒后开——R5 必填) logger.start_session(seed_val) - logger.ap_per_round = int(db.run.action_economy.ap_per_round) logger.log_run({"seed": seed_val, "level_index": level_index, "atk_pct": atk_pct, "hp_pct": hp_pct, "policy": policy, "route": route}) var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(seed_val) @@ -77,8 +74,6 @@ static func run_level(db, seed_val: int, level_index: int, atk_pct: int, hp_pct: if own_session: logger.close_session() return st - - # 单关驱动段(run_level 抽取——零自动推进停点 + 战斗自动玩家;policy 经 st 入 _autoplay_battle) # 入口停点(node_index 尚 -1 = 进节点前无 current_node)也经本分支驱动—— # 读 next_candidate_ids 候选(入口单候选)→ pick_next_node,同一 _enter_node 等价(零 RNG 漂移); @@ -100,8 +95,6 @@ static func _drive_level(runner, st: Dictionary, logger, policy: String, route: if this_settlement == null: break # level_finished 已发(settlement 置 null)而信号尚未置 result 的防御 _autoplay_battle(runner, this_settlement, st, logger) - - # 三关连探冒险驱动——AdventureRunner 编排:主种子恰一次 # (start_adventure 唯一 new_adventure 调用点)、大奖励立即确认、冒险终局置 result/summary。 # 返回结构 = run_level 同族 + summary / wallet_events / encounter_max_hp_seq / reward_events / @@ -116,8 +109,8 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ var own_session := logger == null if own_session: logger = load("res://presentation/playtest_logger.gd").new() + logger.ap_per_round = int(db.run.action_economy.ap_per_round) # 注入序前置(守卫拒后开——R5 必填) logger.start_session(seed_val) - logger.ap_per_round = int(db.run.action_economy.ap_per_round) logger.log_run({"seed": seed_val, "adventure": true, "atk_pct": atk_pct, "hp_pct": hp_pct, "policy": policy, "route": route, "hero_override": hero_override}) var rng = load("res://core/rng_service.gd").new() @@ -137,7 +130,6 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ "seal_placed": 0, "seal_hit": 0, "seal_miss": 0, "seal_hit_gain": 0, "seal_miss_penalty": 0, "prng": _policy_rng(seed_val), "mine_slips": 0, "skip_digs": 0, "shop_buys": 0, "adv": true, # v3 档位行为开关(选路评分/失误注入/商店购买只挂冒险测量面——单关夹具语义不变) - "level_maps": [], # 逐关节点图标识(生成图 = 关卡序号+本局主种子——MC 分层出数) } # 信号直连(拆件批 T3):runner 信号在 level_started 重取点重连(转发族退役——重取点前移到本回调) @@ -158,9 +150,9 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ # 特性注入(formed 画像=开局注入七流派各 2 件档一成型集;测量面专用) if relic.has("seed_ids"): adv_params["relics_seed"] = relic.get("seed_ids", []) - # 树规则键注入(A/B 面读数载体:fork 激活侧 effects rule 键集透传) + # 树规则参数字典注入(A/B 面读数载体:fork 激活侧 effects_* 参数字典透传) if relic.has("tree_rules"): - adv_params["tree_rules"] = relic.get("tree_rules", []) + adv_params["tree_rules"] = relic.get("tree_rules", {}) if relic.get("tree_rules", {}) is Dictionary else {} # 商店分层注入(缺省 legacy 与既有调用方逐位一致零行为变更;pool=共享池货架面) var pool_shop: bool = String(shop.get("mode", "")) == "pool" if pool_shop: @@ -189,8 +181,6 @@ static func run_adventure(db, seed_val: int, atk_pct: int, hp_pct: int, collect_ if own_session: logger.close_session() return st - - # 每关开始(autoplay 直连面):重取 runner 并重连三信号(恰好一次——同关守卫: # level_started 每关恰一次发射,bind Callable 每次新建故以关卡号守卫防重入) static func _on_autoplay_level_started(level_index: int, _display_name: String, adr, st: Dictionary, collect_rows: bool, logger) -> void: @@ -203,8 +193,6 @@ static func _on_autoplay_level_started(level_index: int, _display_name: String, r.step_row.connect(_on_step_row.bind(adr, st, collect_rows)) r.node_entered.connect(_on_adventure_node_entered.bind(adr, st, logger)) r.wallet_changed.connect(_on_adventure_wallet.bind(st, logger)) - - # 冒险模式节点采集(_on_node_entered 的 AdventureRunner 门面版——runner 逐关更替,经 adr.runner() 重取; # encounter_max_hp_seq 抓 effective_max_hp:node_entered 先于 begin_battle,encounter 尚为空字典, # 该值 = begin_battle 将收到 hero_max_hp 同一表达式〔规划书抓取点与代码事实冲突,按代码为准已登记〕) @@ -215,7 +203,6 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, if not st.has("level_maps"): st["level_maps"] = [] st["level_maps"].append("%d#%d" % [int(r.level_row.get("level_index", 0)), int(adr.master_seed)]) # MC 分层出数(关卡×生成图标识) - if t == "battle" or t == "boss": st.battles += 1 st.last_battle_enemies = [] # 末场战斗敌方名序列按战斗重置(失败局终值 = 死亡遭遇) @@ -259,8 +246,6 @@ static func _on_adventure_node_entered(node: Dictionary, info: Dictionary, adr, r.continue_after_node() if logger != null: logger.log_node(node, info, r.node_index) - - # 冒险钱包事件采集(wallet 行族——GP 跨关延续与冒险收束 clear 的验收取数面;同日志合规转发) static func _on_adventure_wallet(event: String, gp_val: int, extra: Dictionary, st: Dictionary, logger) -> void: st.wallet_events.append({"event": String(event), "gp": int(gp_val), @@ -268,8 +253,6 @@ static func _on_adventure_wallet(event: String, gp_val: int, extra: Dictionary, "source": String(extra.get("source", ""))}) if logger != null: logger.log_wallet(event, gp_val, extra) - - # 三选一停点驱动(确定性策略;TP0 起 316-f 判读:1 级最稀有、同档并列取最左; # 奖励事件随 reward_events 留档) static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> void: @@ -283,8 +266,6 @@ static func _on_adventure_bonus(payload: Dictionary, st: Dictionary, adr) -> voi var pick: Dictionary = _pick_token(candidates, String(st.get("feat_mode", "off"))) if not adr.stop().pick_curio(String(pick.get("kind", "")), String(pick.get("tier", "")), String(pick.get("feat_id", ""))): st.result = "advance_failed" - - # 三选一确定性选件(TP0 316-f——1 级优先度=最稀有〔四级:legendary>rare>excellent>common〕、2 级=最左; # 候选并列同档取最左=稳定序保证。空候选/未知档位 = 回退最左防御口径。 # 成型画像(relic 模式 on:候选含 relic 令牌时优先在 relic 集内按 316-f 择一 @@ -302,22 +283,11 @@ static func _pick_token(candidates: Array, feat_mode: String = "off") -> Diction var best_rank := -1 for tok in pool: var t: Dictionary = tok as Dictionary - var rank := 0 - match String(t.get("tier", "")): - "legendary": - rank = 4 - "rare": - rank = 3 - "excellent": - rank = 2 - "common": - rank = 1 + var rank: int = int(TreasureItemTable.TIER_RANK.get(String(t.get("tier", "")), 0)) # 档位排序读表(未知档 0=原 match 落空同语义) if best.is_empty() or rank > best_rank: best = t best_rank = rank return best - - # 三选一兑付完成 → 上下文分派(关卡完成停点=推进下一关 / 奖励节点=回图选路) static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr) -> void: st.reward_events.append({"curio_resolved": result.duplicate(true)}) @@ -328,8 +298,6 @@ static func _on_adventure_curio_resolved(result: Dictionary, st: Dictionary, adr return if not adr.continue_to_next_level(): st.result = "advance_failed" - - # 商店贪心买齐:GP 局内死钱——买得起就买( affordable 集里挑最便宜的 # 反复买到达不上任何商品为止,guard 防死循环)。确定性(零随机流);购买出账/加算区入账全走 # runner.shop.buy_product 核心编排(与呈现层购买同一出账路径,wallet_changed 信号照常采集)。 @@ -350,8 +318,6 @@ static func _autoplay_shop(r, st: Dictionary) -> void: if not r.shop.buy_product(String(pick.get("product_id", ""))): return st.shop_buys = int(st.get("shop_buys", 0)) + 1 - - # 商店共享池贪心:货架三选一同构面贪心买齐——affordable 集挑最便宜反复买 # (每次买后全重抽、货架随买随换,枯竭/不可负担即止,guard 防死循环)。购买走 runner.shop.buy_pool_item # 核心编排(与呈现层购买同一出账+池消费路径,wallet_changed 信号照常采集)。确定性(零随机流)。 @@ -372,24 +338,18 @@ static func _autoplay_shop_pool(r, st: Dictionary) -> void: if not r.shop.buy_pool_item(String(pick.get("kind_id", ""))): return st.shop_buys = int(st.get("shop_buys", 0)) + 1 - - # 冒险终局(completed/failed/abandoned——result 载 summary.result,外层驱动循环随之收敛) static func _on_adventure_finished(summary: Dictionary, st: Dictionary, logger) -> void: st.summary = summary.duplicate(true) st.result = String(summary.get("result", "")) if logger != null: logger.log_level_end(summary) - - # 策略流 RNG:种子派生自局种子 × 黄金比率常数 + 偏移——与核心统一流 # (rng_service 单盐派生)零共享零扰动;逐局确定性保持(同种子 ⇒ 同决策序列 ⇒ 同回放) static func _policy_rng(seed_val: int) -> RandomNumberGenerator: var prng := RandomNumberGenerator.new() prng.seed = seed_val * 2654435761 + 912931 return prng - - # 路径轴选路(目标价值评分制):候选按节点类型评分取最高, # 同分 tie-break 沿 route 方向(min=小句柄/max=大句柄——verifier 双窗轴保持)。 # 评分语义(确定性,零随机流):rest=伤重高值(缺口比例×100)/shop=买得起高值(GP 局内死钱, @@ -438,8 +398,6 @@ static func _route_pick(runner, route: String, st: Dictionary) -> int: if better: best = c return int(best.id) - - static func _on_step_row(row: Dictionary, _runner, st: Dictionary, collect_rows: bool) -> void: if row.type == "damage" and row.source_class == "enemy_action": st.hero_dmg += int(row.value) @@ -470,8 +428,6 @@ static func _on_step_row(row: Dictionary, _runner, st: Dictionary, collect_rows: st.seal_miss_penalty += int(row.value) if collect_rows: st.rows.append(row) - - # 敌方显示名采集:敌方行动/施放行文本首词 = 单位显示名 #(攻击行「地精 撕咬(暴击)」、施放行「地精 施放 毒雾」——显示名不含空格,去重连续重复) static func _note_enemy(text: String, st: Dictionary) -> void: @@ -483,8 +439,6 @@ static func _note_enemy(text: String, st: Dictionary) -> void: var arr: Array = st.last_battle_enemies if arr.is_empty() or String(arr[arr.size() - 1]) != ename: arr.append(ename) - - static func _on_node_entered(node: Dictionary, info: Dictionary, runner, st: Dictionary, logger) -> void: var t := String(node.get("type", "")) if t == "battle" or t == "boss": @@ -506,8 +460,6 @@ static func _on_node_entered(node: Dictionary, info: Dictionary, runner, st: Dic # 不掺购买行为;冒险口径(run_adventure)已在 _on_adventure_node_entered 改贪心买齐 if logger != null: logger.log_node(node, info, runner.node_index) - - static func _on_level_finished(p: Dictionary, st: Dictionary, logger) -> void: st.result = String(p.get("result", "")) st.hp_left = int(p.get("hp", 0)) @@ -516,8 +468,6 @@ static func _on_level_finished(p: Dictionary, st: Dictionary, logger) -> void: st.defeat_node_type = String(p.get("node_type", "")) if logger != null: logger.log_level_end(p) - - static func _autoplay_battle(runner, settlement, st: Dictionary, logger) -> void: # 策略档经 _decide_act 单点分派(单循环承载三档——三档决策语义逐位不变,见 _decide_act)。 # 普通策略档 v3:完美策略 + 受控失误注入(标定依据=真人实证: @@ -534,40 +484,27 @@ static func _autoplay_battle(runner, settlement, st: Dictionary, logger) -> void round_guard -= 1 st.rounds += 1 var start_round: int = settlement.round_index - var action_guard := 400 - var rejects := 0 - while runner.current_battle() == settlement and settlement.battle_result == "" and settlement.action_economy.phase == "Active" \ - and settlement.round_index == start_round and action_guard > 0 and rejects < 3: - action_guard -= 1 - var act: Dictionary = _decide_act(st, settlement) - var kind := String(act.get("kind", "stop")) - if kind == "stop": - break - var ok := false - if kind == "move": - ok = _submit_intent(settlement, "move", act.get("cell"), logger, start_round) - elif kind == "dig": - ok = _submit_intent(settlement, "dig", act.get("cell"), logger, start_round) - elif kind == "seal": - ok = _submit_intent(settlement, "seal_place", act.get("cell"), logger, start_round) - elif kind == "refresh": - ok = bool(settlement.player_request_refresh()) - if logger != null: - logger.log_action("refresh", Vector2i(-1, -1), ok, {"round": start_round}) - if ok: - if kind == "refresh": - st.refreshes = int(st.refreshes) + 1 - elif kind == "seal": - st.seal_placed = int(st.get("seal_placed", 0)) + 1 # 封印数(η 分母=全部封印数〔Q5〕;失误注入转化封印不计数=转化后实为挖) - rejects = 0 - else: - rejects += 1 - if runner.current_battle() == settlement and settlement.battle_result == "" and settlement.action_economy.phase == "Active" and settlement.round_index == start_round: - var out_s: Array = settlement.minesweeper.settle_batch([{"action": "stop"}]) - if logger != null: - logger.log_action("stop", Vector2i(-1, -1), _accepted(out_s), {"round": start_round}) + # 内层单轮循环委托 BattleDriver.drive_round(驱动循环单点——庚案收敛:本类禁再持第二轮驱动循环; + # 策略分派/失误注入全保留在 _decide_act 原地,日志与计数经 _action_logger 回调承载) + BattleDriver.drive_round(settlement, + func() -> bool: return runner.current_battle() == settlement and settlement.battle_result == "" and settlement.action_economy.phase == "Active" and settlement.round_index == start_round, + _act_provider(st), _action_logger(st, logger), 3) +# 策略回调适配器(drive_round act_provider 契约——三档分派 perfect/novice/slip 全在 _decide_act 原地) +static func _act_provider(st: Dictionary) -> Callable: + return func(s) -> Dictionary: return _decide_act(st, s) + + +# 日志回调适配器(drive_round on_action 契约——log_action 带轮号 + refresh/seal 计数语义逐位同原内联) +static func _action_logger(st: Dictionary, logger) -> Callable: + return func(kind: String, cell: Vector2i, ok: bool, ri: int) -> void: + if logger != null: + logger.log_action(kind, cell, ok, {"round": ri}) + if ok and kind == "refresh": + st.refreshes = int(st.refreshes) + 1 + elif ok and kind == "seal_place": + st.seal_placed = int(st.get("seal_placed", 0)) + 1 # 封印数(η 分母=全部封印数〔Q5〕;失误注入转化封印不计数=转化后实为挖) # 普通档 v3 失误注入:三类—— # ①标雷误挖:把「封印已证雷」动作直接转成「挖该格」(真人手滑原型:知道是雷、位置点错, # 踩雷吃伤且该雷奖励报废);期望踩雷增量 ≈ 每板已证雷数 × NORMAL_MINE_SLIP_P(目标完美+2/板) @@ -598,8 +535,6 @@ static func _apply_normal_slips(act: Dictionary, settlement, st: Dictionary) -> if not plan.is_empty(): return _gamble_action(plan, settlement) return act - - # 赌挖动作组装(v3.4 抽出):目标邻接=直接挖,否则先移到目标的已开邻格(下一拍再挖) static func _gamble_action(plan: Dictionary, settlement) -> Dictionary: var ms = settlement.minesweeper @@ -609,8 +544,6 @@ static func _gamble_action(plan: Dictionary, settlement) -> Dictionary: if maxi(d.x, d.y) == 1: return {"kind": "dig", "cell": target} return {"kind": "move", "cell": plan.hero_cell} - - # 战斗决策单行分派: # perfect 档 = 完美策略单步决策(上界口径); # normal 档 v3 = 完美+受控失误注入(冒险测量面 adv=true);单关机制夹具保持 v2 新手画像(adv 缺省)。 @@ -621,15 +554,3 @@ static func _decide_act(st: Dictionary, settlement) -> Dictionary: if bool(st.get("adv", false)): return _apply_normal_slips(AutoPlayer.perfect_action(settlement.minesweeper, settlement), settlement, st) # v3:完美+失误 return AutoPlayer.novice_action(settlement.minesweeper) # 单关机制夹具:v2 新手画像保持 - - -static func _submit_intent(settlement, action: String, cell, logger, round_index: int) -> bool: - var out: Array = settlement.minesweeper.settle_batch([{"action": action, "cell": cell}]) - if logger != null: - logger.log_action(action, cell, _accepted(out), {"round": round_index}) - return _accepted(out) - - -static func _accepted(outcomes: Array) -> bool: - return not outcomes.is_empty() and bool(outcomes[0].accepted) - diff --git a/game/tests/golden/README.md b/game/tests/golden/README.md index 05913c31..51cb1287 100644 --- a/game/tests/golden/README.md +++ b/game/tests/golden/README.md @@ -1,7 +1,12 @@ # 视觉 golden 基线(渲染闸的比对基准) -五张基线:棋盘三图(`board_L1_dungeon` / `board_L2_underdark` / `board_L3_deep_underdark`, -裁剪区 900×760)+ 启动态菜单(`menu_boot`,全屏)+ 关卡画面(`level_map`,全屏)。 +基线**按平台各存**(2026-11-03 用户裁:跨显卡/字形渲染机制差异使跨平台逐像素必失配—— +`windows/` 与 `mac/` 子目录各持一套,平台内部照常严格比对,跨平台不互比; +平台选择实现单点 = `tools/gate_platform.sh` 的 `gate_platform_id`,比较器 `tools/golden_compare.py` +自动拼平台子目录)。本目录现持:`windows/`(棋盘三图 `board_L1_dungeon` / `board_L2_underdark` / +`board_L3_deep_underdark`,裁剪区 900×760 + 启动态菜单 `menu_boot`,全屏)、 +`level_map`(关卡画面全屏,历史基线留根——现行闸件未对拍它)、 +`palette_baseline/`(色板基线,纯文件内容比对与平台无关)。 比对判据与工具见 `tools/golden_compare.py` (逐像素灰度差 >16 的像素占比 ≤2%)。 diff --git a/game/tests/golden/board_L1_dungeon.png b/game/tests/golden/windows/board_L1_dungeon.png similarity index 100% rename from game/tests/golden/board_L1_dungeon.png rename to game/tests/golden/windows/board_L1_dungeon.png diff --git a/game/tests/golden/board_L2_underdark.png b/game/tests/golden/windows/board_L2_underdark.png similarity index 100% rename from game/tests/golden/board_L2_underdark.png rename to game/tests/golden/windows/board_L2_underdark.png diff --git a/game/tests/golden/board_L3_deep_underdark.png b/game/tests/golden/windows/board_L3_deep_underdark.png similarity index 100% rename from game/tests/golden/board_L3_deep_underdark.png rename to game/tests/golden/windows/board_L3_deep_underdark.png diff --git a/game/tests/golden/menu_boot.png b/game/tests/golden/windows/menu_boot.png similarity index 100% rename from game/tests/golden/menu_boot.png rename to game/tests/golden/windows/menu_boot.png diff --git a/game/tests/integration/cross_system_flow_test.gd b/game/tests/integration/cross_system_flow_test.gd index c4593ae5..165ae097 100644 --- a/game/tests/integration/cross_system_flow_test.gd +++ b/game/tests/integration/cross_system_flow_test.gd @@ -142,6 +142,7 @@ func _smoke_playtest_logger() -> void: rng.set_master_seed(20260912) var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng) var logger = load("res://presentation/playtest_logger.gd").new() + logger.ap_per_round = 8 # 必填注入(R5 守卫——夹具沿组合根表值 8) var path: String = logger.start_session(20260912) logger.attach_battle(settlement) var snaps: Array = [] diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 7b0744ac..49302252 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -188,6 +188,10 @@ func _suites() -> Array: {"path": "res://tests/unit/presentation/pacing_queue_test.gd", "expect": 0}, # AC⑤/T9——排空零分叉/零丢行/延迟双态 {"path": "res://tests/unit/presentation/settings_store_test.gd", "expect": 0}, # 减动效开关存储契约(持久化回读/缺省值/引擎动效档应用)〔断言补齐批〕 {"path": "res://tests/unit/battle_settlement/boss_gate_test.gd", "expect": 0}, # Boss 节点开战必选 Boss 行集(行类路由+端到端)〔断言补齐批〕 + {"path": "res://tests/unit/data_config/effect_kind_retired_test.gd", "expect": 1, "negative": true}, # 甲案词表退役——overflow/attack_segments 误写加载致命〔架构整改〕 + {"path": "res://tests/unit/core/playtest_logger_guard_test.gd", "expect": 0}, # R5 必填注入守卫——未注入拒绝开会话〔架构整改〕 + {"path": "res://tests/unit/core/battle_driver_drive_test.gd", "expect": 0}, # R12 驱动循环契约——缺省确定性/provider/rejects/合步〔架构整改〕 + {"path": "res://tests/unit/core/auto_player_deduce_test.gd", "expect": 0}, # R15 0 格连锁算子——远端格经真实闭包入 safe〔架构整改〕 {"path": "res://tests/unit/presentation/standee_fx_sweep_test.gd", "expect": 0}, # 呈现域缺陷扫掠(d2 蓄力档/d4 庆祝句柄/d5+d8 互斥/d12 站桩按 id 解析) {"path": "res://tests/unit/presentation/tune_sim_batch_parity_test.gd", "expect": 0}, # 测速循环批双实现对拍(帧索引/运行态/进度串/stats/报告五面——信号步进 vs await 基线) {"path": "res://tests/unit/presentation/standee_present_values_test.gd", "expect": 0}, # 战斗一屏样张批 V5–V12(血条危险红/名字行素底不压地台与提档 FS_HINT/徽标/护甲 PNG 绑定/素材入包/地台色合并退役) diff --git a/game/tests/static_scan.sh b/game/tests/static_scan.sh old mode 100644 new mode 100755 index e17aaa93..c1e8a1c1 --- a/game/tests/static_scan.sh +++ b/game/tests/static_scan.sh @@ -140,7 +140,7 @@ S11_TABLE="design/gdd/术语表.md" S11_FAIL_LOCAL=0 if [ ! -f "$S11_TABLE" ]; then - echo "[static_scan] S11 FAIL: 术语表文件不存在($S11_TABLE)" + echo "[static_scan] S11 FAIL: 术语表文件不存在(${S11_TABLE})" S11_FAIL_LOCAL=1 else # 解析词表:「退役词对照」第一列→去括号注→斜杠拆分 diff --git a/game/tests/unit/battle_settlement/effect_port_test.gd b/game/tests/unit/battle_settlement/effect_port_test.gd index 85a0afbb..acb419c8 100644 --- a/game/tests/unit/battle_settlement/effect_port_test.gd +++ b/game/tests/unit/battle_settlement/effect_port_test.gd @@ -33,7 +33,7 @@ func _init() -> void: func _enc(feats: Array) -> Dictionary: return {"level_index": 1, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp), - "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": [], + "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": {}, "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "boss", "adv_bucket": {}} @@ -113,7 +113,7 @@ func _test_biz_fact_face_closed() -> void: var ctrl = orch.fx_host().damage # 阳性对照(载荷携带事实——harness 能见命中):合成行独占订阅面(rebuild 持有 id 过滤—— # 只挂阳性行),board_flat_hero 真实单据流,base_value 足量避开伤害下限保底 - orch.fx_host().rebuild_subscriptions([pos_id], [], []) + orch.fx_host().rebuild_subscriptions([pos_id], [], {}) var hp0 := int(ctrl.units.hero_unit().get("hp", 0)) var t_a: Dictionary = ctrl.open_ticket(ctrl.KIND_BOARD_FLAT_HERO, "probe_port", "闭面探测甲", {"channel": "board", "base_value": 10, "target_rule": "hero", "ri": 1, "origin_cell": orch.minesweeper.hero_pos()}) @@ -121,7 +121,7 @@ func _test_biz_fact_face_closed() -> void: var hp1 := int(ctrl.units.hero_unit().get("hp", 0)) Kit.check("V3 阳性对照:hp_ratio 载荷 when 通过——承伤减量恰 1(10→9)", hp0 - hp1 == 9) # 阴性对照(闭面外事实——宿主链答而业务面不答):订阅面换代为阴性合成行独占,同路径零减量=F10 现状形态守卫 - orch.fx_host().rebuild_subscriptions([neg_id], [], []) + orch.fx_host().rebuild_subscriptions([neg_id], [], {}) var t_b: Dictionary = ctrl.open_ticket(ctrl.KIND_BOARD_FLAT_HERO, "probe_port", "闭面探测乙", {"channel": "board", "base_value": 10, "target_rule": "hero", "ri": 1, "origin_cell": orch.minesweeper.hero_pos()}) ctrl.flush_submit(t_b) diff --git a/game/tests/unit/battle_settlement/refresh_feats_test.gd b/game/tests/unit/battle_settlement/refresh_feats_test.gd index 2c8850c5..7b7932c9 100644 --- a/game/tests/unit/battle_settlement/refresh_feats_test.gd +++ b/game/tests/unit/battle_settlement/refresh_feats_test.gd @@ -48,7 +48,7 @@ func _init() -> void: func _install_dt_probe() -> void: if _db.crossrun.feats.row_by_id("probe_dt_counter").is_empty(): (_db.crossrun.feats.rows as Array).append({ - "feat_id": "probe_dt_counter", "display_name": "承伤计数探针", "rarity": "common", + "feat_id": "probe_dt_counter", "display_name": "承伤计数探针", "rarity": "common", "cap": 1, "desc": "测试夹具:每次主角承伤计数 +1", "triggers": [{"on": "damage_taken", "do": {"kind": "counter_add", "counter_id": "dt_probe_hits", "value": 1}}], }) @@ -62,7 +62,7 @@ func _orch(seed_v: int): func _enc(feats: Array, voucher_rap: int = 0, armor: int = 10, hero_hp: int = 200) -> Dictionary: return {"level_index": 1, "node_depth": 4, "hero_hp": hero_hp, "hero_atk": 8, "hero_max_hp": 200, - "hero_armor_class": armor, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": [], + "hero_armor_class": armor, "hero_ap_bonus": 0, "attunement": [], "feats": feats, "tree_rules": {}, "battle_index": 1, "voucher_rap": voucher_rap, "voucher_time_sand": 0, "node_type": "boss", "adv_bucket": {}} @@ -361,7 +361,7 @@ func test_no_feats_baseline() -> void: func _install_br_probe() -> void: if _db.crossrun.feats.row_by_id("probe_br_counter").is_empty(): (_db.crossrun.feats.rows as Array).append({ - "feat_id": "probe_br_counter", "display_name": "刷新事实探针", "rarity": "common", + "feat_id": "probe_br_counter", "display_name": "刷新事实探针", "rarity": "common", "cap": 1, "desc": "测试夹具:每次成功刷新计数 +1", "triggers": [{"on": "board_refreshed", "do": {"kind": "counter_add", "counter_id": "br_probe_hits", "value": 1}}], }) diff --git a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd index 1e55860d..c89a7401 100644 --- a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd +++ b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd @@ -421,7 +421,7 @@ func _smoke_settlement_segments() -> void: if decays != 1 or verdicts != 1: decay_verdict_ok = false # 衰减/判定每结算恰一次 Kit.check("每轮行相位单调(授予→增益/减益→战斗→衰减→判定)", order_ok) - # 案C 段内序:段内伤害序 = 主角先手 →〔连珠矢随先手段〕→ 敌方逐个 → 地图账殿后。 + # 案C 段内序:段内伤害序 = 主角先手 → 敌方逐个 → 地图账殿后。 # 地图账 = 全部棋盘来源伤害行 + 主角倒下不兑留痕行;违序(先于先手或插进敌方还手中间)即失败。 var mapdmg_ok := true for ri_key in by_round.keys(): @@ -440,13 +440,13 @@ func _smoke_settlement_segments() -> void: var rw: Dictionary = rows_r[i] var sc := String(rw.get("source_class", "")) var is_board_acct: bool = (String(rw.get("type", "")) == "damage" \ - and not ["hero_attack", "enemy_action", "fx_streak_segments"].has(sc)) \ + and not ["hero_attack", "enemy_action"].has(sc)) \ or String(rw.get("forfeit", "")) == "hero_down" if not is_board_acct: continue if (first_hero >= 0 and i < first_hero) or (last_enemy >= 0 and i < last_enemy): mapdmg_ok = false - Kit.check("案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账;连珠矢随先手段)", mapdmg_ok) + Kit.check("案C 段内序:地图伤害账殿后(主角先手→敌方逐个→地图账)", mapdmg_ok) Kit.check("衰减/判定段每结算恰一段(先兑付后衰减——含末轮)", decay_verdict_ok) @@ -995,7 +995,7 @@ func _smoke_counter_buckets() -> void: var RngS = load("res://core/rng_service.gd") var enc1 = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": ["twin_pick"], "tree_rules": [], "battle_index": 1, + "feats": ["twin_pick"], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle", "adv_bucket": led} var o1 = RigS.new(_db, _fresh_rng(779301)) @@ -1014,7 +1014,7 @@ func _smoke_counter_buckets() -> void: # V6——battle 桶随宿主销毁:第1战标错落 insurance_used(battle 桶无前缀),次战新编排器读 0。 var encA = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": ["perfect_insurance"], "tree_rules": [], "battle_index": 1, + "feats": ["perfect_insurance"], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle", "adv_bucket": {}} var oA = RigS.new(_db, _fresh_rng(779401)) @@ -1053,7 +1053,7 @@ func _smoke_extended_rows() -> void: var RigS = load("res://core/battle_orchestrator.gd") # 局部装配别名(_smoke_counter_buckets 同款——跨函数不共享局部量) var enc_fx = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": ["geologist"], "tree_rules": [], "battle_index": 1, + "feats": ["geologist"], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle", "adv_bucket": {}} # ② 兑付行:有未揭示安全格 → 掷格揭示成功 → 扩展行带真实格与 fx_reveal 身份 diff --git a/game/tests/unit/battle_settlement/trigger_cond_test.gd b/game/tests/unit/battle_settlement/trigger_cond_test.gd index 026b88d6..fc6eb2c7 100644 --- a/game/tests/unit/battle_settlement/trigger_cond_test.gd +++ b/game/tests/unit/battle_settlement/trigger_cond_test.gd @@ -113,7 +113,7 @@ func _init() -> void: var orch = RigS.new(db, _rng(700001)) Kit.check_quiet(orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], - "feats": [], "tree_rules": [], "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0, + "feats": [], "tree_rules": {}, "battle_index": 1, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle", "adv_bucket": {}}), "V6 前置:战斗受理") # facts 组包经真实摇号路径消费(mines 全零时条件技 sneak_ambush 不入集合——facts 四键在位的可观察面) Kit.check_quiet(int(orch._rounds.mines_hits["total"]) == 0 and int(orch._rounds.mines_hits["last"]) == 0 and int(orch._rounds.mines_hits["round"]) == 0, diff --git a/game/tests/unit/battle_settlement/variant_placement_test.gd b/game/tests/unit/battle_settlement/variant_placement_test.gd index 7ac34fce..415c86de 100644 --- a/game/tests/unit/battle_settlement/variant_placement_test.gd +++ b/game/tests/unit/battle_settlement/variant_placement_test.gd @@ -28,6 +28,7 @@ func _init() -> void: _smoke_variant_placement(1) _smoke_variant_placement(2) _smoke_global_variant() + _test_pack_min_from_table() quit(Kit.finish("variant_placement_test")) @@ -331,3 +332,36 @@ func _errs_has_cross_field(errs: Array) -> bool: # ——互锁负样本/门控三维/资源面偏斜守恒与合成序/快照投影不变)---- # 取号计数桩(variant_place 取号断言——子类拦截计数,派生与取数语义零改动) + + +# ---- 成包下界读表(架构整改——pack_min 表头单点)---- +# 观测面局限申报(实现期裁定,详见实现报告/对照表):pack_min 消费面=randi_range 下界(软目标—— +# 8 邻贴接禁令下生长早停为合法态〔board_installer 注释明示〕,候选池碎片化使「产物零 void: + # ① 静态:布置面零字面量下界(randi_range(2, 退役——下界读 pack_min 表头单点) + var src := FileAccess.get_file_as_string("res://core/board_installer.gd") + Kit.check("布置面零字面量下界(randi_range(2, 退役、下界读表单点在位)〔架构整改 V10·静态〕", + not src.contains("randi_range(2,") and src.contains("int(db.combat.variation.pack_min)")) + # ② 同种子改值产物互异(扫种子取首个差异局——真实入口 begin_battle 布置段) + var vt = _db.combat.variation + var saved_min: int = int(vt.pack_min) + var found_diff := false + for s in range(42, 102): + var sigs := {} + for pm in [3, 1]: + vt.pack_min = pm + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(s) + var settlement = load("res://core/battle_orchestrator.gd").new(_db, rng) + if not settlement.begin_battle({"level_index": 2, "node_depth": 0, "hero_hp": int(_db.combat.hero.max_hp)}): + continue + var b = settlement.minesweeper.current_board() + sigs[pm] = str(b.variants.keys()) + vt.pack_min = saved_min + if String(sigs.get(3, "")) != "" and String(sigs.get(3, "")) != String(sigs.get(1, "")): + found_diff = true + break + vt.pack_min = saved_min + Kit.check("同种子 pack_min=3 vs 1 布置产物互异(下界进 randi_range——改值可观测)〔架构整改 V10〕", found_diff) \ No newline at end of file diff --git a/game/tests/unit/core/adventure_feats_test.gd b/game/tests/unit/core/adventure_feats_test.gd index e362e441..6437559d 100644 --- a/game/tests/unit/core/adventure_feats_test.gd +++ b/game/tests/unit/core/adventure_feats_test.gd @@ -589,7 +589,7 @@ func _smoke_feats() -> void: var sv_c = h.get("set", null) EffectEngine.counter_add(orch.fx_host().counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0)) if sv_c == null else int(sv_c), sv_c != null) # 开格系 - fx.rebuild(["twin_pick"], [], []) + fx.rebuild(["twin_pick"], [], {}) for i in 4: var hits_dp: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_paid", {"ap_cost": 1}) var refund := false @@ -601,7 +601,7 @@ func _smoke_feats() -> void: Kit.check("双管镐:第 %d 次付费挖格返 1 AP(每第 2 次——mod_eq 预评估口径)" % (i + 1), refund) else: Kit.check("双管镐反例:第 %d 次不返(未满 2 次)" % (i + 1), not refund) - fx.rebuild(["geologist"], [], []) + fx.rebuild(["geologist"], [], {}) var geo_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"new_resource_type": true}) apply_fx.call(geo_hits) Kit.check("地质学家:首开新类型集 1 点〔专长批 V3〕", _hits_kind(geo_hits, "counter_add").size() == 1) @@ -611,7 +611,7 @@ func _smoke_feats() -> void: Kit.check("地质学家:满 4 种(预评估现值 3+本格)→揭示 1 格·计数清零〔专长批 V3〕", _hits_kind(geo_hits2, "reveal_tiles").size() == 1 and EffectEngine.counter(orch.fx_host().counters_ctx, "geo_types") == 0) var geo_hits3: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"new_resource_type": false}) Kit.check("地质学家反例:同类型重复首开不叠点〔专长批 V3〕", _hits_kind(geo_hits3, "counter_add").is_empty()) - fx.rebuild(["hard_hat"], [], []) + fx.rebuild(["hard_hat"], [], {}) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {})) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {})) Kit.check("安全帽:开安全格攒层 ×2〔专长批 V3〕", EffectEngine.counter(orch.fx_host().counters_ctx, "helm_layers") == 2) @@ -621,7 +621,7 @@ func _smoke_feats() -> void: if String(h.get("kind", "")) == "damage_reduce": hh_reduce = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", ""))) Kit.check("安全帽:踩雷耗层减伤=层值 2(一次清零随施加面 set 行)〔专长批 V3〕", hh_reduce == 2) - fx.rebuild(["skip_mining"], [], []) + fx.rebuild(["skip_mining"], [], {}) var sk_zero: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_blank_zero": true}) apply_fx.call(sk_zero) Kit.check("跳格采掘:空白 0 格攒 1 点〔专长批 V3〕", _hits_kind(sk_zero, "counter_add").size() == 1) @@ -630,24 +630,24 @@ func _smoke_feats() -> void: apply_fx.call(sk_full) Kit.check("跳格采掘:满 3 点→揭示 1 格〔专长批 V3〕", _hits_kind(sk_full, "reveal_tiles").size() == 1) Kit.check("跳格采掘反例:普通开格不计点〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_blank_zero": false}), "counter_add").is_empty()) - fx.rebuild(["blast_accounting"], [], []) + fx.rebuild(["blast_accounting"], [], {}) for i in 5: var ba_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_paid", {"ap_cost": 1}) for h in ba_hits: if String(h.get("kind", "")) == "counter_add": EffectEngine.counter_add(orch.fx_host().counters_ctx, String(h.get("counter_id", "")), 1) Kit.check("爆破会计:本轮挖格花费≥5→+1 GP〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "round_end", {}), "grant_gp").size() == 1) - fx.rebuild(["frontier_miner"], [], []) + fx.rebuild(["frontier_miner"], [], {}) Kit.check("边陲矿工:边缘格产出 +1·中心格反例零产出〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_edge_cell": true}), "grant_material").size() == 1 and _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {"is_edge_cell": false}), "grant_material").is_empty()) # 封印系 - fx.rebuild(["seal_echo"], [], []) + fx.rebuild(["seal_echo"], [], {}) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {})) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {})) var se_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "round_end", {}) Kit.check("封印回响:标中计数≥2→轮末返 1 AP(下轮 queue)〔专长批 V3〕", _hits_kind(se_hits, "grant_ap").size() == 1 and String((_hits_kind(se_hits, "grant_ap")[0] as Dictionary).get("mode", "")) == "queue") - fx.rebuild(["seal_smith"], [], []) + fx.rebuild(["seal_smith"], [], {}) var sm_remove := 0 for i in 3: var sm_dispatch: Array = fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}) @@ -656,14 +656,14 @@ func _smoke_feats() -> void: if String(h.get("kind", "")) == "remove_mines": sm_remove += 1 Kit.check("封印匠:标中每第 3 次→中立移除恰一次(频率闸死)〔专长批 V3〕", sm_remove == 1) - fx.rebuild(["seal_stake"], [], []) + fx.rebuild(["seal_stake"], [], {}) Kit.check("封印桩:标中本轮 AC +N(round 域——round_start 清零)〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "ac_bonus").size() == 1 and String((_hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "ac_bonus")[0] as Dictionary).get("mode", "")) == "round") - fx.rebuild(["warning_beacon"], [], []) + fx.rebuild(["warning_beacon"], [], {}) Kit.check("示警烟火:标中揭示 3×3 资源类型(不揭安危位置)〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "preview_area").size() == 1) # 踩雷/受伤系 - fx.rebuild(["pain_memory"], [], []) + fx.rebuild(["pain_memory"], [], {}) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {})) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {})) var pm_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) @@ -671,7 +671,7 @@ func _smoke_feats() -> void: for h in orch._hits_of(pm_hits, "attack_bonus"): pm_bonus = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", ""))) Kit.check("疼痛记忆:攻击附加层值伤害=层值 2·用后清零(set 行随施加面)〔专长批 V3〕", pm_bonus == 2) - fx.rebuild(["fuse_wire"], [], []) + fx.rebuild(["fuse_wire"], [], {}) var fw_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "damage_taken", {"kind": "terrain", "amount": 6}) var fw_shield := 0 for h in fw_hits: @@ -682,43 +682,43 @@ func _smoke_feats() -> void: if String(h.get("kind", "")) == "counter_add": EffectEngine.counter_add(orch.fx_host().counters_ctx, String(h.get("counter_id", "")), int(h.get("value", 0))) Kit.check("保险丝:首点不出盾(2 点才出——预评估口径)〔专长批 V3〕", fw_shield == 0) - fx.rebuild(["blood_insight"], [], []) + fx.rebuild(["blood_insight"], [], {}) Kit.check("见血开窍:踩雷揭示 1 格〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}), "reveal_tiles").size() == 1) - fx.rebuild(["trap_instinct"], [], []) + fx.rebuild(["trap_instinct"], [], {}) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {})) var ti_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}) var ti_val := 0 for h in orch._hits_of(ti_hits, "attack_bonus"): ti_val = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", ""))) Kit.check("陷阱本能:踩雷本轮攻击 +2(attack_bonus 通道——面板加算)〔专长批 V3〕", ti_val == 2) - fx.rebuild(["chain_traps", "pain_memory"], [], []) + fx.rebuild(["chain_traps", "pain_memory"], [], {}) apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {})) var ct_before: int = EffectEngine.counter(orch.fx_host().counters_ctx, "pain_layers") apply_fx.call(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {})) Kit.check("连环陷阱:同轮第 2 颗起每颗额外 +1 层(疼痛记忆同账)〔专长批 V3〕", EffectEngine.counter(orch.fx_host().counters_ctx, "pain_layers") == ct_before + 2) - fx.rebuild(["trap_burst"], [], []) + fx.rebuild(["trap_burst"], [], {}) var tb_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {"damage": 8}) Kit.check("陷阱爆发:全体敌雷伤×倍率直伤(blast_scaled——施加面值=damage×mult)〔专长批 V3〕", _hits_kind(tb_hits, "blast_scaled").size() == 1 and float((_hits_kind(tb_hits, "blast_scaled")[0] as Dictionary).get("mult", 0)) > 1.0) - fx.rebuild(["blood_honey"], [], []) + fx.rebuild(["blood_honey"], [], {}) Kit.check("血蜜:踩雷回复 N 生命〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}), "heal").size() == 1) - fx.rebuild(["stone_skin"], [], []) + fx.rebuild(["stone_skin"], [], {}) var ss_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1}) Kit.check("石肤:受伤 AC+1(本场域 cap 3——达上限不再涨由累积器封顶)〔专长批 V3〕", _hits_kind(ss_hits, "ac_bonus").size() == 1 and int((_hits_kind(ss_hits, "ac_bonus")[0] as Dictionary).get("cap", 0)) == 3) - fx.rebuild(["last_stand"], [], []) + fx.rebuild(["last_stand"], [], {}) Kit.check("背水:生命≤50% 减伤·过半血反例零减伤〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 1, "hp_ratio": 40}), "damage_reduce").size() == 1 and _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "damage_taken", {"kind": "enemy_skill", "amount": 5, "source": "s", "round": 2, "hp_ratio": 80}), "damage_reduce").is_empty()) # 跨族桥 - fx.rebuild(["breaker_strike"], [], []) + fx.rebuild(["breaker_strike"], [], {}) Kit.check("破封一击:挖开带封印危险格 +1 层·无封印反例零触发〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {"dug_under_seal": true}), "counter_add").size() == 1 and _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {"dug_under_seal": false}), "counter_add").is_empty()) # 节奏系 - fx.rebuild(["map_exchange"], [], []) + fx.rebuild(["map_exchange"], [], {}) var me_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "dig_stop", {"ap_left": 4}) apply_fx.call(me_hits) EffectEngine.counter_add(orch.fx_host().counters_ctx, "stop_swap", 4) # value_event="ap_left" 解析归施加面(action_economy 载荷取值)——冒烟手动落账 @@ -728,41 +728,41 @@ func _smoke_feats() -> void: me_atk = EffectEngine.counter(orch.fx_host().counters_ctx, String(h.get("value_counter", ""))) Kit.check("弃图兑换:折为攻击伤害=剩余 AP 全额·用后清零〔专长批 V3〕", me_atk == 4) Kit.check("弃图兑换反例:剩余 AP<3 不触发〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "dig_stop", {"ap_left": 2}), "counter_add").is_empty()) - fx.rebuild(["last_drop"], [], []) + fx.rebuild(["last_drop"], [], {}) var ld_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "round_start", {"round": 2, "ap_exhausted_last": true}) Kit.check("一滴不剩:上轮 AP 耗尽→+1 AP〔专长批 V3〕", _hits_kind(ld_hits, "grant_ap").size() == 1) Kit.check("一滴不剩反例:上轮有余不发〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "round_start", {"round": 3, "ap_exhausted_last": false}), "grant_ap").is_empty()) - fx.rebuild(["market_close"], [], []) + fx.rebuild(["market_close"], [], {}) for i in 8: for h in fx.dispatch(orch.fx_host().counters_ctx, "dig_reveal", {}): if String(h.get("kind", "")) == "counter_add": EffectEngine.counter_add(orch.fx_host().counters_ctx, String(h.get("counter_id", "")), 1) Kit.check("收盘:本轮开格≥8→+1 GP〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "round_end", {}), "grant_gp").size() == 1) # 战斗系 - fx.rebuild(["execution_scholar"], [], []) - Kit.check("斩首学者:击杀揭示 1 格〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "kill", {"target": "敌", "overflow": 0}), "reveal_tiles").size() == 1) - fx.rebuild(["executioner"], [], []) + fx.rebuild(["execution_scholar"], [], {}) + Kit.check("斩首学者:击杀揭示 1 格〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "kill", {"target": "敌"}), "reveal_tiles").size() == 1) + fx.rebuild(["executioner"], [], {}) var ex_hits: Array = fx.dispatch(orch.fx_host().counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 30, "is_boss": false}) Kit.check("处刑台:敌血量≤30% 挂单位标记加伤·阈值外反例零命中〔专长批 V3〕", _hits_kind(ex_hits, "target_attack_bonus").size() == 1 and _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "enemy_hp_threshold", {"unit": "e1", "pct": 60, "is_boss": false}), "target_attack_bonus").is_empty()) - fx.rebuild(["field_dressing"], [], []) + fx.rebuild(["field_dressing"], [], {}) Kit.check("战地包扎:战斗胜利回复 N 生命(battle_won 新挂点)〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "battle_won", {"round": 2}), "heal").size() == 1) # 契约尖点 - fx.rebuild(["blood_tax"], [], []) + fx.rebuild(["blood_tax"], [], {}) Kit.check("血税:双向同局账本(踩陷阱−2max_hp/封印命中+1atk——bonus_delta 战末汇入)〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "mine_hit", {}), "bonus_delta").size() == 1 and String((_hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "bonus_delta")[0] as Dictionary).get("axis", "")) == "atk") # 迁入 2 件 - fx.rebuild(["venom_edge"], [], []) + fx.rebuild(["venom_edge"], [], {}) Kit.check("淬毒刃纹(迁入):克制正面(magic_vs_heavy 矩阵值>0)追加·中性面反例零触发〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "clamp_check", {"matchup": "magic_vs_heavy"}), "modify_ticket").size() == 1 and _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "clamp_check", {"matchup": "normal_vs_light"}), "modify_ticket").is_empty()) - fx.rebuild(["seal_burst"], [], []) + fx.rebuild(["seal_burst"], [], {}) Kit.check("封印爆裂(迁入):标中全体敌群伤〔专长批 V3〕", _hits_kind(fx.dispatch(orch.fx_host().counters_ctx, "seal_hit", {}), "open_ticket").size() == 1) # 零持有 var orch0 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(74)) - orch0.fx_host().rebuild_subscriptions([], [], []) + orch0.fx_host().rebuild_subscriptions([], [], {}) Kit.check("零持有订阅=零命中(relic 关着零扰动——伤害管线既有行为逐位不变)", orch0.fx_host().fx_emit("attack_commit", {"atk": 10, "target": "敌", "matchup": "", "dig_streak": 0, "nest_count": 0}).is_empty() and orch0.fx_host().fx_emit("dig_reveal", {"cell": Vector2i(1, 1), "round": 1, "is_first_dig_of_battle": true, "terrain_kind": []}).is_empty() diff --git a/game/tests/unit/core/auto_player_deduce_test.gd b/game/tests/unit/core/auto_player_deduce_test.gd new file mode 100644 index 00000000..380e7865 --- /dev/null +++ b/game/tests/unit/core/auto_player_deduce_test.gd @@ -0,0 +1,70 @@ +# tests/unit/core/auto_player_deduce_test.gd — 模拟玩家 0 格连锁算子〔架构整改 V15〕 +# 断言面 = AutoPlayer._deduce 补齐后:safe 集含「与所有已开格 8 邻不接」的远端格(该形态格 +# single/subset 约束不可达——唯一来源=0 格连锁经 preview_reveal 真实闭包);零盲挖语义回归 +# (mines 集零安全格)。真实入口=AutoPlayer._deduce(真板真流——begin_battle 装配链)。 +# 运行:godot --headless --path game --script res://tests/unit/core/auto_player_deduce_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") +const AutoPlayer := preload("res://sim/auto_player.gd") +const BattleDriver := preload("res://sim/battle_driver.gd") + + +func _init() -> void: + _smoke_zero_chain_rule() + quit(Kit.finish("auto_player_deduce_test")) + + +func _smoke_zero_chain_rule() -> void: + var db = load("res://core/config_db.gd").new() + var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0 + Kit.check("前置:表加载零 error", load_ok) + if not load_ok: + return + var found := false + var found_seed := -1 + var remote_ok := false + var mines_clean := false + for s in range(42, 142): + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(s) + var settlement = load("res://core/battle_orchestrator.gd").new(db, rng) + if not settlement.begin_battle({"level_index": 2, "node_depth": 0, "hero_hp": int(db.combat.hero.max_hp)}): + continue + var ms = settlement.minesweeper + BattleDriver.drive_round(settlement) # 真实挖一轮(0 连锁实挖触发——信息面展开) + if ms.current_board() == null: + continue + var dd: Dictionary = AutoPlayer._deduce(ms) + var safe: Array = dd["safe"] + var mines: Array = dd["mines"] + # 远端格:safe 中与所有已开格 8 邻距离 >1(single/subset 约束只覆盖已开数字格邻域—— + # 此形态格唯一来源=0 格连锁算子) + var revealed: Array = ms.revealed_cells() + var remote := false + for rc_v in safe: + var rc: Vector2i = rc_v + var touches := false + for rv in revealed: + var d: Vector2i = rc - rv + if maxi(absi(d.x), absi(d.y)) <= 1: + touches = true + break + if not touches: + remote = true + break + # 零盲挖:mines 集全落真雷(板面雷集) + var board = ms.current_board() + var clean := true + for mv in mines: + if not board.is_mine(mv): + clean = false + if remote and clean: + found = true + found_seed = s + remote_ok = true + mines_clean = true + break + Kit.check("0 格连锁种子可达(远端 safe 形态在案,seed=%d)〔架构整改 V15〕" % found_seed, found) + Kit.check("远端格入 safe 集(桥接 0 区域传播——改前约束模型必缺)〔架构整改 V15〕", remote_ok) + Kit.check("零盲挖语义保持(mines 集零安全格)〔架构整改 V15〕", mines_clean) diff --git a/game/tests/unit/core/auto_player_deduce_test.gd.uid b/game/tests/unit/core/auto_player_deduce_test.gd.uid new file mode 100644 index 00000000..d7c3b2c4 --- /dev/null +++ b/game/tests/unit/core/auto_player_deduce_test.gd.uid @@ -0,0 +1 @@ +uid://cggeuptv8skhl diff --git a/game/tests/unit/core/battle_driver_drive_test.gd b/game/tests/unit/core/battle_driver_drive_test.gd new file mode 100644 index 00000000..43ba5e85 --- /dev/null +++ b/game/tests/unit/core/battle_driver_drive_test.gd @@ -0,0 +1,150 @@ +# tests/unit/core/battle_driver_drive_test.gd — drive_round 驱动循环契约〔架构整改 V13〕 +# 断言面 = BattleDriver.drive_round 通用签名四形态: +# ① 缺省两参(真编排器同种子两跑逐位——确定性自证;四调用方零改动回归由全套承担); +# ② provider 形态(桩 settlement——策略回调产出 dig/seal_place/refresh 各一、日志回调收四元组); +# ③ max_rejects(策略恒返被拒动作——第 3 拒后停手出环); +# ④ 合步 next(move 后相位变更即断弃 dig)。 +# 桩形态沿 seal_polarity_test 状态袋先例(手搭 settlement——引用步骤不复制代码)。 +# 运行:godot --headless --path game --script res://tests/unit/core/battle_driver_drive_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") +const BattleDriver := preload("res://sim/battle_driver.gd") + +const Vector2iArray := [] + + +class _StubEcon: + var phase := "Active" + + +class _StubMS: + var log: Array = [] + var accept := true + var reject_cells := {} + var on_submit: Callable = Callable() # 提交后钩子(④ 相位变更注入) + + func settle_batch(actions: Array) -> Array: + var out: Array = [] + for a in actions: + var cell: Vector2i = a.get("cell", Vector2i(-1, -1)) + var ok := accept and not reject_cells.has(cell) + log.append({"action": String(a.get("action", "")), "cell": cell, "ok": ok}) + out.append({"accepted": ok, "outcome": {}, "reject_reason": "" if ok else "stub_reject"}) + if on_submit.is_valid(): + on_submit.call(String(a.get("action", ""))) + return out + + +class _StubSettlement: + var round_index := 1 + var battle_result := "" + var action_economy := _StubEcon.new() + var minesweeper := _StubMS.new() + var refresh_calls := 0 + + func player_request_refresh() -> bool: + refresh_calls += 1 + return true + + +func _init() -> void: + _smoke_drive_round_contract() + quit(Kit.finish("battle_driver_drive_test")) + + +# ① 缺省两参(真编排器):同种子两跑整轮驱动逐位(确定性自证——动作序列快照经 cell_revealed 事件流) +func _smoke_drive_round_contract() -> void: + var db = load("res://core/config_db.gd").new() + var load_ok: bool = db.load_tables("res://data/tables", true) and db.diagnostics_count("error") == 0 + Kit.check("前置:表加载零 error", load_ok) + if not load_ok: + return + var sigs: Array = [] + for run in range(2): + var rng = load("res://core/rng_service.gd").new() + rng.set_master_seed(777) + var settlement = load("res://core/battle_orchestrator.gd").new(db, rng) + if not settlement.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": int(db.combat.hero.max_hp)}): + Kit.check("前置:开局受理(run %d)" % run, false) + return + var moves: Array = [] + settlement.minesweeper.cell_revealed.connect(func(evt): moves.append(str(evt.get("cell", "")) + ":" + String(evt.get("kind", "")))) + BattleDriver.drive_round(settlement) + sigs.append(str(moves)) + Kit.check("缺省两参:真编排器同种子两跑事件流逐位(驱动语义确定性自证)〔架构整改 V13〕", sigs[0] == sigs[1] and String(sigs[0]) != "") + _provider_shapes() + _max_rejects() + _combined_step_next() + + +# ② provider 形态:策略回调产出 dig/seal_place/refresh 各一——提交与日志回调四元组在案 +func _provider_shapes() -> void: + var st := _StubSettlement.new() + var script := [ {"kind": "dig", "cell": Vector2i(1, 1)}, {"kind": "seal", "cell": Vector2i(2, 2)}, {"kind": "refresh"}, {"kind": "stop"} ] + var logs: Array = [] + var provider := func(_s) -> Dictionary: + return script.pop_front() if script.size() > 0 else {"kind": "stop"} + var logger := func(kind: String, cell: Vector2i, ok: bool, ri: int) -> void: + logs.append({"kind": kind, "cell": cell, "ok": ok, "ri": ri}) + BattleDriver.drive_round(st, Callable(), provider, logger, 0) + Kit.check("provider 形态:dig 提交在案(桩日志 action=dig cell=(1,1))〔架构整改 V13〕", + logs.size() >= 1 and String(logs[0]["kind"]) == "dig" and logs[0]["cell"] == Vector2i(1, 1) and bool(logs[0]["ok"])) + Kit.check("provider 形态:seal 提交动作名 seal_place(提交分派换名)〔架构整改 V13〕", + logs.size() >= 2 and String(logs[1]["kind"]) == "seal_place" and logs[1]["cell"] == Vector2i(2, 2)) + Kit.check("provider 形态:refresh 经 player_request_refresh(计数面外——桩 refresh_calls=1)+回调四元组 round_index 同轮〔架构整改 V13〕", + st.refresh_calls == 1 and logs.size() >= 3 and String(logs[2]["kind"]) == "refresh" and int(logs[2]["ri"]) == 1) + Kit.check("provider 形态:轮末 stop 统一回调(submit_kind=stop)〔架构整改 V13〕", + logs.size() >= 4 and String(logs[logs.size() - 1]["kind"]) == "stop") + + +# ③ max_rejects:策略恒返被拒动作——第 3 拒后停手出环(循环次数=3) +func _max_rejects() -> void: + var st := _StubSettlement.new() + st.minesweeper.accept = false + st.minesweeper.reject_cells = {Vector2i(9, 9): true} + var provider := func(_s) -> Dictionary: + return {"kind": "dig", "cell": Vector2i(9, 9)} + var logs: Array = [] + var logger := func(kind: String, _cell: Vector2i, _ok: bool, _ri: int) -> void: + logs.append(kind) + BattleDriver.drive_round(st, Callable(), provider, logger, 3) + var dig_rejects := 0 + for k in logs: + if String(k) == "dig": + dig_rejects += 1 + Kit.check("max_rejects=3:第 3 拒后停手出环(被拒 dig 日志恰 3 行——轮末 stop 另行在案)〔架构整改 V13〕", dig_rejects == 3) + Kit.check("max_rejects=3:轮末 stop 回调在案〔架构整改 V13〕", logs.size() >= 1 and String(logs[logs.size() - 1]) == "stop") + + +# ④ 合步 next:move 后相位变更即断弃 dig(桩 on_submit 钩子把 phase 推离 Active) +func _combined_step_next() -> void: + var st := _StubSettlement.new() + st.minesweeper.on_submit = func(action: String) -> void: + if action == "move": + st.action_economy.phase = "Settling" # 移位触发结算相位变更 + var provider := func(_s) -> Dictionary: + return {"kind": "move", "cell": Vector2i(3, 3), "next": {"kind": "dig", "cell": Vector2i(3, 4)}} + BattleDriver.drive_round(st, Callable(), provider, Callable(), 0) + var dig_count := 0 + for entry in st.minesweeper.log: + if String(entry["action"]) == "dig": + dig_count += 1 + Kit.check("合步 next:move 后相位变更断弃 dig(提交日志零 dig——与原内联逐位同)〔架构整改 V13〕", dig_count == 0) + # 对照:相位不变时合步 dig 照常提交 + var st2 := _StubSettlement.new() + var fired := {"once": false} + var provider2 := func(_s) -> Dictionary: + if bool(fired["once"]): + return {"kind": "stop"} + fired["once"] = true + return {"kind": "move", "cell": Vector2i(3, 3), "next": {"kind": "dig", "cell": Vector2i(3, 4)}} + BattleDriver.drive_round(st2, Callable(), provider2, Callable(), 0) + var dig2 := 0 + var move2 := 0 + for entry in st2.minesweeper.log: + if String(entry["action"]) == "dig": + dig2 += 1 + if String(entry["action"]) == "move": + move2 += 1 + Kit.check("合步 next:相位不变时 dig 照常提交(move+dig 两行)〔架构整改 V13〕", dig2 == 1 and move2 == 1) diff --git a/game/tests/unit/core/battle_driver_drive_test.gd.uid b/game/tests/unit/core/battle_driver_drive_test.gd.uid new file mode 100644 index 00000000..f02efce4 --- /dev/null +++ b/game/tests/unit/core/battle_driver_drive_test.gd.uid @@ -0,0 +1 @@ +uid://dj6k6mmyk26pc diff --git a/game/tests/unit/core/board_gen_reward_test.gd b/game/tests/unit/core/board_gen_reward_test.gd index 441314fc..e7dd0c4f 100644 --- a/game/tests/unit/core/board_gen_reward_test.gd +++ b/game/tests/unit/core/board_gen_reward_test.gd @@ -130,6 +130,7 @@ func _smoke_reward_identity_unit() -> void: class _BoardRowStub: class _StubBoardGen: var row: Dictionary + var spawn_edge: int = 3 # 表头成员(generate 直索引面——现行边长 3) func resolve_row(_L: int, _D: int) -> Dictionary: return row class _StubCombat: @@ -200,22 +201,23 @@ func _smoke_board_generator() -> void: # ③ ring_removed 量级:≤ 域内候选危险格数(5×5−出生 9=16——无全域剃雷); # ④ 闸压线行为:cap=9(出生保障面下限)时出图必 r0≤9 或 accept_worst 非法标记(三分出口诚实降级,玩家永不卡死) var cap_val: int = int(row.get("r0_max_cap", 0)) - Kit.check("L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 9——板面 30%% 锚)" % [L, cap_val], - cap_val >= BoardData.SPAWN_AREA_CELLS) + var spawn_cells_n: int = int(_db.combat.board_gen.spawn_edge) * int(_db.combat.board_gen.spawn_edge) + Kit.check("L%d r0_max_cap 正式行投值在位(%d ≥ 出生保障面 %d——板面 30%% 锚)" % [L, cap_val, spawn_cells_n], + cap_val >= spawn_cells_n) Kit.check("L%d 结构断言 R₀=%d ≤ cap=%d(R₀≤cap 重掷闸承载)" % [L, int(b1.metrics.r0_count), cap_val], int(b1.metrics.r0_count) <= cap_val) Kit.check("L%d r0_cap_rejected 尝试事实指标在位(本图 %d 次触发)" % [L, int(b1.metrics.get("r0_cap_rejected", -1))], int(b1.metrics.get("r0_cap_rejected", -1)) >= 0) - Kit.check("L%d ring_removed=%d ≤ 剃雷域候选危险格数((3+2r)²−9——全域剃雷退役)" % [L, int(b1.metrics.get("ring_removed", 99))], - int(b1.metrics.get("ring_removed", 99)) <= (2 + int(row.get("shave_ring_radius", 1))) * (2 + int(row.get("shave_ring_radius", 1))) * 4 - 5) + Kit.check("L%d ring_removed=%d ≤ 剃雷域候选危险格数((edge+2r)²−edge²——全域剃雷退役)" % [L, int(b1.metrics.get("ring_removed", 99))], + int(b1.metrics.get("ring_removed", 99)) <= (int(_db.combat.board_gen.spawn_edge) + 2 * int(row.get("shave_ring_radius", 1))) * (int(_db.combat.board_gen.spawn_edge) + 2 * int(row.get("shave_ring_radius", 1))) - int(_db.combat.board_gen.spawn_edge) * int(_db.combat.board_gen.spawn_edge)) var row9: Dictionary = row.duplicate() - row9["r0_max_cap"] = BoardData.SPAWN_AREA_CELLS + row9["r0_max_cap"] = int(_db.combat.board_gen.spawn_edge) * int(_db.combat.board_gen.spawn_edge) # cap 压线=出生保障面(spawn_edge² 推导——语义 9 不减) row9["max_gen_attempts"] = 4 var stub9 := _BoardRowStub.new() stub9.combat.board_gen.row = row9 var b9 = board_generator.generate(1, 0, _mk_stream("board_gen", 909), stub9) - Kit.check("L1 cap 压线 9:出图 R₀=%d %s(三分出口诚实降级——超阈即 accept_worst 非法标记,玩家永不卡死)" % [int(b9.metrics.r0_count), "≤9" if int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS else ">9→illegal_product"], - b9 != null and (int(b9.metrics.r0_count) <= BoardData.SPAWN_AREA_CELLS or b9.illegal_product)) + Kit.check("L1 cap 压线 9:出图 R₀=%d %s(三分出口诚实降级——超阈即 accept_worst 非法标记,玩家永不卡死)" % [int(b9.metrics.r0_count), "≤9" if int(b9.metrics.r0_count) <= spawn_cells_n else ">9→illegal_product"], + b9 != null and (int(b9.metrics.r0_count) <= spawn_cells_n or b9.illegal_product)) Kit.check("L1 cap 压线出图指标集完整(accept_mode=%s / 闸触发 %d 次)" % [String(b9.metrics.get("accept_mode", "")), int(b9.metrics.get("r0_cap_rejected", 0))], int(b9.metrics.get("r0_cap_rejected", 0)) >= 0 and String(b9.metrics.get("accept_mode", "")) != "") diff --git a/game/tests/unit/core/board_generator_test.gd b/game/tests/unit/core/board_generator_test.gd index e4340644..4acd338d 100644 --- a/game/tests/unit/core/board_generator_test.gd +++ b/game/tests/unit/core/board_generator_test.gd @@ -6,6 +6,7 @@ extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") const BoardGeneratorScript := preload("res://core/board_generator.gd") +const BoardDataScript := preload("res://core/board_data.gd") const RngServiceScript := preload("res://core/rng_service.gd") var _db: ConfigDb @@ -19,6 +20,7 @@ func _init() -> void: return _test_generate_shape_and_mines() _test_target_mine_count_single_source() + _test_spawn_edge_from_table() _test_digits_equal_neighbor_mine_count() _test_same_seed_bit_identical() _test_clearable_and_inferability_selfconsistent() @@ -66,9 +68,11 @@ func _test_target_mine_count_single_source() -> void: var params := { "w": int(row.board_width), "h": int(row.board_height), "density": float(row.mine_density), + "spawn_edge": int(_db.combat.board_gen.spawn_edge), } var from_fn: int = BoardGeneratorScript.target_mine_count(params) - var from_expr: int = int(round(float(params.density) * float(params.w * params.h - BoardData.SPAWN_AREA_CELLS))) + var edge: int = int(params.spawn_edge) + var from_expr: int = int(round(float(params.density) * float(params.w * params.h - edge * edge))) checked += 1 if from_fn != from_expr: bad += 1 @@ -80,7 +84,8 @@ func _test_target_mine_count_single_source() -> void: var row1: Dictionary = _db.combat.board_gen.resolve_row(1, 0) Kit.check("产物断言读数 = target_mine_count 同源(metrics.target_mines)〔wd14〕", int(b.metrics.get("target_mines", -1)) == BoardGeneratorScript.target_mine_count({ - "w": int(row1.board_width), "h": int(row1.board_height), "density": float(row1.mine_density)})) + "w": int(row1.board_width), "h": int(row1.board_height), "density": float(row1.mine_density), + "spawn_edge": int(_db.combat.board_gen.spawn_edge)})) func _test_digits_equal_neighbor_mine_count() -> void: @@ -124,3 +129,36 @@ func _test_clearable_and_inferability_selfconsistent() -> void: var safe_total := b.width * b.height - b.mines.size() Kit.check("清图判定与安全格总数同在(安全格 %d 个,可清=%s)" % [safe_total, str(clearable)], safe_total > 0) + + +# ---- V8 出生区边长表单点(架构整改——spawn_edge 表头驱动布雷域基/几何/护栏全导出)---- + +func _test_spawn_edge_from_table() -> void: + # ① 纯函数对拍:edge=5 夹具 → target = round(d×(W×H−25))(25 格布雷域基可观测) + var p5 := {"w": 12, "h": 12, "density": 0.15, "spawn_edge": 5} + Kit.check("V8 纯函数:edge=5 布雷域基 25 格(target=round(d×(144−25)))〔架构整改 V8〕", + BoardGeneratorScript.target_mine_count(p5) == int(round(0.15 * (144 - 25)))) + # ② BoardData 几何:spawn_cells() 按 spawn_edge 导出(5→25 / 3→9) + var b5 = BoardDataScript.new() + b5.width = 12 + b5.height = 12 + b5.spawn_center = Vector2i(5, 5) + b5.spawn_edge = 5 + Kit.check("V8 几何:edge=5 → spawn_cells 25 格(in_bounds 过滤前满方)〔架构整改 V8〕", b5.spawn_cells().size() == 25) + var b3 = BoardDataScript.new() + b3.width = 12 + b3.height = 12 + b3.spawn_center = Vector2i(5, 5) + b3.spawn_edge = 3 + Kit.check("V8 几何:edge=3 → spawn_cells 9 格(与退役常量同值——逐位回归)〔架构整改 V8〕", b3.spawn_cells().size() == 9) + # ③ 真路径回归:真表头 spawn_edge=3 驱动出图(spawn_cells 9 格——生成器注入面) + var b := _generate(1, 0, 74000) + Kit.check("V8 真路径:真表头 edge=3 出图 spawn_cells 9 格〔架构整改 V8〕", b != null and b.spawn_cells().size() == 9) + # ④ 表头域校验:edge=5 时 r0_max_cap=9 低于出生保障面 25 → validate 拒(护栏读表头) + var tbl = load("res://data/schemas/board_gen_table.gd").new() + tbl.spawn_edge = 5 + var probe_row := {"level_index": 1, "node_depth": 0, "board_width": 12, "board_height": 12, "mine_density": 0.15, + "inferability_target": 0.95, "solver_rules": "single_subset", "min_safe_count": 3, "max_gen_attempts": 8, + "board_type": "dungeon", "shave_ring_radius": 1, "r0_max_cap": 9} + tbl.rows.append(probe_row) # typed Array[Dictionary]——append 面 + Kit.check("V8 护栏:edge=5 表头下 r0_cap=9 低于出生保障面 25 被拒〔架构整改 V8〕", not tbl.validate().is_empty()) diff --git a/game/tests/unit/core/effect_words_t1_test.gd b/game/tests/unit/core/effect_words_t1_test.gd index 12a17964..0b1a196b 100644 --- a/game/tests/unit/core/effect_words_t1_test.gd +++ b/game/tests/unit/core/effect_words_t1_test.gd @@ -59,7 +59,7 @@ func _init() -> void: var fx = EffectEngineScript.new(null) var ctx_f := EffectContext.new() var hits: Array = [] - fx.rebuild([], [], []) + fx.rebuild([], [], {}) fx._push_trigger("test", "v1_row", {"on": "dig_stop", "when": [{"fact": "ap_left", "op": "gte", "value": 3}], "do": {"kind": "counter_add", "counter_id": "stop_swap", "value_event": "ap_left"}}, {}) @@ -73,7 +73,7 @@ func _init() -> void: # ⑤ mod_eq 算子(双管镐落法——预评估口径每第 2 次判定;battle 桶无前缀) var fx2 = EffectEngineScript.new(null) var ctx2 := EffectContext.new() - fx2.rebuild([], [], []) + fx2.rebuild([], [], {}) fx2._push_trigger("test", "mod_row", {"on": "dig_paid", "when": [{"fact": "counter", "id": "dbl_digs", "op": "mod_eq", "value": 2, "ref": 1}], "do": {"kind": "grant_ap", "value": 1}}, {}) @@ -108,7 +108,7 @@ func _init() -> void: # ⑦ V4——前缀归桶清零/消耗语义(round_ 桶随 round_start 整桶清空;battle 无前缀计数跨 round_start 存活) var fx5 = EffectEngineScript.new(null) var ctx5 := EffectContext.new() - fx5.rebuild([], [], []) + fx5.rebuild([], [], {}) fx5._push_trigger("test", "round_row", {"on": "mine_hit", "do": {"kind": "counter_add", "counter_id": "round_mines", "value": 1}}, {}) for h in fx5.dispatch(ctx5, "mine_hit", {}): @@ -126,7 +126,7 @@ func _init() -> void: # battle 桶对照:无前缀 helm_layers 不受 round_start 影响(跨 round_start 存活) var fx6 = EffectEngineScript.new(null) var ctx6 := EffectContext.new() - fx6.rebuild([], [], []) + fx6.rebuild([], [], {}) fx6._push_trigger("test", "battle_row", {"on": "mine_hit", "do": {"kind": "counter_add", "counter_id": "helm_layers", "value": 1}}, {}) for h in fx6.dispatch(ctx6, "mine_hit", {}): diff --git a/game/tests/unit/core/feat_vouchers_test.gd b/game/tests/unit/core/feat_vouchers_test.gd index c17b8881..04839ab6 100644 --- a/game/tests/unit/core/feat_vouchers_test.gd +++ b/game/tests/unit/core/feat_vouchers_test.gd @@ -165,7 +165,7 @@ func _init() -> void: var orch = RigScript.new(db, rig_rng) Kit.check_quiet(orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": ["perfect_insurance"], "tree_rules": ["sense_guard"], "battle_index": 1, + "feats": ["perfect_insurance"], "tree_rules": {"sense_guard": {"ap_refund": 1, "cap_per_level": 2}}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}), "保险 rig:战斗启动(feats=perfect_insurance 与 tree_rules=sense_guard 双订阅 seal_missed)〔卡② R2〕") @@ -215,7 +215,7 @@ func _init() -> void: var orch2 = RigScript.new(db, rig_rng2) Kit.check_quiet(orch2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": ["perfect_insurance"], "tree_rules": [], "battle_index": 2, + "feats": ["perfect_insurance"], "tree_rules": {}, "battle_index": 2, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}), "次战 rig:第二战编排启动(feats 同件)〔卡② R2〕") @@ -240,7 +240,7 @@ func _init() -> void: o10.step_row.connect(func(row): rows10.append(row)) Kit.check_quiet(o10.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": [], "tree_rules": [], "battle_index": 1, + "feats": [], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}), "A10 rig:无保险战斗启动〔卡① N7〕") @@ -267,7 +267,7 @@ func _init() -> void: o11.step_row.connect(func(row): rows11.append(row)) Kit.check_quiet(o11.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": ["perfect_insurance"], "tree_rules": [], "battle_index": 1, + "feats": ["perfect_insurance"], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}), "A11 rig:带保险战斗启动〔卡① N7〕") diff --git a/game/tests/unit/core/level_shop_economy_test.gd b/game/tests/unit/core/level_shop_economy_test.gd index 45069651..c3b91d75 100644 --- a/game/tests/unit/core/level_shop_economy_test.gd +++ b/game/tests/unit/core/level_shop_economy_test.gd @@ -251,6 +251,7 @@ func _init() -> void: _smoke_shop_graph() _smoke_multi_terminal_settlement() _smoke_shop_pool() + _test_shelf_size_from_table() _test_shop_node_entered_payload() _smoke_generated_coverage() quit(Kit.finish("level_shop_economy_test")) @@ -261,7 +262,6 @@ func _init() -> void: func _smoke_table_migration() -> void: print("--- 数值入表(shop 两 knob + hero 显示名 · 原值迁移)---") Kit.check("shop.relic_base_price 表值 == 迁移前字面量(15.0)〔wd11〕", _db.run.shop.relic_base_price == 15.0) - Kit.check("shop.spend_discount 表值 == 迁移前字面量(0.8)〔wd11〕", _db.run.shop.spend_discount == 0.8) Kit.check("hero.display_name 表值 == 迁移前字面量(探险者)〔wd16〕", _db.combat.hero.display_name == "探险者") # 消费端读表:spawn 真实构造链的英雄单位显示名与表值同 var units = load("res://core/unit_container.gd").new(_db) @@ -1554,3 +1554,24 @@ func _has_err_category(errs: Array, category: String) -> bool: # ⑦ 实装探针(妨害技蛛网束缚 + 毒爆必中附加分量,实装面 = L2 Boss 卓尔): # 停手轮驱动 (2,9)——零挖掘零封印 → 零池,伤害行可逐拍精确复算;种子 hunt 至少各命一粒毒爆伤害行 # 与蛛网束缚施放行;毒分量随开战掷定的攻击基线缩放(atk 区间 2–4 → rider 1–3)。 + + +# ---- 货架位数读表(架构整改——shelf_size 表头单点:进店与重抽同源)---- + +func _test_shelf_size_from_table() -> void: + var autoplay = load("res://sim/level_autoplay.gd") + var st = _db.run.shop + var saved: int = int(st.shelf_size) + st.shelf_size = 5 # 运行时表头改值夹具(测毕还原——真表行值零触碰) + var got5 := -1 + for s in range(42, 92): + var a9 = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(s)) + a9.set_hero_override(900, 400) + if not a9.start_adventure(s, {"shop_mode": "pool"}): + continue + if not _adv_drive_to_shop(a9, _adv_st(), autoplay): + continue + got5 = (a9.runner().shop.shop_shelf() as Array).size() + break + st.shelf_size = saved + Kit.check("夹具 shelf_size=5 → 货架五件可观测(实测 %d;真表值 3 回归=既有「进店抽 3」断言承载)〔架构整改 V9〕" % got5, got5 == 5) diff --git a/game/tests/unit/core/meta_progression_forge_test.gd b/game/tests/unit/core/meta_progression_forge_test.gd index 28a8b9ae..7048c521 100644 --- a/game/tests/unit/core/meta_progression_forge_test.gd +++ b/game/tests/unit/core/meta_progression_forge_test.gd @@ -314,7 +314,7 @@ func _meta_settle_and_workshop() -> void: sv_e.meta_state.set_attune_refresh_level(2) sv_e.meta_state.set_fork_side("fg_posture", "a") var mp_e = load("res://core/meta_progression.gd").new(_db, sv_e) - var rules_e: Array = mp_e.tree_rule_keys() + var rules_e: Dictionary[Variant, Variant] = mp_e.tree_rules() Kit.check("开局传参夹具前置:Fork 侧 a 激活 rule 键非空 + Y=Lv2 行表值 2(防断言空转)", rules_e.has("posture_first_strike") and mp_e.attune_refresh_charges() == 2) var p_new: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv_e, mp_e, @@ -324,7 +324,7 @@ func _meta_settle_and_workshop() -> void: Kit.check("主菜单开新冒险参数面:Y 刷新池 = 当前等级行表值(营地升几档开局就给几次)", int(p_new.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges()) Kit.check("主菜单开新冒险参数面:树 rule 键集 = Fork 激活侧 effects(点哪侧带哪侧)", - (p_new.get("tree_rules", []) as Array) == rules_e) + (p_new.get("tree_rules", {}) as Dictionary) == rules_e) Kit.check("主菜单开新冒险参数面:入口特有键(treasure_unlocked/shop_mode)随 extra 原样并入", bool((p_new.get("treasure_unlocked", {}) as Dictionary).get("wide_scout_band", false)) and String(p_new.get("shop_mode", "")) == "pool") var p_resume: Dictionary = load("res://presentation/main.gd").build_adventure_params(sv_e, mp_e, @@ -332,7 +332,7 @@ func _meta_settle_and_workshop() -> void: Kit.check("主菜单继续上局参数面:三键与开新冒险同源同值(续玩不吃白板开局)", (p_resume.get("attunement", []) as Array) == (sv_e.meta_state.attune_state().equipped as Array) and int(p_resume.get("attune_refresh_y", -1)) == mp_e.attune_refresh_charges() - and (p_resume.get("tree_rules", []) as Array) == rules_e) + and (p_resume.get("tree_rules", {}) as Dictionary) == rules_e) # 续玩路径 tree_rules 受理断言:直呼 runner 续玩参数面 # (v2 旧断点夹具同式最小断点——smoke ⑫ 受理路径同形)验 _tree_rules 与传入一致。 var adr_res = load("res://core/adventure_runner.gd").new(_db, _fresh_rng(19)) @@ -344,7 +344,7 @@ func _meta_settle_and_workshop() -> void: adr_res.resume_adventure(bp_min, p_resume)) Kit.check("继续上局受理面:tree_rules 随参数面进 runner._tree_rules(消费面 set_tree_rules 下传现成)", adr_res.runner() != null and adr_res.runner().pick_next_node(int(adr_res.runner().next_candidate_ids()[0])) - and ((adr_res.runner().current_battle()._facts.encounter.get("tree_rules", []) as Array) == rules_e)) + and ((adr_res.runner().current_battle()._facts.encounter.get("tree_rules", {}) as Dictionary) == rules_e)) _meta_smoke_cleanup() @@ -616,10 +616,10 @@ func _smoke_tree() -> void: Kit.check("零币切态 b(购买解锁后 A/B 可切)", mp.tree_toggle_fork("fg_posture", "b") and sv.meta_state.tree_state().fork_sides.get("fg_posture", "") == "b") Kit.check("切态零消费(钥石余额差分不动)", int(sv.meta_state.keystone_balance()) == bal_pre_toggle) - Kit.check("规则键投影=激活态效果键(B 态稳健拓荒——tree_rule_keys 随切态换键集)", - (mp.tree_rule_keys() as Array).has("posture_bulwark") and not (mp.tree_rule_keys() as Array).has("posture_first_strike")) + Kit.check("规则键投影=激活态效果键(B 态稳健拓荒——tree_rules 随切态换键集)", + (mp.tree_rules() as Dictionary).has("posture_bulwark") and not (mp.tree_rules() as Dictionary).has("posture_first_strike")) Kit.check("切回 A 态键集随换(六键词表在册——V11 校验面归单元段)", - mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rule_keys() as Array).has("posture_first_strike")) + mp.tree_toggle_fork("fg_posture", "a") and (mp.tree_rules() as Dictionary).has("posture_first_strike")) # 彩蛋特化觉醒(V8) Kit.check("彩蛋不经购买(手动购亮=红——觉醒唯一入口=特化判定)", not mp.tree_can_buy("easter_ley")) Kit.check("觉醒前置未满足不亮(两线未拉满)", sv.meta_state.tree_state().nodes.get("easter_ley", 0) == 0) @@ -640,7 +640,7 @@ func _smoke_tree_rules() -> void: print("--- 天赋树六键行为(V10:窗/限次/聚散/守侧)---") # 开局姿态组:每关首轮窗(battle_index 事实——引擎直构 + 自建 ctx) var fx = load("res://core/effect_engine.gd").new(null) - fx.rebuild([], [], ["posture_first_strike", "posture_bulwark"]) + fx.rebuild([], [], {"posture_first_strike": {"ap_bonus": 1}, "posture_bulwark": {"shield": 1}}) var ctx_p := EffectContext.new() Kit.check("先手压制:每关首战首轮窗命中(battle_index=1 且 round=1 → grant_ap queue)〔天赋树批 V10〕", (fx.dispatch(ctx_p, "round_start", {"battle_index": 1, "round_index": 1}, Callable()) as Array).size() == 1) @@ -652,7 +652,7 @@ func _smoke_tree_rules() -> void: and (fx.dispatch(ctx_p, "damage_taken", {"battle_index": 2, "round_index": 1}, Callable()) as Array).is_empty()) # 危险直觉·锋:限次闸(引擎直构——mine_hit 挂点现成;限次计数住 level 桶,跨战持久=共享同一字典引用) var fz = load("res://core/effect_engine.gd").new(null) - fz.rebuild([], [], ["sense_sharp"]) + fz.rebuild([], [], {"sense_sharp": {"halve_pct": 50, "cap_per_level": 2}}) var lvl_shared := {} # 同一关的 level 桶(两编排器共享引用 = 跨战持久,替代旧副本搬运) var ctx_fz := EffectContext.new() ctx_fz.level_bucket = lvl_shared @@ -669,7 +669,7 @@ func _smoke_tree_rules() -> void: Kit.check("锋:限次耗尽全额(counter 2 不<2 闸关——每关限次 provisional 2)〔无状态化 V5〕", (fz.dispatch(ctx_fz, "mine_hit", {}, Callable()) as Array).is_empty()) var fz2 = load("res://core/effect_engine.gd").new(null) - fz2.rebuild([], [], ["sense_sharp"]) + fz2.rebuild([], [], {"sense_sharp": {"halve_pct": 50, "cap_per_level": 2}}) var ctx_fz2 := EffectContext.new() ctx_fz2.level_bucket = lvl_shared # 挂同一 level 桶引用(新形态跨战持久:无 export/seed,同字典即持久) Kit.check("锋:限次跨战持久(level 桶共享引用——替代旧副本搬运链)〔无状态化 V4〕", @@ -679,7 +679,7 @@ func _smoke_tree_rules() -> void: var orch_nc = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777003)) orch_nc.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": [], "tree_rules": [], "battle_index": 1, + "feats": [], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"}) # 真实战斗上下文(状态容器开域——dispatch 链受理) var nc_rows: Array = [] # step_row 采集盒(lambda 捕引用容器——捕获变量赋值不出闭包) @@ -703,7 +703,7 @@ func _smoke_tree_rules() -> void: Kit.check("守侧 rig:战斗编排启动(tree_rules=sense_guard 携带)", orch.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, + "feats": [], "tree_rules": {"sense_guard": {"ap_refund": 1, "cap_per_level": 2}}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"})) var board = orch.minesweeper.current_board() @@ -733,7 +733,7 @@ func _smoke_tree_rules() -> void: Kit.check("守⑤ rig:次战编排启动(新战新队列实例)", orch2.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": [], "tree_rules": ["sense_guard"], "battle_index": 1, + "feats": [], "tree_rules": {"sense_guard": {"ap_refund": 1, "cap_per_level": 2}}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"})) Kit.check("守⑤:末轮判出不返(queue 只在 notify_round_start 兑现、战亡即弃——无跨战泄漏)", @@ -741,35 +741,36 @@ func _smoke_tree_rules() -> void: # 树限次按关清零改由 LevelRunner.level_counters 随 new 承载(旧硬编码名单冒烟断言随函数删除——换代同生死;新语义由 V4/V5 段承载) # 财富之道·散:商品标价折价(level_runner 计价单点) var lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777004), - {"tree_rules": []}) # 构造注入(拆件批 T3——setter 群退役) + {"tree_rules": {}}) # 构造注入(拆件批 T3——setter 群退役) Kit.check("散未激活:商品标价原价(无 economy_spend 键)", int(lr.shop.current_price({"base_price": 100, "price_increment": 0})) == 100 + int(_db.run.shop.price_level_bonus_at(0))) + var spend_dict: Dictionary = {"economy_spend": {"discount_pct": 80}} lr = load("res://core/level_runner.gd").new(_db, _fresh_rng(777005), - {"tree_rules": ["economy_spend"]}) # 构造注入(拆件批 T3——setter 群退役) + {"tree_rules": spend_dict}) # 构造注入(拆件批 T3——setter 群退役) Kit.check("散激活:商品购买价 ×80%(provisional 折率——floor 偏向玩家、下限 1;刷新价不折=另一计价点零触碰)", - int(lr.shop.current_price({"base_price": 100, "price_increment": 0})) == maxi(1, int(floor(float(100 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount)))) - and int(lr.shop.current_price({"base_price": 1, "price_increment": 0})) == maxi(1, int(floor(float(1 + int(_db.run.shop.price_level_bonus_at(0))) * float(_db.run.shop.spend_discount))))) + int(lr.shop.current_price({"base_price": 100, "price_increment": 0})) == maxi(1, int(floor(float(100 + int(_db.run.shop.price_level_bonus_at(0))) * float(spend_dict["economy_spend"]["discount_pct"]) / 100.0))) + and int(lr.shop.current_price({"base_price": 1, "price_increment": 0})) == maxi(1, int(floor(float(1 + int(_db.run.shop.price_level_bonus_at(0))) * float(spend_dict["economy_spend"]["discount_pct"]) / 100.0)))) # 财富之道·聚:战斗掉落乘算·入账前(编排 _roll_gp_drop——同种子配对对拍 1.5×) var orch3 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777005)) Kit.check("聚 rig:基线编排启动(无增益侧)", orch3.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": [], "tree_rules": [], "battle_index": 1, + "feats": [], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"})) - orch3._facts.encounter["tree_rules"] = [] + orch3._facts.encounter["tree_rules"] = {} var gp_base: int = orch3._roll_gp_drop() var orch4 = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(777005)) Kit.check("聚 rig:增益编排启动(tree_rules=economy_hoard)", orch4.begin_battle({"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": [], "tree_rules": ["economy_hoard"], "battle_index": 1, + "feats": [], "tree_rules": {"economy_hoard": {"mult_pct": 150}}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "adventure_counters": {}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle"})) - orch4._facts.encounter["tree_rules"] = ["economy_hoard"] + orch4._facts.encounter["tree_rules"] = {"economy_hoard": {"mult_pct": 150}} var gp_hoard: int = orch4._roll_gp_drop() Kit.check("聚:战斗掉落乘算 ×1.5(provisional——同种子配对 %d→%d,入账前=结算框显示实得)", - gp_hoard == int(round(float(gp_base) * 1.5)) and gp_hoard >= gp_base) + gp_hoard == int(round(float(gp_base) * float({"economy_hoard": {"mult_pct": 150}}["economy_hoard"]["mult_pct"]) / 100.0)) and gp_hoard >= gp_base) _meta_smoke_cleanup() diff --git a/game/tests/unit/core/node_map_generator_test.gd b/game/tests/unit/core/node_map_generator_test.gd index c2701bb1..1c1e8acb 100644 --- a/game/tests/unit/core/node_map_generator_test.gd +++ b/game/tests/unit/core/node_map_generator_test.gd @@ -30,6 +30,7 @@ func _init() -> void: _test_battle_floor() _test_exit_structure() _test_boss_exit_per_region() + _test_gen_params_table_driven() _test_rng_single_point() _test_negative_fixtures() quit(Kit.finish("node_map_generator_test")) @@ -477,3 +478,57 @@ func _has_msg(errs: Array, needle: String) -> bool: if String(e["message"]).contains(needle): return true return false + + +# ---- V6 节点图三参数表值驱动(架构整改——fork_third_way_pct/fill_rest_first_pct/width_max 入 level_gen_params)---- + +func _test_gen_params_table_driven() -> void: + # ① 同种子真实表行产图逐位(表值驱动回归锚) + var g1 := _graph(2, 42) + var g2 := _graph(2, 42) + Kit.check("表值驱动:同种子产图逐位复现〔架构整改 V6〕", _sig(g1) == _sig(g2)) + # ② width_max=2 夹具 → 各深度节点数 ≤2(宽度上限读表可观测) + var p2: Dictionary = _params(2).duplicate(true) + p2["width_max"] = 2 + var gw: Dictionary = _gen.generate(p2, 42) + var per_depth := {} + for n in gw["nodes"]: + var d2: int = int(n["depth"]) + per_depth[d2] = int(per_depth.get(d2, 0)) + 1 + var width_ok := true + for d3 in per_depth: + if int(per_depth[d3]) > 2: + width_ok = false + Kit.check("width_max=2 夹具:各深度节点数 ≤2(宽度上限读表可观测)〔架构整改 V6〕", width_ok) + # ③ fork_third_way_pct=0 夹具 → 无节点去路数超 fork_ways_min(三去路概率读表可观测) + var p0: Dictionary = _params(2).duplicate(true) + p0["fork_third_way_pct"] = 0 + var g0: Dictionary = _gen.generate(p0, 42) + var ways_ok := true + for n in g0["nodes"]: + if (n["next"] as Array).size() > int(p0["fork_ways_min"]): + ways_ok = false + Kit.check("fork_third_way_pct=0 夹具:无节点去路数超 fork_ways_min(三去路概率读表可观测)〔架构整改 V6〕", ways_ok) + # ④ fill_rest_first_pct=100 vs 0 → 填充交替起点可观测变化(交替起始骰读表)。 + # 夹具=L1 真实表行改 branch_density=0/battle_floor=0:fill 段只在单出口图跑(e==1——_assign_types)、 + # 岔口修型段会覆盖侧链头填充位——无岔口+零保底组合让填充差异直达产物(多种子扫过 diff 恒真) + var p100: Dictionary = _params(1).duplicate(true) + p100["branch_density"] = 0.0 + p100["battle_floor"] = 0 + p100["fill_rest_first_pct"] = 100 + var g100: Dictionary = _gen.generate(p100, 40) + var p000: Dictionary = _params(1).duplicate(true) + p000["branch_density"] = 0.0 + p000["battle_floor"] = 0 + p000["fill_rest_first_pct"] = 0 + var g000: Dictionary = _gen.generate(p000, 40) + var rest100 := 0 + for n in g100["nodes"]: + if String(n["type"]) == "rest": + rest100 += 1 + var rest000 := 0 + for n in g000["nodes"]: + if String(n["type"]) == "rest": + rest000 += 1 + Kit.check("fill_rest_first_pct=100 vs 0 夹具:填充交替起点产物互异(%d vs %d rest)〔架构整改 V6〕" % [rest100, rest000], + _sig(g100) != _sig(g000) and rest100 > rest000) diff --git a/game/tests/unit/core/node_map_growth_test.gd b/game/tests/unit/core/node_map_growth_test.gd index 86d3227f..3ab25ad9 100644 --- a/game/tests/unit/core/node_map_growth_test.gd +++ b/game/tests/unit/core/node_map_growth_test.gd @@ -66,11 +66,13 @@ func _test_fail_fast_static(gen, db) -> void: and NodeMapGenerator.check_params(db.run.level_gen_params.row_by_level_index(3)).is_empty()) var unsolvable := {"layers": 6, "branch_density": 0.0, "battle_ratio": 0.5, "reward_ratio": 0.2, "rest_ratio": 0.2, "shop_ratio": 0.1, "entry_count": 1, "exit_count": 3, "battle_floor": 4, - "fork_ways_min": 2, "fork_ways_max": 3} + "fork_ways_min": 2, "fork_ways_max": 3, + "fork_third_way_pct": 35, "fill_rest_first_pct": 50, "width_max": 4} Kit.check("层深 6 放三出口无解 → check_params 拦截〔fv4〕", not NodeMapGenerator.check_params(unsolvable).is_empty()) var overfloor := {"layers": 6, "branch_density": 0.0, "battle_ratio": 0.5, "reward_ratio": 0.2, "rest_ratio": 0.2, "shop_ratio": 0.1, "entry_count": 1, "exit_count": 1, "battle_floor": 99, - "fork_ways_min": 2, "fork_ways_max": 3} + "fork_ways_min": 2, "fork_ways_max": 3, + "fork_third_way_pct": 35, "fill_rest_first_pct": 50, "width_max": 4} Kit.check("战斗保底超可放数无解 → check_params 拦截〔fv4〕", not NodeMapGenerator.check_params(overfloor).is_empty()) @@ -96,7 +98,8 @@ func _trigger_fatal() -> void: # 域内合法但深度预算无解:层深 6 放三出口(check_params 拦下 → 致命中止) var unsolvable := {"layers": 6, "branch_density": 0.0, "battle_ratio": 0.5, "reward_ratio": 0.2, "rest_ratio": 0.2, "shop_ratio": 0.1, "entry_count": 1, "exit_count": 3, "battle_floor": 4, - "fork_ways_min": 2, "fork_ways_max": 3} + "fork_ways_min": 2, "fork_ways_max": 3, + "fork_third_way_pct": 35, "fill_rest_first_pct": 50, "width_max": 4} gen.generate(unsolvable, 42) # 致命封印未置位=fail-fast 静默化(缺陷)→ 显式退出码 2 与致命停机码 1 区分 if gen.is_fatal(): diff --git a/game/tests/unit/core/playtest_logger_guard_test.gd b/game/tests/unit/core/playtest_logger_guard_test.gd new file mode 100644 index 00000000..1f1ec9eb --- /dev/null +++ b/game/tests/unit/core/playtest_logger_guard_test.gd @@ -0,0 +1,36 @@ +# tests/unit/core/playtest_logger_guard_test.gd — 试玩日志器 ap_per_round 必填注入守卫〔架构整改 V5〕 +# 断言面 = PlaytestLogger.start_session():未注入(0)返回空串+零文件产出;注入 8 返回非空路径。 +# 真实入口=PlaytestLogger.start_session(R5 必填注入——组合根注入序前置的守卫面)。 +# 运行:godot --headless --path game --script res://tests/unit/core/playtest_logger_guard_test.gd +extends SceneTree + +const Kit := preload("res://tests/unit/test_kit.gd") + + +func _init() -> void: + _smoke_required_injection() + quit(Kit.finish("playtest_logger_guard_test")) + + +func _smoke_required_injection() -> void: + # 未注入(缺省 0):start_session 返回空串、零文件产出 + var lg0 = load("res://presentation/playtest_logger.gd").new() + var p0: String = lg0.start_session(42) + Kit.check("未注入拒绝开会话:start_session 返回空串〔架构整改 V5〕", p0 == "") + var f0 := FileAccess.file_exists(String(lg0._path)) + Kit.check("未注入零文件产出(_path 留空、盘上无会话件)〔架构整改 V5〕", not f0 and String(lg0._path) == "") + # 注入 8:返回非空路径(会话开启) + var lg8 = load("res://presentation/playtest_logger.gd").new() + lg8.ap_per_round = 8 + var p8: String = lg8.start_session(42) + Kit.check("注入 8 返回非空路径(守卫放行——会话开启)〔架构整改 V5〕", p8 != "") + if lg8._file != null: + lg8._file.close() + var f8 := FileAccess.file_exists(p8) + Kit.check("注入后会话件在盘〔架构整改 V5〕", f8) + if f8: + DirAccess.remove_absolute(ProjectSettings.globalize_path(p8)) + # 越域拒绝(33>32 上界) + var lg33 = load("res://presentation/playtest_logger.gd").new() + lg33.ap_per_round = 33 + Kit.check("越域 33 拒绝开会话(守卫上界 [1,32])〔架构整改 V5〕", lg33.start_session(42) == "") diff --git a/game/tests/unit/core/playtest_logger_guard_test.gd.uid b/game/tests/unit/core/playtest_logger_guard_test.gd.uid new file mode 100644 index 00000000..6feabbe6 --- /dev/null +++ b/game/tests/unit/core/playtest_logger_guard_test.gd.uid @@ -0,0 +1 @@ +uid://cyaq5yjgwr0ng diff --git a/game/tests/unit/core/treasure_effects_test.gd b/game/tests/unit/core/treasure_effects_test.gd index c2b0d23e..6a6c34cf 100644 --- a/game/tests/unit/core/treasure_effects_test.gd +++ b/game/tests/unit/core/treasure_effects_test.gd @@ -318,7 +318,7 @@ func _init() -> void: # enc 带 feats 键+ledger,专长触发经 _judge_seal 真实封印判定管线) var enc_feats = {"level_index": 1, "node_depth": 0, "hero_hp": 40, "hero_atk": 8, "hero_max_hp": 40, "hero_armor_class": 10, "hero_ap_bonus": 0, "attunement": [], "attune_charges": {}, - "feats": ["seal_smith"], "tree_rules": [], "battle_index": 1, + "feats": ["seal_smith"], "tree_rules": {}, "battle_index": 1, "bonus_ref": {"atk": 0, "max_hp": 0, "armor_class": 0}, "voucher_rap": 0, "voucher_time_sand": 0, "node_type": "battle", "adv_bucket": {}} var rng15 = load("res://core/rng_service.gd").new(); rng15.set_master_seed(341) diff --git a/game/tests/unit/core/tree_rules_v11_test.gd b/game/tests/unit/core/tree_rules_v11_test.gd index 8ecd5030..8c791eb7 100644 --- a/game/tests/unit/core/tree_rules_v11_test.gd +++ b/game/tests/unit/core/tree_rules_v11_test.gd @@ -14,34 +14,45 @@ extends SceneTree func _init() -> void: var eng = load("res://core/effect_engine.gd") var tree_tbl = load("res://data/schemas/meta_tree_table.gd") + var db = load("res://core/config_db.gd").new() + db.load_tables("res://data/tables", false) var errs: Array = [] + # 规则键参数字典夹具值源(R1 换代——synth 参数读 meta_tree Fork 行 effects_*,字面量退役) + var fork_params := {} + for row in db.crossrun.meta_tree.rows: + if String(row.get("kind", "")) != "fork": + continue + for skey in ["effects_a", "effects_b"]: + var fx: Dictionary = row.get(skey, {}) as Dictionary + for rk in fx.keys(): + fork_params[String(rk)] = fx[rk] # 四键合成行夹具(与 rebuild 树规则段同构——逐字段对照,实现漂移在此转红) var synth := { "posture_first_strike": [{"on": "round_start", "when": [{"fact": "battle_index", "op": "eq", "value": 1}, {"fact": "round_index", "op": "eq", "value": 1}], - "do": {"kind": "grant_ap", "mode": "queue", "value": 1}}], + "do": {"kind": "grant_ap", "mode": "queue", "value": int(fork_params["posture_first_strike"]["ap_bonus"])}}], "posture_bulwark": [{"on": "damage_taken", "when": [{"fact": "battle_index", "op": "eq", "value": 1}, {"fact": "round_index", "op": "eq", "value": 1}], - "do": {"kind": "shield", "value": 1}}], + "do": {"kind": "shield", "value": int(fork_params["posture_bulwark"]["shield"])}}], "sense_sharp": [{"on": "mine_hit", - "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}], - "do": {"kind": "damage_mult", "value": 50}}, + "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": int(fork_params["sense_sharp"]["cap_per_level"])}], + "do": {"kind": "damage_mult", "value": int(fork_params["sense_sharp"]["halve_pct"])}}, {"on": "mine_hit", - "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": 2}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_sharp_used", "op": "lt", "value": int(fork_params["sense_sharp"]["cap_per_level"])}], "do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_sharp_used", "value": 1}}], "sense_guard": [{"on": "seal_missed", - "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}], - "do": {"kind": "grant_ap", "mode": "queue", "value": 1}}, + "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": int(fork_params["sense_guard"]["cap_per_level"])}], + "do": {"kind": "grant_ap", "mode": "queue", "value": int(fork_params["sense_guard"]["ap_refund"])}}, {"on": "seal_missed", - "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": 2}], + "when": [{"fact": "counter", "id": "lvl_tree_sense_guard_used", "op": "lt", "value": int(fork_params["sense_guard"]["cap_per_level"])}], "do": {"kind": "counter_add", "counter_id": "lvl_tree_sense_guard_used", "value": 1}}], } var six := ["posture_first_strike", "posture_bulwark", "sense_sharp", "sense_guard", "economy_hoard", "economy_spend"] # rebuild 实装面(db=null→feat/attune 两段空转,_triggers 只含树规则合成行)——行数对照载体 var eng_inst = eng.new() - eng_inst.rebuild([], [], six) + eng_inst.rebuild([], [], fork_params) for key in six: if not tree_tbl.RULE_KEYS.has(key): errs.append("规则键出域 RULE_KEYS 词表: %s" % key) @@ -64,10 +75,58 @@ func _init() -> void: eng.validate_trigger_row("bad_kind", {}, [{"on": "round_start", "do": {"kind": "no_such_kind"}}], bad_errs) if bad_errs.is_empty(): errs.append("反例护栏失效:出域事件/出域效果类型未被 validate_trigger_row 拒绝") + errs.append_array(_test_rule_key_params(eng, tree_tbl, db)) if errs.is_empty(): - print("[tree_rules_v11] all green(四键合成行零违例 + 聚散两键无行成立 + 反例被拒——契约 5 闭集在册词表承载)") + print("[tree_rules_v11] all green(四键合成行零违例 + 聚散两键无行成立 + 反例被拒 + 六键参数域全过——契约 5 闭集在册词表承载)") quit(0) return for e in errs: printerr("[tree_rules_v11] %s" % str(e)) quit(1) + + +# ---- 规则键参数域校验(架构整改批 R1——六键参数字典):正例全过/缺参数拒/多参数拒/越域拒/非字典拒 ---- + +func _test_rule_key_params(eng, tree_tbl, db) -> Array: + var errs: Array = [] + var params_tbl: Dictionary = tree_tbl.RULE_KEY_PARAMS + # 正例:meta_tree.tres 现行 Fork 行逐键过 validate(加载期已验——此处显式复核) + var probe_ok := {"node_id": "probe_rule", "effects": {}, "effects_a": {}, "effects_b": {}} + for row in db.crossrun.meta_tree.rows: + if String(row.get("kind", "")) != "fork": + continue + for skey in ["effects_a", "effects_b"]: + for rk in (row.get(skey, {}) as Dictionary).keys(): + probe_ok[skey][String(rk)] = (row.get(skey, {}) as Dictionary)[rk] + errs.append_array(db.crossrun.meta_tree._validate_rule_key_params(probe_ok, "probe_ok")) + # 负例构造:取 sense_sharp 行(双参数键——缺/多/越域/非字典四形态) + var base_params: Dictionary = {"halve_pct": 50, "cap_per_level": 2} + var mk := func(mod: Dictionary) -> Dictionary: + var row := {"node_id": "probe_neg", "effects_a": {"sense_sharp": base_params.duplicate(true)}, "effects_b": {}} + for k in mod.keys(): + row["effects_a"]["sense_sharp"] = mod[k] + return row + var cases := [ + {"name": "缺参数", "row": {"node_id": "probe_neg", "effects_a": {"sense_sharp": {"halve_pct": 50}}, "effects_b": {}}}, + {"name": "多参数", "row": {"node_id": "probe_neg", "effects_a": {"sense_sharp": {"halve_pct": 50, "cap_per_level": 2, "ghost": 1}}, "effects_b": {}}}, + {"name": "越域", "row": {"node_id": "probe_neg", "effects_a": {"sense_sharp": {"halve_pct": 50, "cap_per_level": 99}}, "effects_b": {}}}, + {"name": "非字典", "row": {"node_id": "probe_neg", "effects_a": {"sense_sharp": 1}, "effects_b": {}}}, + ] + for c in cases: + var got: Array = db.crossrun.meta_tree._validate_rule_key_params(c["row"], "probe_neg") + if got.is_empty(): + errs.append("参数域负例未被拒(%s)" % String(c["name"])) + # 越域判别词在 message 内(断言定位面) + var oor: Array = db.crossrun.meta_tree._validate_rule_key_params(cases[2]["row"], "probe_neg") + var oor_hit := false + for e in oor: + if str(e).contains("越域"): + oor_hit = true + if not oor_hit: + errs.append("越域 message 缺判别词(断言定位面违约)") + # R1 对照表落点:_tree_rule_dict 注入构造面最小断言(sim 单点) + var mc = load("res://sim/adventure_monte_carlo.gd") + var td: Dictionary = mc._tree_rule_dict(db, ["posture_first_strike"]) + if not (td.size() == 1 and (td.get("posture_first_strike", {}) as Dictionary).get("ap_bonus", -1) == 1): + errs.append("_tree_rule_dict 注入构造面失配(应={posture_first_strike:{ap_bonus:1}}): %s" % str(td)) + return errs diff --git a/game/tests/unit/data_config/effect_kind_retired_test.gd b/game/tests/unit/data_config/effect_kind_retired_test.gd new file mode 100644 index 00000000..fe9819cb --- /dev/null +++ b/game/tests/unit/data_config/effect_kind_retired_test.gd @@ -0,0 +1,69 @@ +# 致命类用例(独立进程,期望退出码 1) +# 甲案词表退役〔架构整改 V3〕——overflow/attack_segments 两死效果通道摘出 EFFECT_KINDS: +# 向 feats.tres 注入 do.kind="overflow" 与 do.kind="attack_segments" 探针行 → validate_trigger_row +# 须以 EFFECT_KINDS 出域拒绝(词表闭面收窄——误写即加载致命)。任一违规被放行即回归(退出码 2)。 +# 运行:godot --headless --path game --script res://tests/unit/data_config/effect_kind_retired_test.gd +extends SceneTree + +const ConfigDBScript := preload("res://core/config_db.gd") +const Kit := preload("res://tests/unit/test_kit.gd") + +const SRC_DIR := "res://data/tables" +const BAD_OVERFLOW_DIR := "user://effect_kind_fixture_overflow" +const BAD_SEGMENTS_DIR := "user://effect_kind_fixture_segments" + +# 注入探针:死通道 do.kind(甲案摘名两族——各一探针行) +const PROBE_OVERFLOW := ' {\n "feat_id": "kind_overflow_probe",\n "display_name": "通道探针·溢出",\n "rarity": "common",\n "triggers": [ { "on": "dig_reveal", "when": [], "do": { "kind": "overflow", "value": 50 } } ],\n "desc": "夹具探针——甲案退役词 overflow"\n},\n' +const PROBE_SEGMENTS := ' {\n "feat_id": "kind_segments_probe",\n "display_name": "通道探针·连珠",\n "rarity": "common",\n "triggers": [ { "on": "dig_reveal", "when": [], "do": { "kind": "attack_segments", "cap": 2, "divisor": 3, "value": 0 } } ],\n "desc": "夹具探针——甲案退役词 attack_segments"\n},\n' + + +func _inject(dir: String, probe: String) -> bool: + var path := dir + "/feats.tres" + var f := FileAccess.open(path, FileAccess.READ) + if f == null: + push_error("注入失败:读不到 %s" % path) + return false + var s := f.get_as_text() + f.close() + var anchor := "rows = [" + var idx := s.find(anchor) + if idx < 0: + push_error("注入失败:锚点 '%s' 未找到(len=%d)" % [anchor, s.length()]) + return false + var cut := idx + anchor.length() + s = s.substr(0, cut) + "\n" + probe + s.substr(cut) + var w := FileAccess.open(path, FileAccess.WRITE) + if w == null: + push_error("注入失败:写不开 %s" % path) + return false + w.store_string(s) + w.close() + return true + + +func _assert_rejected(bad_dir: String, probe: String, label: String) -> bool: + # 返回 true=正确拒绝;false=放行或夹具失败(调用方据此退出 2) + if not Kit.copy_tables(SRC_DIR, bad_dir) or not _inject(bad_dir, probe): + push_error("[%s] 夹具目录准备失败" % label) + return false + var db = ConfigDBScript.new() + var ok: bool = db.load_tables(bad_dir, false) + if ok: + push_error("[%s] 违规表被放行——EFFECT_KINDS 出域校验失守(回归)" % label) + return false + if db.diagnostics_count("error") < 1: + push_error("[%s] 加载被拒但零 error 诊断——校验通道静默化(回归)" % label) + return false + print("[%s] 正确拒绝(error 诊断在案——EFFECT_KINDS 出域)" % label) + return true + + +func _init() -> void: + if not _assert_rejected(BAD_OVERFLOW_DIR, PROBE_OVERFLOW, "effect_kind_retired_test/①overflow 出域"): + quit(2) + return + if not _assert_rejected(BAD_SEGMENTS_DIR, PROBE_SEGMENTS, "effect_kind_retired_test/②attack_segments 出域"): + quit(2) + return + print("[effect_kind_retired_test] 双探针均正确拒绝(甲案词表退役——死通道误写加载致命)〔架构整改 V3〕") + quit(1) diff --git a/game/tests/unit/data_config/effect_kind_retired_test.gd.uid b/game/tests/unit/data_config/effect_kind_retired_test.gd.uid new file mode 100644 index 00000000..bb97b0c6 --- /dev/null +++ b/game/tests/unit/data_config/effect_kind_retired_test.gd.uid @@ -0,0 +1 @@ +uid://pgo3ndugo2w1 diff --git a/game/tests/unit/data_config/forge_table_test.gd b/game/tests/unit/data_config/forge_table_test.gd index 5f8e94e0..226a5114 100644 --- a/game/tests/unit/data_config/forge_table_test.gd +++ b/game/tests/unit/data_config/forge_table_test.gd @@ -214,7 +214,7 @@ func _test_negative_cases() -> void: # 现行 31;本套件零扩签,钉现行数(将来任何加词即转红提示复核)。 func _test_effect_kinds_closed() -> void: - Kit.check("效果引擎效果类型闭集 34 种(迭代 46 换图系扩签 grant_reserve_ap/apply_discount 换代)〔锻造工坊批 V11〕", - EffectEngineScript.EFFECT_KINDS.size() == 34) + Kit.check("效果引擎效果类型闭集 32 种(甲案词表退役——overflow/attack_segments 摘名)〔锻造工坊批 V11〕", + EffectEngineScript.EFFECT_KINDS.size() == 32) Kit.check("效果引擎事件词表在册 22 种(迭代 46 换图系扩签 refresh_request/board_refreshed 换代)〔锻造工坊批 V11〕", EffectEngineScript.EVENTS.size() == 22) diff --git a/game/tests/unit/data_config/level_gen_params_test.gd b/game/tests/unit/data_config/level_gen_params_test.gd index 28ac1aba..116cee43 100644 --- a/game/tests/unit/data_config/level_gen_params_test.gd +++ b/game/tests/unit/data_config/level_gen_params_test.gd @@ -26,7 +26,8 @@ func _row(L: int, layers: int, bd: float, ratios: Array, entry: int, exit_c: int return {"level_index": L, "layers": layers, "branch_density": bd, "battle_ratio": ratios[0], "reward_ratio": ratios[1], "rest_ratio": ratios[2], "shop_ratio": ratios[3], "entry_count": entry, "exit_count": exit_c, "battle_floor": floor_n, - "fork_ways_min": 2, "fork_ways_max": 3} + "fork_ways_min": 2, "fork_ways_max": 3, + "fork_third_way_pct": 35, "fill_rest_first_pct": 50, "width_max": 4} func _table(rows: Array) -> Variant: diff --git a/game/tests/unit/data_config/table_structure_guard_test.gd b/game/tests/unit/data_config/table_structure_guard_test.gd index ff0d7f8b..f1d5bfeb 100644 --- a/game/tests/unit/data_config/table_structure_guard_test.gd +++ b/game/tests/unit/data_config/table_structure_guard_test.gd @@ -42,6 +42,9 @@ func _init() -> void: _smoke_ac_swap_guard() _smoke_enemy_bands() _smoke_variation_framework() + _test_level_gen_new_fields() + _test_variation_header_domains() + _test_feat_cap_required() _smoke_difficulty_table() _smoke_nine_types_gp_stream() _smoke_adventure_table() @@ -529,3 +532,87 @@ func _conversion_row_of(rows: Array, attr: String) -> Dictionary: if String(r.get("attr", "")) == attr: return r return {} + + +# ---- V7 节点图三新键域校验(架构整改——缺新键行拒/width_max 越域拒/两 pct 越域拒/表值全过)---- + +func _test_level_gen_new_fields() -> void: + var tbl = _db.run.level_gen_params + var mk := func(mod: Dictionary) -> Dictionary: + var row := {"level_index": 1, "layers": 6, "branch_density": 0.3, "battle_ratio": 0.5, "reward_ratio": 0.2, + "rest_ratio": 0.2, "shop_ratio": 0.1, "entry_count": 1, "exit_count": 1, "battle_floor": 4, + "fork_ways_min": 2, "fork_ways_max": 3, "fork_third_way_pct": 35, "fill_rest_first_pct": 50, "width_max": 4} + for k in mod.keys(): + if bool(mod[k]): + row.erase(k) + else: + row[k] = mod[k] + return row + var inst = load("res://data/schemas/level_gen_params.gd").new() + # 表值 35/50/4 全过 + inst.rows.append(mk.call({})) + Kit.check("V7 表值行全过(35/50/4)〔架构整改 V7〕", inst.validate().is_empty()) + # 缺新键行拒(structural 键集闭集) + var missing: Dictionary = mk.call({"fork_third_way_pct": true, "fill_rest_first_pct": true, "width_max": true}) + inst.rows = [] as Array[Dictionary] + inst.rows.append(missing) + Kit.check("V7 缺新键行拒(ROW_KEYS 闭集)〔架构整改 V7〕", not inst.validate().is_empty()) + # width_max 越域拒 + inst.rows = [] as Array[Dictionary] + inst.rows.append(mk.call({"width_max": 7})) + Kit.check("V7 width_max=7 越域拒〔架构整改 V7〕", not inst.validate().is_empty()) + # 两 pct 越域拒 + inst.rows = [] as Array[Dictionary] + inst.rows.append(mk.call({"fork_third_way_pct": 101})) + Kit.check("V7 fork_third_way_pct=101 越域拒〔架构整改 V7〕", not inst.validate().is_empty()) + inst.rows = [] as Array[Dictionary] + inst.rows.append(mk.call({"fill_rest_first_pct": -1})) + Kit.check("V7 fill_rest_first_pct=-1 越域拒〔架构整改 V7〕", not inst.validate().is_empty()) + + +# ---- 变异表头域校验 / feat cap 必填(架构整改——丁案三件的数据面守卫)---- + +func _test_variation_header_domains() -> void: + var inst = load("res://data/schemas/variation_table.gd").new() + inst.percent_of_cells = 5 + inst.per_level_bonus = 0 + inst.pack_max = 4 + inst.pack_min = 2 + Kit.check("V11 表值头全过(5/0/4/2)〔架构整改 V11〕", inst.validate().is_empty()) + inst.pack_min = 5 # > pack_max=4 → 拒 + Kit.check("V11 pack_min>pack_max 拒〔架构整改 V11〕", not inst.validate().is_empty()) + inst.pack_min = 2 + inst.percent_of_cells = 51 # > 50 → 拒 + Kit.check("V11 percent_of_cells=51 拒〔架构整改 V11〕", not inst.validate().is_empty()) + inst.percent_of_cells = 5 + inst.pack_max = 10 # > 9 → 拒 + Kit.check("V11 pack_max=10 拒〔架构整改 V11〕", not inst.validate().is_empty()) + + +func _test_feat_cap_required() -> void: + var inst = load("res://data/schemas/feat_table.gd").new() + # 生产表全行 cap 在场(加载期已验——此处显式复核 37 行) + var all_cap := true + for row in _db.crossrun.feats.rows: + if not row.has("cap"): + all_cap = false + elif int(row["cap"]) < 1 or int(row["cap"]) > 99: + all_cap = false + Kit.check("V12 生产 feats 表全行 cap 在场且在域 [1,99]〔架构整改 V12〕", all_cap and _db.crossrun.feats.rows.size() > 0) + # 缺 cap 行拒 + var mk := {"feat_id": "probe_cap", "display_name": "探针", "rarity": "common", "triggers": []} + var no_cap := mk.duplicate(true) + inst.rows.append(no_cap) + Kit.check("V12 缺 cap 行拒(必填——零兜底)〔架构整改 V12〕", not inst.validate().is_empty()) + # 越域拒 + var oor := mk.duplicate(true) + oor["cap"] = 100 + inst.rows = [] as Array[Dictionary] + inst.rows.append(oor) + Kit.check("V12 cap=100 越域拒〔架构整改 V12〕", not inst.validate().is_empty()) + # cap_ok 直索引零兜底:在场行读 cap(relic 面真实入口) + var deck = load("res://core/curio_deck.gd").new(_db, load("res://core/rng_service.gd").new()) + var some_feat: Dictionary = _db.crossrun.feats.rows[0] + var cap_v: int = int(some_feat["cap"]) + Kit.check("V12 cap_ok 在场行直索引读 cap(首行 %s cap=%d——零兜底路径)〔架构整改 V12〕" % [String(some_feat["feat_id"]), cap_v], + deck.cap_ok("relic", String(some_feat["feat_id"])) == (int(deck.taken.get("relic:" + String(some_feat["feat_id"]), 0)) < cap_v)) diff --git a/game/tests/unit/presentation/player_copy_sweep_test.gd b/game/tests/unit/presentation/player_copy_sweep_test.gd index 40fa7d12..b0b5db89 100644 --- a/game/tests/unit/presentation/player_copy_sweep_test.gd +++ b/game/tests/unit/presentation/player_copy_sweep_test.gd @@ -36,6 +36,7 @@ func _init() -> void: _sweep_source_names() _sweep_board_glyph() _sweep_token_scans() + _sweep_refresh_toast_no_literals() quit(Kit.finish("player_copy_sweep_test")) @@ -156,6 +157,27 @@ func _sweep_token_scans() -> void: orch_hits == 0) +# 刷新两 toast 文案零数字字面量(架构整改 R4——格式串零数字、插值位与表字段一一对应; +# 静态判据+评审注记观测面局限:真机文案随表值由实机目检兜——T10 清单两项) +func _sweep_refresh_toast_no_literals() -> void: + var main_src := FileAccess.get_file_as_string("res://presentation/main.gd") + var rap_toast := "" + var cap_toast := "" + for line in main_src.split(" +"): + if "弃图刷新需" in line: + rap_toast = line + if "本轮刷新次数已用完" in line: + cap_toast = line + Kit.check("V4 前置:两 toast 行在场(源锚)", rap_toast != "" and cap_toast != "") + # RAP 不足 toast:格式串零数字字面量(「需 %d」插值读 rap_refresh_cost 表值) + var rap_fmt_bad := "(弃图刷新需 8)" in rap_toast or "%d 点(弃图刷新需 %d)" not in rap_toast + Kit.check("RAP 不足 toast:格式串零数字字面量、插值位=cost 表值拼接〔架构整改 V4〕", not rap_fmt_bad) + # 轮上限 toast:文案零数字字面量(「每轮 %d 次」插值读 refresh_cap_per_round 表值) + Kit.check("轮上限 toast:零数字字面量、插值位=refresh_cap_per_round 表值拼接〔架构整改 V4〕", + "(每轮 2 次)" not in cap_toast and "每轮 %d 次" in cap_toast) + + func _first_label(node: Node) -> Label: for child in node.get_children(): if child is Label: diff --git a/reports/iter50-反哺点-2026-11-03.md b/reports/iter50-反哺点-2026-11-03.md new file mode 100644 index 00000000..aad33097 --- /dev/null +++ b/reports/iter50-反哺点-2026-11-03.md @@ -0,0 +1,28 @@ +# 迭代 50 反哺点清单(2026-11-03) + +> 实现批禁写 design/ 与根规则文件——以下各条待用户裁决后由文档批落文,落文后本清单条目由用户在会话里清掉。 + +## 甲、design/ 文档落文义务 + +| # | 文档 | 落点 | 内容 | 依据 | +|---|---|---|---|---| +| 1 | design/gdd/systems/战斗循环/增益状态.md v1.5 | 〈目标规则与个体归属〉节「实装随该批」规矩行 | 删除该行(个体化已实装——合入前行为仍为旧共享态的注记过时) | 卡①关联同步面 | +| 2 | design/engineering/architecture/architecture.md | 件表/API 面 | ①新增 `MinesweeperRules.preview_reveal` 公开只读口(core 公开 API 净增 1);②戊案 `SPAWN_AREA_CELLS` 常量退役(如有登记);③庚案 battle_driver.drive_round 通用签名(act_provider/on_action/max_rejects);④R1 `MetaProgression.tree_rule_keys()` 改名 `tree_rules()` 返参数字典;⑤R1 shop.spend_discount 表字段退役(折价读规则键参数) | 卡③第 6 节同步面 | +| 3 | design/gdd/systems/冒险循环/奇物.md | cap 字段清单行(若有「可缺省/缺省按 1」类句) | 订正为「cap 必填(缺行加载致命),域 [1,99]」 | 卡③丁③(处置记录明示随卡申报反哺) | +| 4 | design/gdd/systems/跨局循环/天赋树.md v0.12 | — | 零反哺确认行(规则键参数表已是本批字段权威,实现与表逐字一致) | 卡③第 6 节 | +| 5 | design/gdd/systems/冒险循环/关卡节点图生成.md v1.4 | — | 零反哺确认行(三参数调参表行已落,实现照落;唯一注记=节点 depth 字段=最长路径深、区域序号可观测形态=d0−r——如需可入该文档注记) | 卡③第 6 节 | +| 6 | design/gdd/systems/冒险循环/冒险结构.md v1.4 | — | 零反哺确认行(货架位数行已落) | 卡③第 6 节 | +| 7 | design/gdd/systems/排雷循环/地形变异.md v1.2 | — | 零反哺确认行(pack_min 行已落;注记=per_level_bonus 现行表值 0 在域 [0,9] 内合法) | 卡③第 6 节 | +| 8 | design/gdd/systems/排雷循环/棋盘生成.md v1.2 | — | 零反哺确认行(出生区边长行已落) | 卡③第 6 节 | +| 9 | 术语表 | — | 零新增术语确认行(沿用处置记录既定用词:参数字典/只读口/驱动循环单点/个体归属) | 卡③第 6 节 | + +## 乙、game/tests/README.md(能力清单/覆盖域表) + +| # | 落点 | 内容 | +|---|---|---| +| 10 | 覆盖域表 :211「减动效开关存储契约」行 | 状态列「部分覆盖」→「已覆盖」(存储契约三断言〔断言补齐批 V6-V8〕落地;多语言切换面仍 P1 未建)——实现批禁写,归文档批翻转 | + +## 丙、过程注记(不落文,查实现报告) + +- 卡①现状考证五处行号/路径漂移、卡③取证表漂移与两处卡面-实况偏差(R8 注入通道/R10 观测面)、R1 结构还债——全数记入 `reports/iter50-实现报告-2026-11-03.md` 第六节实现决定记录 #1-#9。 +- MC 锚漂移预期与重跑义务见实现报告第八节(一档只能由用户发起)。 diff --git a/reports/iter50-实现报告-2026-11-03.md b/reports/iter50-实现报告-2026-11-03.md new file mode 100644 index 00000000..33ceb8bd --- /dev/null +++ b/reports/iter50-实现报告-2026-11-03.md @@ -0,0 +1,105 @@ +# 迭代 50 实现报告(2026-11-03) + +> 三卡合批:①敌方状态个体化批 ②断言补齐批 ③架构审查 3.1+3.2 整改批。工作区 `../g-plan2-iter50`(分支 iter50,基点 6261b36e)。全部验收断言当轮真实跑批执行并通过;单元套 77 套件全绿(含 2 负面件);冒烟 15 项 0 失败;gdstyle 0 新增 0 恶化。 + +## 一、各卡状态 + +| 卡 | T 段 | 状态 | 关键提交 | +|---|---|---|---| +| 卡①敌方状态个体化 | T0/T1/T2 全收口 | 完成(V1-V8 落地) | 8d535b6f / 58c5294b / 93f25fcb | +| 卡②断言补齐 | T0-T3 全落地(T4 产出物并入本报告) | 完成(V1-V16 落地) | 卡B 提交(73 套件绿节点) | +| 卡③架构审查整改 | T0-T10 全收口 | 完成(乙丙戊丁甲/A类①/庚己/组C组D 全落) | T0→T10 逐卡提交 | + +## 二、卡①实际对照表(改动—验收,全绿) + +| 需求点 | 改动 | 验收测试(尾缀〔个体化批 Vn〕) | 层级 | 真实入口 | 判据绑定面 | 结果 | +|---|---|---|---|---|---|---| +| 实例按个体归属 | status_container.gd 桶键换代(owner_key_for/OWNER_HERO 单点,敌人="e<生成序>"沿 handle_id) | system_contracts_test.gd:`_test_status_individual_isolation`〔V1〕 | 单元 | StatusContainer.apply_effect()/query_active() | 各体投影读数互不影响+owner_key 出户 | 绿 | +| 全体授予逐体发 | reward_grant.gd `_apply_percent_row` 循环存活敌逐只发 | system_contracts_test.gd:`_test_status_grant_all_per_unit`〔V2〕 | 单元 | 容器 apply_effect(授予路径同构模拟)+reward_grant 循环经 V5/回归承载 | 逐体实例数与各自衰减 | 绿 | +| 个体死亡即清 | damage_controller `_on_unit_died`→`purge_owner` | system_contracts_test.gd:`_test_status_death_clears`〔V3〕 | 单元 | apply_effect+purge_owner() | 死体桶清零、他体不变、侧汇总只含存活 | 绿 | +| 同体合并照旧 | 合并臂 hit 提取(累加/叠加同体内) | system_contracts_test.gd:`_test_status_merge_per_unit`〔V4〕 | 单元 | apply_effect() | 累加时长倍增/满层恰拒/异体受理 | 绿 | +| 增益技归施放者自己 | combat_segment 落点=owner_key_for(u)(debuff→主角) | round_flow_skill_pool_test.gd:`_smoke_enemy_buff_targets_self`〔V5〕 | 单元 | 战斗结算编排链(技能施放——begin_battle+settle_batch 推进) | 施放者 output 池=empower 幅度、他敌恒 0(种子扫描恰一次 cast=charge) | 绿 | +| 乘区池按个体 | damage_controller.clamped_pool 带 owner_key(:375 hero攻击打target/:478 敌技source_unit) | damage_controller_test.gd:`_test_pool_consumption_per_unit`〔V6〕 | 单元 | DamageController 兑付(clamped_pool 公有算术面) | e1 池含 weaken 值、e2 恒 0 | 绿 | +| AC 合流按个体 | defender_ac 敌分支只读被打者名下 ac 型 | damage_controller_test.gd:`_test_defender_ac_per_unit`〔V7〕 | 单元 | defender_ac() | e1=基数+alert、e2=自身基数 | 绿 | +| 呈现各读各的 | presentation_snapshot enemies[].effects 逐卡读自己桶(删同引用)+standee_body 注释换代 | battle_narrator_test.gd:`_test_snapshot_effects_per_unit`〔V8〕 | 单元 | presentation_snapshot()(真编排器) | A 卡现削弱、B 卡无同款 | 绿 | +| 申报换代两件 | armor_roll V1-V3 重写(个体键 "e1" 读数,累加/覆盖/同轮语义保留);衰减相位断言 :413-421/:449 核对=decay 行相位与桶结构无关,零改动即等价换代 | roll_multiplier_test〔状态累加 V1/V2/V3〕+round_flow 衰减段 | 单元 | 原真实入口 | 原判据个体化 | 绿 | + +主角侧回归:attack_up/ac_up 全套既有断言绿(roll_multiplier 42 checks)+冒烟 15 项 0 失败。 + +## 三、卡②实际对照表(全绿,尾缀〔断言补齐批 Vn〕) + +| 需求点 | 验收测试 | 结果 | +|---|---|---| +| ①衰减逐值/到期移除/战斗结束全清 | system_contracts_test:`_test_status_decay_and_clear`〔V1/V2〕(notify_battle_end 全测试目录首次落断言) | 绿 | +| ①满层拒绝 | `_test_status_stack_full_reject`〔V3〕 | 绿 | +| ①独立并存+首授快照 | `_test_status_independent_branch`〔V4,试验行注入测毕还原〕/`_test_status_snapshot_first_grant`〔V5〕 | 绿 | +| ②存储回读/缺省/应用链 | settings_store_test.gd(新件)三函数〔V6/V7/V8〕——_init path 注入隔离,禁碰真实 user://settings.cfg | 绿 | +| ⑤每区域恰一 Boss 终点 | node_map_generator_test:`_test_boss_exit_per_region`〔V9〕——节点 depth=最长路径深(生成器校验轴),区域序号可观测形态=d0−r(区域段 L−r 的 1 步差) | 绿 | +| ⑤Boss 节点开战必选 Boss 行集 | boss_gate_test.gd(新件)`_test_boss_node_spawns_boss_rowset`〔V10〕——行类路由+begin_battle 端到端(实例化敌人全部出自 boss 行集) | 绿 | +| ⑥全路线价格梯+多级购买 | forge_table_test:`_test_price_ladder`(扩)〔V11〕——floor 公式表值推算六路线逐级严格递增;meta_progression_forge_test 长剑链至 3 级〔V12〕 | 绿 | +| ⑥拒购零副作用 | `_test_forge_buy_reject`〔V13〕 | 绿 | +| ⑥祝福件零泄漏 | `_test_blessing_no_forge_leak`〔V14,LevelRunner 真实装配链 buy_product〕 | 绿 | +| ⑥默认值解耦+甲型双轴 | `_test_default_axes_pre_key`〔V15〕/`_test_armor_switch_dual_axes`〔V16——切回后新 runner 读新选中(快照冻结契约)〕 | 绿 | + +注:卡② T3-2 卡面写「扩展 `_meta_pricing_and_buy`」,实况长剑购买链在 `_smoke_forge_axes`(:411-420 考证即该函数)——V12 落该函数购买链段(主题归属正确),函数名指认漂移记实现决定记录 #1。 + +## 四、卡③实际对照表(R1-R15 全绿,尾缀〔架构整改 Vn〕) + +| R | 改动 | 验收测试 | 结果 | +|---|---|---|---| +| R1 六键参数字典 | meta_tree.tres 三 Fork 行参数字典+RULE_KEY_PARAMS 域表+四形态校验;effect_engine 四分支全读参零字面量;battle_fx_host/orchestrator/shop_session/meta_progression(tree_rules())/adventure_runner/level_runner/main 全链 Dictionary 换代;sim _tree_rule_dict 注入单点+透传守卫 | tree_rules_v11_test:`_test_rule_key_params`(六键域正反例+_tree_rule_dict 最小断言)+既有六键行为段回归 | 绿 | +| R2 散折价收回 | shop.spend_discount 字段与域校验退役;_spend_discounted 读 discount_pct/100 | level_shop_economy_test:`_test_spend_discount_from_rule_key`〔V2〕+迁移断言删(申报 7) | 绿 | +| R3 死通道退役 | EFFECT_KINDS 摘两名(34→32);_kill_effects 改名 overflow 分支删;attack_segments 消费段删 | effect_kind_retired_test(新负面件)双探针加载致命〔V3〕+round_flow 段内序回归 | 绿 | +| R4 文案读表 | main.gd 两 toast 插值读 rap_refresh_cost/refresh_cap_per_round | player_copy_sweep_test:`_sweep_refresh_toast_no_literals`〔V4〕静态+目检局限注记 | 绿 | +| R5 必填注入 | playtest_logger 删默认+守卫 [1,32];action_economy_table 删 schema 默认;level_autoplay 注入序前置×2 | playtest_logger_guard_test(新件)〔V5〕 | 绿 | +| R6 clamp 读单点 | resolver 读 MULT_MIN/MULT_MAX;失实「GDD Edge Case」注释订正两处 | config_db_resolver_test 回归(值逐位同) | 绿 | +| R7 节点图参数入表+域并单点 | ROW_KEYS+三键+域校验;_cp_domain 整函数退役;MAX_WIDTH 删(三处消费全读表——:274 宽度校验为卡面漏列同款消费,记 #2) | node_map_generator_test:`_test_gen_params_table_driven`〔V6〕+table_structure_guard:`_test_level_gen_new_fields`〔V7〕 | 绿 | +| R8 出生区入表 | spawn_edge 表头零哨兵+[3,5]奇数域;board_data 常量退役+几何导出;board_generator edge² 公式+剃雷域导出;calibrate 一行透传 | board_generator_test:`_test_spawn_edge_from_table`〔V8〕+board_gen_reward_test 推导换代(cap 压线 9 语义不减) | 绿 | +| R9 货架位数入表 | shelf_size 零哨兵+[1,6];shop_session 读表 | level_shop_economy_test:`_test_shelf_size_from_table`〔V9,夹具=5 货架五件〕 | 绿 | +| R10 成包下界入表+表头域校验 | pack_min 零哨兵+三域校验单点;board_installer 下界读表 | variant_placement_test:`_test_pack_min_from_table`〔V10——观测面局限申报见六-#4〕+table_structure_guard:`_test_variation_header_domains`〔V11〕 | 绿 | +| R11 feat cap 必填 | 缺 cap 拒+域 [1,99];feats.tres 37 行全 cap(加载零诊断);curio_deck 死变量删+悬空报错 | table_structure_guard:`_test_feat_cap_required`〔V12〕 | 绿 | +| R12 驱动循环收敛 | battle_driver 通用签名(缺省两参逐位);level_autoplay 内层委托+抄本删除 | battle_driver_drive_test(新件)`_smoke_drive_round_contract`〔V13——缺省确定性/provider 三形态/max_rejects/合步〕 | 绿 | +| R13 档位排序读表 | _pick_token 改读 TIER_RANK(未知档 0 同语义) | adventure_feats 316-f 既有断言回归+静态(源零 rank 字面量——match 表删实核) | 绿 | +| R14 只读预览口 | preview_reveal 只读口(零写入零事件) | minesweeper_rules_test:`_test_preview_reveal_readonly`〔V14——封印两形态零写入+实挖差集 F2 同口径〕 | 绿 | +| R15 0 格连锁算子 | _deduce 补算子经 preview_reveal 真实闭包 | auto_player_deduce_test(新件)〔V15——远端 safe 形态格+零盲挖〕 | 绿 | + +零行为改动面(不设对照表行):组 D(experiment_density 删除+引用面清理——全库 grep 零命中,board_gen.tres 出处指针合法);组 C(damage_probe 零改动——run_all/闸脚本零引用+头注「诊断工具·非验收件」在位)。证据=T9/T10 步骤读数(本报告第五节)。 + +## 五、S13 对账与基线读数 + +- **改前底账**(T0 grep):overflow(damage_controller :361/:396/:402/:403/:407/:411-414+effect_engine :29/:464+adventure_feats_test :743)、attack_segments(effect_engine :29+combat_segment :62/:65)、fx_streak_segments(combat_segment :66+round_flow :443)、experiment_density(README :53+board_generator :23+board_gen.tres :414)。 +- **冒烟基线**:15 项 0 失败(T0 起点;终态同绿)。 +- **S13 对账**:现场读数(T0)→逐卡改后核对——终态 `gdstyle_gate: ok`(0 新增 0 恶化);途中三条新红(apply_effect 参数/返回/嵌套+presentation_snapshot 函数长)与两条(_validate_rule_key_params 嵌套/feat_table.validate 分支)全部就地拆段还债(单出口化+hit 提取+私有 helper 四件),无基线改动。 +- **T0 复核**:取证表 18 处现址——子代理 10 处+本会话补核 #7/#16 全部一致;漂移处(fx_apply_status :451→:427、_kill_overflow 体至 :418、combat_segment :63、卡② V12 函数名、卡③ #16 _cp_domain :76 起)按 L2 备案(第五节决定记录)。 + +## 六、实现决定记录(代拍板白名单外备案) + +1. **卡② V12 函数名指认漂移**:卡面写扩展 `_meta_pricing_and_buy`,实况长剑购买链在 `_smoke_forge_axes`(考证 :411-420 即该段)——V12 落购买链段;`_meta_pricing_and_buy` 是树购买面与锻造无关。覆盖实质不受损。 +2. **卡③ R8 MAX_WIDTH 第三消费点**:卡面列 :382 一处,实况 :274(_vo_width_rules 校验段)与 :383 两处同引——同需求点同款读表换代一并落(常量删除的编译级连带)。 +3. **卡③ R8 spawn_edge 注入通道**:卡面「resolve 注入保证在场」与实况不符(resolve_board_gen 返回行字典不含表头)——改 generate 内 `db.combat.board_gen.spawn_edge` 直索引(fail-fast 同语义),FakeDB/_StubBoardGen 两桩补表头成员(夹具形态迁移)。 +4. **卡③ R10 pack_min 观测面局限申报**(test-conventions §8.4):pack_min 消费面=randi_range 下界(软目标——8 邻贴接禁令下生长早停为合法态〔board_installer 注释明示〕,L2 候选池碎片化使「产物零 <输出路径> [分片数=16] +镜像回落顺序:直连 → gh-proxy.net → gh-proxy.com(HEAD 探测 Content-Length 决定实际用的源)。 +校验:分片按字节范围落盘、按序拼合;zip 结构校验由调用方(mac_setup.sh 的 unzip -tq)承担。 +退出码:0=成功 1=失败。 +""" +import concurrent.futures as cf +import os +import subprocess +import sys + +MIRRORS = ["", "https://gh-proxy.net/", "https://gh-proxy.com/"] + + +def pick_mirror(url: str): + """HEAD 探测取 Content-Length + 前 4 字节 zip 魔数校验(镜像站会回 200 错误页体, + 只看 Content-Length 会把 195 字节的假体当真——2026-11-03 gh-proxy.net 实证)。 + 返回(实际可用 URL, 字节数)或 (None, 0)。非 zip 目标禁用本件。""" + for m in MIRRORS: + u = m + url + r = subprocess.run(["curl", "-sIL", "-m", "30", u], capture_output=True, text=True) + size = 0 + for line in r.stdout.splitlines(): + if line.lower().startswith("content-length:"): + try: + size = int(line.split(":", 1)[1].strip()) + except ValueError: + size = 0 + if size <= 65536: + continue + head = subprocess.run( + ["curl", "-sL", "-m", "20", "-r", "0-3", "-o", "-", u], + capture_output=True) + if head.stdout[:2] != b"PK": + continue + return u, size + return None, 0 + + +def fetch(job) -> bool: + i, s, e, url, out = job + p = "%s.part%02d" % (out, i) + want = e - s + 1 + for _attempt in range(200): + have = os.path.getsize(p) if os.path.exists(p) else 0 + if have >= want: + return True + r0 = s + have + subprocess.run( + ["curl", "-sL", "--speed-limit", "1024", "--speed-time", "30", + "-m", "600", "-r", "%d-%d" % (r0, e), "-o", p + ".new", url], + capture_output=True) + if os.path.exists(p + ".new"): + with open(p, "ab") as f, open(p + ".new", "rb") as g: + f.write(g.read()) + os.remove(p + ".new") + return False + + +def main() -> int: + if len(sys.argv) < 3: + print("用法: python3 chunk_dl.py <输出路径> [分片数=16]") + return 1 + url, out = sys.argv[1], sys.argv[2] + n = int(sys.argv[3]) if len(sys.argv) > 3 else 16 + real, size = pick_mirror(url) + if not real: + print("全部下载源失败") + return 1 + print("size=%d url=%s" % (size, real), flush=True) + chunk = (size + n - 1) // n + jobs = [(i, i * chunk, min((i + 1) * chunk, size) - 1, real, out) + for i in range(n) if i * chunk < size] + with cf.ThreadPoolExecutor(n) as ex: + for ok in ex.map(fetch, jobs): + if not ok: + print("分片重试耗尽") + return 1 + with open(out, "wb") as f: + for i, _, _, _, _ in jobs: + with open("%s.part%02d" % (out, i), "rb") as g: + f.write(g.read()) + for i, _, _, _, _ in jobs: + os.remove("%s.part%02d" % (out, i)) + print("done %d" % os.path.getsize(out)) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/ci/domain_route.sh b/tools/ci/domain_route.sh new file mode 100644 index 00000000..98f2803e --- /dev/null +++ b/tools/ci/domain_route.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# 乙案域分流——push 触发的远端 CI 按改动域选闸(2026-11-03 用户裁)。 +# 用法: tools/ci/domain_route.sh +# 输出: stdout 打印 full|docs|skip;GITHUB_OUTPUT 在位时另写 mode= 行(Actions 步骤输出)。 +# 规则(唯一事实源,乙案语义): +# 1. diff 含 game/ 下非 .md 文件(行为载体:代码/场景/数据表/素材)→ full(两域全量,渲染闸随 code 域) +# 2. 否则 diff 非空(design/ 文档、reports/、根 md、game/ 下 .md 等一切非代码面)→ docs +# (乙案:文档批只跑文档域秒级闸——gdd_lint/doc_link/doc_term/pipeline/doc_ref/capability+熵增) +# 例外:diff 只含 tools/ 与 .gitea/(闸件与管线自身改动)→ skip——闸件改动不占合回全量, +# 质量义务=落批人工确认可跑通(run_all_gates.sh 头注既有口径,不在远端重复烧引擎)。 +# 3. diff 空 → skip。 +# 判定面说明:game/tests/** 与 game/sim/** 属 game/ 行为载体(.md 除外)→ full, +# 与 run_all_gates.sh 关联档口径一致(测量面批 02 照跑全套件)。 +set -u +base="${1:-}" +head="${2:-HEAD}" +[ -n "$base" ] || { echo "用法: domain_route.sh " >&2; exit 2; } +head="$(git rev-parse --verify "${head}^{commit}" 2>/dev/null)" || { echo "坏 head: $head" >&2; exit 2; } +# base 不可用(新分支 zero-sha/被 force-push 掉)→ 回落 HEAD~1;孤提交(无父)→ docs 保守最小跑。 +git cat-file -e "$base^{commit}" 2>/dev/null || base="$(git rev-parse "$head~1" 2>/dev/null || true)" +if [ -z "$base" ]; then + mode="docs" +else + files="$(git diff --name-only "$base" "$head" 2>/dev/null)" + if [ -z "$files" ]; then + mode="skip" + elif echo "$files" | grep -vE '\.md$' | grep -qE '^game/'; then + mode="full" + else + non_tools="$(echo "$files" | grep -vE '^(tools/|\.gitea/)' || true)" + if [ -n "$non_tools" ]; then mode="docs"; else mode="skip"; fi + fi +fi +if [ -n "${GITHUB_OUTPUT:-}" ]; then printf 'mode=%s\n' "$mode" >> "$GITHUB_OUTPUT"; fi +echo "$mode" +exit 0 diff --git a/tools/ci/mac_setup.sh b/tools/ci/mac_setup.sh new file mode 100644 index 00000000..a8ba6a36 --- /dev/null +++ b/tools/ci/mac_setup.sh @@ -0,0 +1,234 @@ +#!/usr/bin/env bash +# mac_setup.sh — CI runner 自举件(Mac 本机 host 模式+专用账户图形会话;2026-11-03 用户裁)。 +# 一条命令把一台 macOS 变成 gplan 的远端 CI runner:专用账户+自动登录图形会话+Godot+gdstyle+ +# Pillow+act_runner 用户级常驻(LaunchAgent)。 +# 形态要点(渲染闸的硬需求):渲染闸要真窗口 = 必须有图形会话(WindowServer 只存在于已登录会话)。 +# 方案 = ci-runner 账户开机自动登录(owns 控制台图形会话)→ 该会话内 LaunchAgent 拉起 runner → +# 测试作业全部以 ci-runner 身份运行(无 admin、可写面限定);日常使用经「快速用户切换」到本人账户, +# 两会话并存互不干扰(后台会话保留约 1-2GB 内存;后台会话渲染行为在端到端验收第一轮实证)。 +# 用法(sudo 必须——建账户/写 /etc/kcpassword/装 /usr/local/bin 都要 root): +# sudo bash tools/ci/mac_setup.sh [--godot-zip <本地已下载的 macOS 版 zip 路径>] +# 环境前提: +# ① macOS(Intel/Apple Silicon 皆可,Godot 走 universal 包)② Xcode Command Line Tools 已装 +# (python3/git 在位;缺则先 xcode-select --install)③ 调用账户的 ~/gitea-runner/ 下已有 +# act_runner(darwin 包,gitea.com gitea/runner releases)与已注册的 .runner(一次性注册动作)。 +# 幂等:重复执行只补缺件;账户密码首次生成后存 /var/db/gplan-ci-runner.pw(root 600),重跑复用。 +set -euo pipefail + +GODOT_VER="4.7.2" +GODOT_URL="https://github.com/godotengine/godot/releases/download/${GODOT_VER}-stable/Godot_v${GODOT_VER}-stable_macos.universal.zip" +GDSTYLE_VER="0.4.0" +GDSTYLE_URL="https://github.com/atelico/gdstyle/releases/download/v${GDSTYLE_VER}/gdstyle-x86_64-apple-darwin.tar.gz" +MIRRORS=("" "https://gh-proxy.net/" "https://gh-proxy.com/") # 直连优先,镜像回落 +CI_USER="ci-runner" +CI_HOME="/Users/${CI_USER}" +PW_FILE="/var/db/gplan-ci-runner.pw" + +log() { echo "[mac_setup] $*"; } +die() { echo "[mac_setup] ✗ $*" >&2; exit 1; } + +[ "$(uname -s)" = "Darwin" ] || die "仅支持 macOS" +[ "$(id -u)" -eq 0 ] || die "请用 sudo 运行(建账户/写 /etc/kcpassword/装 /usr/local/bin 需要 root)" +for t in git curl python3 unzip dscl; do command -v "$t" >/dev/null 2>&1 || die "缺 ${t}(先 xcode-select --install)"; done +INVOKER="${SUDO_USER:-$(whoami)}" +RUNNER_SRC="/Users/${INVOKER}/gitea-runner" +[ -x "${RUNNER_SRC}/act_runner" ] || die "缺 ${RUNNER_SRC}/act_runner——先从 gitea.com/gitea/runner releases 下载 darwin 包" +[ -f "${RUNNER_SRC}/.runner" ] || die "缺 ${RUNNER_SRC}/.runner——act_runner 尚未注册(register 子命令,见 Gitea 文档)" + +GODOT_ZIP="" +while [ $# -gt 0 ]; do + case "$1" in + --godot-zip) GODOT_ZIP="${2:-}"; shift 2 ;; + *) die "未知参数: $1" ;; + esac +done +rm -f /tmp/godot_mac_XXXXXX.zip /tmp/gdstyle_XXXXXX.tar.gz 2>/dev/null || true # 早期 BSD-mktemp 缺陷的字面残留清理 + +# ── 1. 专用账户+密码落盘(自动登录需要密码编码进 /etc/kcpassword,root 600)── +if dscl . -read "${CI_HOME}" NFSHomeDirectory >/dev/null 2>&1; then + log "账户 ${CI_USER} 已存在" + [ -f "$PW_FILE" ] || die "账户已存在但 $PW_FILE 缺失(无法配置自动登录)——删除账户重跑,或手工补密码文件" + CI_PW="$(cat "$PW_FILE")" +else + CI_PW="$(uuidgen | tr -d '-')" + /usr/sbin/sysadminctl -addUser "$CI_USER" -password "$CI_PW" -home "$CI_HOME" \ + || die "账户创建失败(sysadminctl)" + printf '%s' "$CI_PW" > "$PW_FILE" + chmod 600 "$PW_FILE" + log "账户 ${CI_USER} 已创建(密码仅存 ${PW_FILE},root 600,不对外告知)" +fi +# 家目录兜底:sysadminctl 只登记不创建(首次运行输出 "not created!" 在案)——家目录缺失时 +# 自动登录与任何以该用户执行的操作都会失败(Pillow --user 失败即此根因)。 +if [ ! -d "${CI_HOME}" ]; then + mkdir -p "${CI_HOME}" + chown -R "${CI_USER}:staff" "${CI_HOME}" + createhomedir -c -u "$CI_USER" >/dev/null 2>&1 || true + log "家目录 ${CI_HOME} 已补建" +fi + +# ── 2. 自动登录(ci-runner 拥有控制台图形会话——渲染闸的窗口来源)── +_KCP="/etc/kcpassword" +_need_kcp=1 +[ -f "$_KCP" ] && _need_kcp=0 +if defaults read /Library/Preferences/com.apple.loginwindow autoLoginUser 2>/dev/null | grep -q "$CI_USER"; then + log "自动登录已指向 ${CI_USER},跳过" + _need_kcp=0 +fi +if [ "$_need_kcp" = 1 ]; then + python3 - "$CI_PW" <<'PYEOF' +import sys +pwd = sys.argv[1].encode() +key = bytes([0x7D, 0x89, 0x52, 0x23, 0xD2, 0xBC, 0xDD, 0xEA, 0xA3, 0xB9, 0x1F]) +enc = bytes(b ^ key[i % len(key)] for i, b in enumerate(pwd)) +enc += b"\x00" * ((12 - len(enc) % 12) % 12 + 1) +open("/etc/kcpassword", "wb").write(enc) +PYEOF + chmod 600 /etc/kcpassword + defaults write /Library/Preferences/com.apple.loginwindow autoLoginUser "$CI_USER" + log "自动登录已配置:开机直接进入 ${CI_USER} 图形会话(日常使用=快速用户切换到本人账户)" +fi + +# ── 3. Godot macOS 版 → 完整 .app 装 /usr/local/lib + /usr/local/bin/godot 包装转发 ── +# 纪律:官方签名封 bundle 的 Info.plist——从 .app 抽裸二进制会被 taskgated 判 +# 「Code Signature Invalid」SIGKILL(2026-11-03 实证),必须整 bundle 安装保持签名校验路径。 +if /usr/local/bin/godot --version >/dev/null 2>&1 && /usr/local/bin/godot --version 2>/dev/null | grep -q "$GODOT_VER"; then + log "Godot ${GODOT_VER} 已在位,跳过" +else + ZIP="" + SRC_ZIP="/Users/${INVOKER}/godot-linux/Godot_v${GODOT_VER}-stable_macos.universal.zip" + if [ -n "$GODOT_ZIP" ] && [ -s "$GODOT_ZIP" ]; then + ZIP="$GODOT_ZIP"; log "使用本地 zip: $ZIP" + elif [ -z "$GODOT_ZIP" ] && [ -s "$SRC_ZIP" ]; then + ZIP="$SRC_ZIP"; log "复用已下载 zip: $ZIP" + else + ZIP="$(mktemp "${TMPDIR:-/tmp}/godot_mac_XXXXXX")" # macOS BSD mktemp 禁 X 后带后缀 + _ok=0 + for m in "${MIRRORS[@]}"; do + log "下载 Godot(${m:+经 $m})" + # 镜像站可能回 200 的错误页体——下载后必须验 zip 结构,假成功当失败处理 + if curl -fL --retry 2 -m 1800 -o "$ZIP" "${m}${GODOT_URL}" && unzip -tq "$ZIP" >/dev/null 2>&1; then + _ok=1; break + fi + log "该源失败,换下一源" + done + [ "$_ok" = 1 ] || die "全部下载源失败——手动下载 ${GODOT_URL} 后重跑并加 --godot-zip <路径>;或用 tools/ci/chunk_dl.py 分片拉取" + fi + TMPD="$(mktemp -d)" + unzip -tq "$ZIP" >/dev/null # 双保险:本地复用的 zip 同样验结构 + unzip -q -o "$ZIP" -d "$TMPD" + mkdir -p /usr/local/lib + rm -rf "/usr/local/lib/Godot_ci.app" + cp -R "${TMPD}/Godot.app" "/usr/local/lib/Godot_ci.app" + xattr -dr com.apple.quarantine "/usr/local/lib/Godot_ci.app" 2>/dev/null || true + cat > /usr/local/bin/godot <<'EOF' +#!/bin/bash +exec /usr/local/lib/Godot_ci.app/Contents/MacOS/Godot "$@" +EOF + chmod 0755 /usr/local/bin/godot + rm -rf "$TMPD" + log "Godot 已装:$(/usr/local/bin/godot --version 2>/dev/null | head -1)" +fi + +# ── 4. gdstyle v0.4.0 darwin → /usr/local/bin/gdstyle(S13 静态扫描闸件,版本钉死)── +if /usr/local/bin/gdstyle --version >/dev/null 2>&1; then + log "gdstyle 已在位,跳过" +else + TGZ="$(mktemp "${TMPDIR:-/tmp}/gdstyle_XXXXXX")" # macOS BSD mktemp 禁 X 后带后缀 + SRC_TGZ="/Users/${INVOKER}/godot-linux/gdstyle-x86_64-apple-darwin.tar.gz" + if [ -s "$SRC_TGZ" ] && tar -tzf "$SRC_TGZ" >/dev/null 2>&1; then + TGZ="$SRC_TGZ"; log "复用已下载 gdstyle 包: $TGZ" + else + _ok=0 + for m in "${MIRRORS[@]}"; do + log "下载 gdstyle v${GDSTYLE_VER} darwin(${m:+经 $m})" + if curl -fL --retry 2 -m 600 -o "$TGZ" "${m}${GDSTYLE_URL}" && tar -tzf "$TGZ" >/dev/null 2>&1; then _ok=1; break; fi + done + [ "$_ok" = 1 ] || die "gdstyle 下载失败——手动下载 ${GDSTYLE_URL} 解压放 /usr/local/bin/gdstyle 后重跑" + fi + TMPD="$(mktemp -d)" + tar -xzf "$TGZ" -C "$TMPD" + BIN="$(find "$TMPD" -type f -name 'gdstyle*' | head -1)" + [ -n "$BIN" ] || die "压缩包内未找到 gdstyle 二进制" + install -m 0755 "$BIN" /usr/local/bin/gdstyle + xattr -dr com.apple.quarantine /usr/local/bin/gdstyle 2>/dev/null || true + rm -rf "$TMPD" + log "gdstyle 已装:/usr/local/bin/gdstyle" +fi + +# ── 5. Pillow(渲染闸依赖)——系统级安装(/Library/Python,全账户可导入), +# 不用 --user:ci-runner 家目录时序敏感,系统级绕开全部家目录问题 ── +if python3 -c "import PIL" >/dev/null 2>&1; then + log "Pillow 已在位(系统级),跳过" +else + log "系统级安装 Pillow(阿里源)" + python3 -m pip install pillow \ + -i https://mirrors.aliyun.com/pypi/simple/ --quiet \ + || die "Pillow 系统级安装失败" +fi + +# ── 6. runner 程序与注册文件就位(复制既有注册身份,免重新注册)── +mkdir -p "${CI_HOME}/gitea-runner" "${CI_HOME}/Library/LaunchAgents" +cp -f "${RUNNER_SRC}/act_runner" "${CI_HOME}/gitea-runner/act_runner" +cp -f "${RUNNER_SRC}/.runner" "${CI_HOME}/gitea-runner/.runner" +cp -f "${RUNNER_SRC}/config.yaml" "${CI_HOME}/gitea-runner/config.yaml" 2>/dev/null || true +python3 - "${CI_HOME}/gitea-runner/.runner" <<'PYEOF' +import json, sys +p = sys.argv[1] +d = json.load(open(p)) +d["labels"] = ["ci-mac:host"] +json.dump(d, open(p, "w")) +print("labels ->", d["labels"]) +PYEOF +sed -i '' 's/^ level: debug/ level: info/' "${CI_HOME}/gitea-runner/config.yaml" 2>/dev/null || true +chown -R "${CI_USER}:staff" "${CI_HOME}/gitea-runner" "${CI_HOME}/Library/LaunchAgents" + +# ── 7. LaunchAgent(用户级常驻,挂在 ci-runner 图形会话内——渲染闸窗口的来源)── +PLIST="${CI_HOME}/Library/LaunchAgents/com.gitea.act-runner.plist" +cat > "$PLIST" < + + + + Labelcom.gitea.act-runner + ProgramArguments + + ${CI_HOME}/gitea-runner/act_runner + daemon + + WorkingDirectory${CI_HOME}/gitea-runner + RunAtLoad + KeepAlive + StandardOutPath${CI_HOME}/gitea-runner/daemon.log + StandardErrorPath${CI_HOME}/gitea-runner/daemon.log + + +EOF +chown "${CI_USER}:staff" "$PLIST" +_uid="$(id -u "$CI_USER")" +if launchctl asuser "$_uid" launchctl bootstrap "gui/$_uid" "$PLIST" 2>/dev/null; then + log "LaunchAgent 已在 ${CI_USER} 会话内启动" + _started=1 +else + _started=0 + log "当前无 ${CI_USER} 图形会话——LaunchAgent 已就位,重启后随自动登录自动拉起" +fi + +# ── 8. 退役 Docker 版 runner(如存在)── +if command -v docker >/dev/null 2>&1; then + docker rm -f gitea-runner >/dev/null 2>&1 && log "Docker 版 runner 容器已退役" || log "无 Docker 版 runner 容器(或 docker 未运行)" +fi + +# ── 9. 验证与收尾提示 ── +if [ "$_started" = 1 ]; then + sleep 5 + grep -q "declare successfully" "${CI_HOME}/gitea-runner/daemon.log" 2>/dev/null \ + && log "runner 已向 Gitea 声明成功" || log "runner 已启动,声明确认见 ${CI_HOME}/gitea-runner/daemon.log" +else + log "⚠ 重启 Mac 后自动登录生效、runner 随会话拉起——重启后验证第①条" +fi +log "全部完成。验证清单:" +log " ① Gitea 网页 → 仓库设置 → Actions → Runners 应显示 mac-ci 在线(Idle,标签 ci-mac)" +log " ② sudo -u ${CI_USER} /usr/local/bin/godot --headless --version" +log " ③ sudo -u ${CI_USER} python3 -c 'import PIL'" +log " ④ 日常使用:登录窗/快速用户切换到本人账户(ci-runner 会话在后台保持)" +log " ⑤ 端到端:下一次合回 master 的推送自动触发跑批" diff --git a/tools/ci_runner.sh b/tools/ci_runner.sh deleted file mode 100644 index 855e2d59..00000000 --- a/tools/ci_runner.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/usr/bin/env bash -# 本地 CI 跑批件——合回 master 后的全量验证执行器(git 钩子触发、后台运行,2026-10-30 用户裁)。 -# 触发语义(钩子只负责拉起,判定在本件): -# merge 模式=合回验证点:主工作区 git merge 落地后(tools/hooks/post-merge)对 master 尖无条件 -# 跑一次全量域闸;任务 worktree 内的合并不触发(钩子里限主工作区)。 -# commit 模式=红态追绿:仅当最近一次 CI 判红时(tools/hooks/post-commit)本提交触发复验; -# 绿态与无状态零触发——小批直提不重复跑全量,有意义的重跑只剩「验证修复」一类。 -# 并跑语义=跑完补跑(coalesce,2026-10-30 用户裁):跑批期间新触发不中断当前轮——锁被占时, -# 在跑实例的尖端就是当前尖端则本触发无事可做,否则落 ci.rerun 标记;当前轮跑完见标记或尖端 -# 前移,对最新尖端自动补跑一轮。绝不逐笔触发各跑一轮,最终只测最新尖端。 -# 产物(均不入库,本目录 .gitignore 已收):ci-status.json=最新一跑判定(commit/verdict/红闸/ -# 关键日志行/耗时),ci-history.jsonl=逐次流水账,ci.log=当轮全量闸完整输出, -# ci.lock=锁目录(内含在跑尖端),ci.rerun=补跑标记。 -# 用法: tools/ci_runner.sh merge|commit -set -u -REPO="$(cd "$(dirname "$0")/.." && pwd)" -GATES_DIR="$REPO/game/tests/.gates" -LOCK="$GATES_DIR/ci.lock" -RERUN="$GATES_DIR/ci.rerun" -STATUS="$GATES_DIR/ci-status.json" -HISTORY="$GATES_DIR/ci-history.jsonl" -LOG="$GATES_DIR/ci.log" -GODOT="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" -export PYTHONIOENCODING=utf-8 - -MODE="${1:-commit}" - -# ── 触发判定 ── -if [ "$MODE" = "commit" ]; then - [ -f "$STATUS" ] || exit 0 - grep -q '"verdict": "red"' "$STATUS" || exit 0 -fi - -# ── 锁(mkdir 原子);被占时按在跑尖端判定:同尖=已在验这份字节,异尖=落补跑标记 ── -TIP="$(git -C "$REPO" rev-parse HEAD)" -if ! mkdir "$LOCK" 2>/dev/null; then - if [ "$(cat "$LOCK/tip" 2>/dev/null)" = "$TIP" ]; then - exit 0 - fi - touch "$RERUN" - exit 0 -fi -trap 'rm -rf "$LOCK"' EXIT - -# ── 跑批主循环(跑完补跑)── -while :; do - rm -f "$RERUN" - TIP="$(git -C "$REPO" rev-parse HEAD)" - echo "$TIP" > "$LOCK/tip" - _start="$(date +%s)" - - # 起手刷类名缓存:.godot 缓存不入 git,与合并后代码失配会假红假绿(在册坑)—— - # 原「合并后零复跑」的秒级动作由本件接管,全量闸跑的就是刷新后的字节 - "$GODOT" --headless --path "$REPO/game" --editor --quit >>"$LOG" 2>&1 - - bash "$REPO/tools/run_all_gates.sh" >"$LOG" 2>&1 - RC=$? - _end="$(date +%s)" - - # 判定与记录:读闸脚本写的回执(all_green/各闸退出码),红项关键行取自本轮 ci.log - python - "$REPO" "$TIP" "$RC" "$((_end - _start))" <<'PY' -import json, re, sys, datetime -repo, tip, rc, dur = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4]) -gates_dir = repo + "/game/tests/.gates" -entry = {"commit": tip, - "timestamp": datetime.datetime.now().astimezone().isoformat(timespec="seconds"), - "exit_code": rc, "verdict": "green" if rc == 0 else "red", - "duration_s": dur, "red_gates": [], "key_lines": []} -try: - with open(gates_dir + "/receipt.json", encoding="utf-8") as f: - r = json.load(f) - # 回执只有确属本跑(commit 对齐)才采信——闸在起手自检即中止时不写新回执, - # 盘上旧回执是上一跑的读数,当作本次红项会撒谎(2026-10-31 实跑缺陷修正) - if r.get("commit") == tip: - entry["red_gates"] = [g["name"] for g in r.get("gates", []) if str(g.get("exit")) != "0"] - else: - entry["note"] = "闸未跑完(起手自检或早期中止,回执为上一跑旧件)——红项读数不可用,看 key_lines 与 ci.log" -except Exception: - pass -try: - with open(gates_dir + "/ci.log", encoding="utf-8", errors="replace") as f: - lines = [l.rstrip() for l in f] - entry["key_lines"] = [l for l in lines if re.search(r"FAIL|红项|Error|error", l)][-12:] -except Exception: - pass -with open(gates_dir + "/ci-status.json", "w", encoding="utf-8") as f: - json.dump(entry, f, ensure_ascii=False, indent=1) -with open(gates_dir + "/ci-history.jsonl", "a", encoding="utf-8") as f: - f.write(json.dumps(entry, ensure_ascii=False) + "\n") -PY - - # 轮尾补跑判定:跑批期间有新触发(标记在)或尖端前移 → 对最新尖端再来一轮 - _newtip="$(git -C "$REPO" rev-parse HEAD)" - if [ ! -f "$RERUN" ] && [ "$_newtip" = "$TIP" ]; then - exit 0 - fi -done diff --git a/tools/ci_watch.sh b/tools/ci_watch.sh index 79fe2701..13b2b5e9 100755 --- a/tools/ci_watch.sh +++ b/tools/ci_watch.sh @@ -1,57 +1,126 @@ #!/usr/bin/env bash -# 回执等待件——合回后等 CI 回执的后台轮询器(2026-11-02 用户裁;规则面=process-conventions §1 第 3 条「回执等待纪律」)。 -# 用法: tools/ci_watch.sh <合并提交hash> [超时秒,默认 900] -# 调用面: 合回落地先向用户呈报本批总结,随即以 run_in_background 后台任务运行本件;命令结束时 -# harness 唤醒会话——绿→删区收口;红→呈报红闸名单走修复批;超时→呈报「回执未落」(CI 可能崩溃)。 -# 「本次回执已落」认领判据(三条全满足,防认错轮): -# ① 回执可整读——CI 写回执非原子,半截/读空静默重试; -# ② 回执 commit 为传入提交或其后代——另一会话合回推进尖端触发补跑覆盖时按后代认领(coalesce 语义见 ci_runner.sh); -# ③ 回执 timestamp 晚于合并提交时刻——排除合并前盘上的旧回执(旧尖端是合并提交的祖先,只查②会误领旧回执)。 +# 回执等待件——合回后等远端 CI 回执的后台轮询器(2026-11-03 用户裁:CI 迁远端,本件改轮询 Gitea API)。 +# 用法: tools/ci_watch.sh remote <推送提交hash> [超时秒,默认 3000] +# 调用面: post-merge 钩子推送 master 成功后自动后台挂起;人手等待亦可(agent 会话以 run_in_background 运行, +# 命令结束时 harness 唤醒——绿→删区收口;红→呈报红闸名单走修复批;超时→呈报「回执未落」)。 +# 「本次回执已落」判据:Gitea commit status 的 state 离开 pending(success/failure/error), +# 且 status 的 target_url 指向本仓库 Actions(防认错仓)。远端跑批 20-35 分钟量级,默认超时给足 50 分钟。 +# 产物:沿用本地 CI 时代的回执合同——game/tests/.gates/ci-status.json(commit/verdict/timestamp/ +# duration_s/red_gates/key_lines/note)覆写 + ci-history.jsonl 追加;红态 red_gates=失败的 job 名单 +# (来自 runs//jobs API),job 日志链接在 key_lines,细粒度红闸清单以 job 日志为准。 +# 授权:GITEA_TOKEN 环境变量优先,否则读 git 凭据库(host=git.kuaimeo.com)。 # 退出码: 0=绿 1=红 2=超时/回执未落 3=参数错误 set -u -# 主工作区定位:CI 只在主工作区跑(post-merge 钩子限主区),从任务 worktree 内调用也要读主区回执—— -# 经 git 公共目录(.git)的上级定位主仓根,主区调用与 worktree 调用同一读数 COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \ || { echo "不在 git 仓库内"; exit 3; } REPO="$(dirname "$COMMON")" -STATUS="$REPO/game/tests/.gates/ci-status.json" -BASE="${1:-}" -TIMEOUT="${2:-900}" +GATES_DIR="$REPO/game/tests/.gates" +STATUS="$GATES_DIR/ci-status.json" +HISTORY="$GATES_DIR/ci-history.jsonl" + +MODE="${1:-}" +BASE="${2:-}" +TIMEOUT="${3:-3000}" INTERVAL=30 -[ -n "$BASE" ] || { echo "用法: ci_watch.sh <合并提交hash> [超时秒]"; exit 3; } +case "$MODE" in + remote) ;; + *) + echo "用法: ci_watch.sh remote <推送提交hash> [超时秒](本地 CI 模式已退役——回执现由远端产生)" + exit 3 ;; +esac +[ -n "$BASE" ] || { echo "用法: ci_watch.sh remote <推送提交hash> [超时秒]"; exit 3; } BASE="$(git -C "$REPO" rev-parse --verify "${BASE}^{commit}")" || { echo "提交不存在: $BASE"; exit 3; } -BASE_CT="$(git -C "$REPO" log -1 --format=%ct "$BASE")" -_deadline=$(( $(date +%s) + TIMEOUT )) -while :; do - _read="$(python -I -c " -import json, sys, datetime -try: - d = json.load(open(sys.argv[1], encoding='utf-8')) - ts = int(datetime.datetime.fromisoformat(d['timestamp']).timestamp()) - print(d.get('commit', ''), d.get('verdict', ''), ts) -except Exception: - pass -" "$STATUS")" - _com=""; _verdict=""; _ts="" - if [ -n "$_read" ]; then - set -- $_read - [ $# -eq 3 ] && { _com="$1"; _verdict="$2"; _ts="$3"; } - fi - if [ -n "$_com" ] && [ -n "$_verdict" ] && [ -n "$_ts" ] \ - && [ "$_ts" -gt "$BASE_CT" ] \ - && git -C "$REPO" merge-base --is-ancestor "$BASE" "$_com" 2>/dev/null; then - python -I -c " -import json, sys -d = json.load(open(sys.argv[1], encoding='utf-8')) -print('CI回执:', d.get('verdict'), '| commit', d.get('commit', '')[:8], '| 耗时', d.get('duration_s'), 's') -if d.get('note'): print('note:', d['note']) -for g in d.get('red_gates', []): print('红闸:', g) -for l in d.get('key_lines', []): print('key:', l) -" "$STATUS" - [ "$_verdict" = "green" ] && exit 0 - exit 1 - fi - _now=$(date +%s); _left=$((_deadline - _now)) - [ "$_left" -le 0 ] && { echo "超时(${TIMEOUT}s)回执未落——CI 可能崩溃或未触发,查 game/tests/.gates/ 的 ci.lock 与 ci.log"; exit 2; } - sleep $(( _left < INTERVAL ? _left : INTERVAL )) -done + +# 仓库全名(owner/repo)从 origin URL 解析(HTTPS 与 SSH 形式都兼容) +_origin="$(git -C "$REPO" remote get-url origin 2>/dev/null || true)" +REPO_SLUG="$(printf '%s' "$_origin" | grep -oE '[^/:]+/[^/]+\.git$' | sed 's/\.git$//')" +[ -n "$REPO_SLUG" ] || { echo "无法从 origin 解析 owner/repo: $_origin"; exit 3; } + +# 令牌:环境变量优先,回落 git 凭据库 +TOKEN="${GITEA_TOKEN:-}" +if [ -z "$TOKEN" ]; then + TOKEN="$(printf 'protocol=https\nhost=git.kuaimeo.com\n\n' | git credential fill 2>/dev/null | sed -n 's/^password=//p' | head -1)" +fi +[ -n "$TOKEN" ] || { echo "取不到 Gitea 令牌(设 GITEA_TOKEN 或先 git credential approve 存储)"; exit 3; } +API="https://git.kuaimeo.com/api/v1/repos/$REPO_SLUG" + +export PYTHONIOENCODING=utf-8 +python -I - "$API" "$BASE" "$TOKEN" "$TIMEOUT" "$INTERVAL" "$STATUS" "$HISTORY" <<'PYEOF' +import json, os, re, subprocess, sys, time, urllib.request, urllib.error, datetime + +api, base, token, timeout, interval, status_path, history_path = sys.argv[1:8] +timeout, interval = int(timeout), int(interval) +deadline = time.time() + timeout +start = time.time() +H = {"Authorization": "token " + token} + + +def get(url, t=20): + req = urllib.request.Request(url, headers=H) + return json.load(urllib.request.urlopen(req, timeout=t)) + + +def combined_status(sha): + return get(api + "/commits/%s/status" % sha) + + +def write_receipt(verdict, note, red_gates, key_lines): + now = datetime.datetime.now().astimezone().isoformat(timespec="seconds") + dur = int(time.time() - start) + d = {"commit": base, "verdict": verdict, "timestamp": now, + "duration_s": dur, "note": note, "red_gates": red_gates, "key_lines": key_lines} + os.makedirs(os.path.dirname(status_path), exist_ok=True) + tmp = status_path + ".tmp" + with open(tmp, "w", encoding="utf-8") as f: + json.dump(d, f, ensure_ascii=False, indent=1) + os.replace(tmp, status_path) + with open(history_path, "a", encoding="utf-8") as f: + f.write(json.dumps(d, ensure_ascii=False) + "\n") + return d + + +def emit(d): + print("CI回执:", d.get("verdict"), "| commit", d.get("commit", "")[:8], "| 耗时", d.get("duration_s"), "s") + if d.get("note"): + print("note:", d["note"]) + for g in d.get("red_gates", []): + print("红闸:", g) + for l in d.get("key_lines", []): + print("key:", l) + + +last_err = "" +while True: + try: + s = combined_status(base) + state = s.get("state", "") + statuses = s.get("statuses", []) + target = next((x.get("target_url", "") for x in statuses if x.get("target_url")), "") + if state in ("success", "failure", "error", "warning"): + red = state != "success" + red_gates, run_id = [], None + m = re.search(r"/actions/runs/(\d+)", target) + if m and red: + run_id = m.group(1) + try: + jobs = get(api + "/actions/runs/%s/jobs" % run_id) + red_gates = sorted({j["name"] for j in jobs.get("jobs", []) if j.get("status") not in ("success", "skipped")}) + except Exception as e: + red_gates = ["" % e] + note = "远端CI state=%s%s" % (state, ("(run %s)" % run_id) if run_id else "") + key_lines = ["job日志: %s" % target] if target else [] + d = write_receipt("green" if not red else "red", note, red_gates, key_lines) + emit(d) + sys.exit(0 if not red else 1) + # pending:继续等 + except urllib.error.HTTPError as e: + last_err = "HTTP %s" % e.code + except Exception as e: + last_err = str(e) + if time.time() > deadline: + note = "超时(%ds)远端回执未落——最后错误 %s;查 runner 在线态与 %s/actions" % (timeout, last_err or "无", api.rsplit("/repos/", 1)[0]) + d = write_receipt("red", note, ["<回执未落>"], []) + emit(d) + sys.exit(2) + time.sleep(interval) +PYEOF diff --git a/tools/gate_platform.sh b/tools/gate_platform.sh new file mode 100644 index 00000000..8847cae3 --- /dev/null +++ b/tools/gate_platform.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# 闸件跨平台件——平台探测/路径形式/带超时执行/Godot 孤儿清杀,Git Bash 与 macOS/Linux 双栖。 +# 单点定义:run_all_gates.sh / probe_gate.sh / render_gate.sh / golden_baseline_refresh.sh 共用(source 引入)。 +# 纪律:闸脚本禁裸调 coreutils timeout 与 Windows 专用件(taskkill/PowerShell/ps -W/tasklist/cygpath)—— +# 一律走本件封装;macOS 为 BSD 工具链(无 coreutils timeout,sed/stat 语义不同),裸调必断 +# (2026-11-03 远端 CI 迁移裁决:runner=Mac 本机 host 模式,禁再增 Windows 专用直调)。 +# 平台判定以 uname 为准(Git Bash=MINGW*,macOS=Darwin,Linux=Linux)。 + +gate_is_win() { + case "$(uname -s 2>/dev/null)" in + MINGW*|MSYS*|CYGWIN*|Windows_NT) return 0 ;; + *) return 1 ;; + esac +} + +# Godot --path 接受的项目路径形式:Windows 需 cygpath -m 混合斜杠,其他平台原样。 +gate_game_path() { + if gate_is_win; then cygpath -m "$1"; else printf '%s' "$1"; fi +} + +# 杀整棵进程树()——递归杀子进程后杀本进程。macOS/Linux 走 pgrep -P; +# Windows 侧 MSYS kill 对其可见进程有效,带 wrapper 树的场景仍由调用方原 taskkill /T 逻辑负责。 +gate_kill_tree() { + local p c + kill -9 "$1" 2>/dev/null + for c in $(pgrep -P "$1" 2>/dev/null); do gate_kill_tree "$c"; done +} + +# 带超时执行(coreutils timeout 的纯 bash 替代,双平台同语义): +# gate_run_timeout <秒> <日志路径> <命令...> +# 命令输出重定向到日志;退出码=命令退出码,超时=124(与 coreutils 语义一致,判据与既有闸一致)。 +# 实现=后台启动+守卫落标志文件(probe_gate run_suite 同款手法:taskkill 强杀后 wait 报 0 的 +# 假绿以标志文件纠偏);超时先杀整树再回 124。 +gate_run_timeout() { + local t="$1" log="$2"; shift 2 + local kf + kf="$(mktemp "${TMPDIR:-/tmp}/gatetime_XXXXXX")" + "$@" >"$log" 2>&1 & + local pid=$! + ( sleep "$t" 2>/dev/null + touch "$kf" + gate_kill_tree "$pid" + true ) & + local guard=$! + wait "$pid"; local st=$? + kill "$guard" 2>/dev/null; wait "$guard" 2>/dev/null + if [ -f "$kf" ]; then + gate_kill_tree "$pid" + st=124 + fi + rm -f "$kf" + return "$st" +} + +# 列命令行含 的 Godot 进程 PID(孤儿清杀与批末残留清点共用)。 +# Windows:PowerShell WMI 按进程名+命令行匹配(console wrapper+真引擎都算); +# macOS/Linux:ps 全表按「可执行含 godot+命令行含本 game 路径」过滤(ps 两平台同形)。 +gate_orphans() { + local gp="$1" + if gate_is_win; then + local tmpd tmpf + tmpd="$(mktemp -d "${TMPDIR:-/tmp}/gateorph_XXXXXX")" # macOS BSD mktemp 禁 X 后带后缀(本分支 Windows 专用,模板统一防复用) + tmpf="$tmpd/k.ps1" + local gp_win + gp_win="$(cygpath -m "$gp")" + printf "Get-CimInstance Win32_Process -Filter \"Name like 'Godot%%'\" | Where-Object { \$_.CommandLine -like '*%s*' } | ForEach-Object { \$_.ProcessId }\n" "$gp_win" > "$tmpf" + powershell -NoProfile -ExecutionPolicy Bypass -File "$(cygpath -w "$tmpf")" 2>/dev/null | tr -d '\r' + rm -rf "$tmpd" + else + ps -axo pid=,command= 2>/dev/null | awk -v g="$gp" 'tolower($0) ~ /godot/ && index($0, g) {print $1}' + fi +} + +gate_kill_orphans() { # gate_kill_orphans + local p + for p in $(gate_orphans "$1"); do + if gate_is_win; then taskkill //F //PID "$p" >/dev/null 2>&1 + else kill -9 "$p" 2>/dev/null; fi + done +} + +# 全局 Godot 进程计数(其他会话的仅报数不算账——批末残留清点的对照面)。 +gate_godot_count_all() { + if gate_is_win; then tasklist 2>/dev/null | grep -ci '^Godot' || true + else ps -axo comm= 2>/dev/null | grep -ci 'godot' || true + fi +} + +# golden 基线平台子目录名——渲染基线按平台各存一份(Windows 显卡字形/抗锯齿与 macOS CoreText +# 差异使跨平台逐像素比对必失配,2026-11-03 用户裁:按平台各存基线,平台内部照常严格比对)。 +gate_platform_id() { + case "$(uname -s 2>/dev/null)" in + MINGW*|MSYS*|CYGWIN*|Windows_NT) echo "windows" ;; + Darwin) echo "mac" ;; + *) echo "linux" ;; + esac +} diff --git a/tools/gdstyle_gate.py b/tools/gdstyle_gate.py index b93d54cb..49c375a5 100644 --- a/tools/gdstyle_gate.py +++ b/tools/gdstyle_gate.py @@ -48,7 +48,10 @@ CONFIG = ROOT / "tools" / "gdstyle.toml" BASELINE = ROOT / "tools" / "gdstyle_baseline.txt" SCAN_DIRS = ["game/core", "game/data", "game/presentation"] PINNED_VERSION = "0.4.0" -DEFAULT_BIN = "D:/code/gdstyle/gdstyle.exe" +# 二进制定位:GDSTYLE_BIN 环境变量优先;Windows 本机固定路径保底,macOS/Linux 走 /usr/local/bin +# (mac_setup.sh 自举件装钉定版 v0.4.0 darwin 包至此,远端 CI 与本机同判据)。 +DEFAULT_BIN = ("D:/code/gdstyle/gdstyle.exe" if sys.platform == "win32" + else "/usr/local/bin/gdstyle") MAX_REPORT = 50 MAX_VANISHED_REPORT = 20 SHRINK_GUARD = 0.9 # 新键数 < 旧键数 * 本系数=骤降,refresh 拒写须 --force diff --git a/tools/golden_baseline_refresh.sh b/tools/golden_baseline_refresh.sh old mode 100644 new mode 100755 index 93ad7d7f..e74858bf --- a/tools/golden_baseline_refresh.sh +++ b/tools/golden_baseline_refresh.sh @@ -15,6 +15,7 @@ set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行) GAME="" MODE="board" for a in "$@"; do @@ -24,7 +25,8 @@ for a in "$@"; do esac done GAME="${GAME:-$REPO/game}" -GAME_WIN="$(cygpath -m "$GAME")" +GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) +PLAT="$(gate_platform_id)" # 基线落点=本平台子目录(game/tests/golden//) TMP="$(mktemp -d /tmp/goldenrefresh_XXXXXX)" trap 'rm -rf "$TMP"' EXIT @@ -32,20 +34,20 @@ if [ "$MODE" = "menu" ]; then for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \ assets/art/ui/button_stone.png; do if [ ! -f "$GAME/$a" ]; then - echo "[golden_refresh] 拒跑 — 菜单素材缺位: $a(S3 停机点口径:基线禁锚回退态,先补素材+导入边车)" + echo "[golden_refresh] 拒跑 — 菜单素材缺位: ${a}(S3 停机点口径:基线禁锚回退态,先补素材+导入边车)" exit 1 fi done - timeout 240 "$G" --path "$GAME_WIN" res://probe/shot_probe.tscn -- "$TMP/shots" >"$TMP/run.log" 2>&1 + gate_run_timeout 240 "$TMP/run.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/shot_probe.tscn -- "$TMP/shots" st=$? grep -E "\[shot_probe\]" "$TMP/run.log" | tail -3 if [ "$st" -ne 0 ] || [ ! -s "$TMP/shots/shot_menu.png" ]; then - echo "[golden_refresh] FAIL — shot_probe 退出码=$st(菜单截图缺失/探针自检红,见上)" + echo "[golden_refresh] FAIL — shot_probe 退出码=${st}(菜单截图缺失/探针自检红,见上)" exit 1 fi - mkdir -p "$REPO/game/tests/golden" - cp "$TMP/shots/shot_menu.png" "$REPO/game/tests/golden/menu_boot.png" || exit 1 - echo "[golden_refresh] 完成 — 菜单全屏基线已代入 game/tests/golden/menu_boot.png(提交说明须写明触发原因)" + mkdir -p "$REPO/game/tests/golden/$PLAT" + cp "$TMP/shots/shot_menu.png" "$REPO/game/tests/golden/$PLAT/menu_boot.png" || exit 1 + echo "[golden_refresh] 完成 — 菜单全屏基线已代入 game/tests/golden/$PLAT/menu_boot.png(提交说明须写明触发原因)" exit 0 fi @@ -56,21 +58,21 @@ for a in assets/fonts/ui_font_fusion_pixel_zh_hans.ttf \ assets/art/board/terrain_wall_n_underdark.png assets/art/board/terrain_floor_underdark_1.png \ assets/art/board/terrain_wall_n_deep_underdark.png assets/art/board/terrain_floor_deep_underdark_1.png; do if [ ! -f "$GAME/$a" ]; then - echo "[golden_refresh] 拒跑 — 素材缺位: $a(S3 停机点口径:基线禁锚字形回退态,先补素材+导入边车)" + echo "[golden_refresh] 拒跑 — 素材缺位: ${a}(S3 停机点口径:基线禁锚字形回退态,先补素材+导入边车)" exit 1 fi done -timeout 180 "$G" --path "$GAME_WIN" res://probe/board_shot_probe.tscn -- "$TMP/shots" >"$TMP/run.log" 2>&1 +gate_run_timeout 180 "$TMP/run.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/board_shot_probe.tscn -- "$TMP/shots" st=$? grep -E "\[board_shot\]" "$TMP/run.log" | tail -6 if [ "$st" -ne 0 ] || [ ! -s "$TMP/shots/board_meta.json" ]; then - echo "[golden_refresh] FAIL — board_shot_probe 退出码=$st(meta/截图缺失见上)" + echo "[golden_refresh] FAIL — board_shot_probe 退出码=${st}(meta/截图缺失见上)" exit 1 fi python tools/golden_board_crop.py "$TMP/shots/board_meta.json" "$TMP/shots" "$TMP/crop" || exit 1 -mkdir -p "$REPO/game/tests/golden" +mkdir -p "$REPO/game/tests/golden/$PLAT" for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png; do - cp "$TMP/crop/$b" "$REPO/game/tests/golden/$b" || exit 1 + cp "$TMP/crop/$b" "$REPO/game/tests/golden/$PLAT/$b" || exit 1 done -echo "[golden_refresh] 完成 — 三张基线已换代入 game/tests/golden/(提交说明须写明触发原因)" +echo "[golden_refresh] 完成 — 三张基线已换代入 game/tests/golden/$PLAT/(提交说明须写明触发原因)" diff --git a/tools/golden_compare.py b/tools/golden_compare.py index c98701df..c87decc8 100644 --- a/tools/golden_compare.py +++ b/tools/golden_compare.py @@ -19,6 +19,14 @@ from PIL import Image, ImageChops TOLERANCE = 16 # 灰度差容差(单通道) FAIL_RATIO = 0.02 # changed 占比失败线 +# 基线平台子目录:<黄金图根目录>/——渲染基线按平台各存(gate_platform_id 同口径), +# 平台内部照常逐像素严格比对;跨平台不互比(字形/抗锯齿机制差异,2026-11-03 用户裁)。 +_PLAT = {"win32": "windows", "darwin": "mac"}.get(sys.platform, "linux") + + +def plat_missing(base: Path) -> bool: + return not (base / _PLAT).is_dir() + def compare(base: Path, curr: Path, name: str, failures: list) -> None: pb, pc = base / name, curr / name @@ -48,6 +56,11 @@ def main() -> int: print("用法: python tools/golden_compare.py <黄金图目录> <新截图目录> [屏清单...]") return 1 base, curr = Path(sys.argv[1]), Path(sys.argv[2]) + base = base / _PLAT + if not base.is_dir(): + print("[golden] FAIL — 本平台(%s)基线目录未建:%s" % (_PLAT, base)) + print(" 处置:在该平台跑 tools/golden_baseline_refresh.sh 重制基线并提交(基线换代权限 Q6)") + return 1 shots = sys.argv[3:] or ["shot_menu.png", "shot_camp.png", "shot_level.png", "shot_battle.png"] failures = [] for name in shots: diff --git a/tools/hooks/post-commit b/tools/hooks/post-commit deleted file mode 100644 index dc986266..00000000 --- a/tools/hooks/post-commit +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env bash -# 红态追绿钩子(本地 CI 触发钩子,2026-10-30 用户裁)——仅当最近一次 CI 判红时,本提交触发 -# 后台复验(tools/ci_runner.sh commit,判定在跑批件内);绿态与无状态零触发—— -# 小批直提不重复跑全量,同一份字节禁止第二次人手跑。主工作区限定。 -set -u -_GD="$(cd "$(git rev-parse --git-dir)" && pwd)" -_CD="$(cd "$(git rev-parse --git-common-dir)" && pwd)" -[ "$_GD" = "$_CD" ] || exit 0 -REPO="$(git rev-parse --show-toplevel)" -( nohup bash "$REPO/tools/ci_runner.sh" commit >/dev/null 2>&1 & ) -exit 0 diff --git a/tools/hooks/post-merge b/tools/hooks/post-merge index 13298b9c..f0ca94c6 100644 --- a/tools/hooks/post-merge +++ b/tools/hooks/post-merge @@ -1,12 +1,27 @@ #!/usr/bin/env bash -# 合回验证点(本地 CI 触发钩子,2026-10-30 用户裁)——主工作区 git merge 落地后无条件触发 -# 全量域闸后台跑批(tools/ci_runner.sh merge,约 4 分钟,不阻塞合并动作本身)。 -# 任务 worktree 内的合并不触发(CI 只认 master;worktree 批要提前验证就手动跑 ci_runner.sh)。 -# CI 红了不回滚合并——红项记入 game/tests/.gates/ci-status.json,修复提交经 post-commit 追绿。 +# 合回验证点(远端 CI 触发钩子,2026-11-03 用户裁)——主工作区 git merge 落地后: +# ① push origin master(触发远端 CI:Gitea 收到 push → 派发 mac-ci runner 按改动域跑闸) +# ② 后台挂 ci_watch.sh remote 等回执(回执落 ci-status.json,agent 下次开工读态零改动) +# 推送失败=远端验证不可能发生——大声呈报并落 push_failed 判定进 ci-status.json(防静默,下游开工读态即见)。 +# 任务 worktree 内的合并不触发(CI 只认 master);CI 红了不回滚合并——修复批合回后本钩子再推再验(追绿同链)。 set -u _GD="$(cd "$(git rev-parse --git-dir)" && pwd)" _CD="$(cd "$(git rev-parse --git-common-dir)" && pwd)" [ "$_GD" = "$_CD" ] || exit 0 REPO="$(git rev-parse --show-toplevel)" -( nohup bash "$REPO/tools/ci_runner.sh" merge >/dev/null 2>&1 & ) +TIP="$(git -C "$REPO" rev-parse HEAD)" +export PYTHONIOENCODING=utf-8 +if ! git -C "$REPO" push origin master >/dev/null 2>&1; then + echo "[post-merge] ✗ push origin master 失败——远端 CI 无法触发!先修推送(凭据/网络)后手动 git push,再手动挂 tools/ci_watch.sh remote $TIP" + python -I -c " +import json, os, datetime +p = r'$REPO/game/tests/.gates/ci-status.json' +d = {'commit': r'$TIP', 'verdict': 'push_failed', 'timestamp': datetime.datetime.now().astimezone().isoformat(timespec='seconds'), + 'duration_s': 0, 'note': 'post-merge 推送失败,远端 CI 未触发——修复推送后手动 git push + ci_watch remote', 'red_gates': [], 'key_lines': []} +os.makedirs(os.path.dirname(p), exist_ok=True) +json.dump(d, open(p, 'w', encoding='utf-8'), ensure_ascii=False, indent=1) +" 2>/dev/null + exit 0 +fi +( nohup bash "$REPO/tools/ci_watch.sh" remote "$TIP" >/dev/null 2>&1 & ) exit 0 diff --git a/tools/probe_gate.sh b/tools/probe_gate.sh old mode 100644 new mode 100755 index 839caa7d..fb1a29e2 --- a/tools/probe_gate.sh +++ b/tools/probe_gate.sh @@ -17,10 +17,12 @@ # 四件带断言探针;默认段断言面零删减零改动(--full 只增不改)。 # 渲染面归 render_gate.sh 正主(窗口化:shot_probe 七屏+fx_shot/board_shot 取证+棋盘 golden 逐像素)。 # 用法: tools/probe_gate.sh [game目录] [--click] [--full] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) -# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell;Godot 路径可用环境变量 GODOT_BIN 覆盖。 +# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell(Windows 侧);macOS/Linux 走 tools/gate_platform.sh +# 共享跨平台件(Godot 路径两平台都可用 GODOT_BIN 环境变量覆盖)。 set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行/孤儿清杀) GAME=""; WITH_CLICK=0; WITH_FULL=0 for a in "$@"; do case "$a" in @@ -30,7 +32,7 @@ for a in "$@"; do esac done GAME="${GAME:-$REPO/game}" -GAME_WIN="$(cygpath -m "$GAME")" # Godot --path 需 Windows 形式 +GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) TMP="$(mktemp -d /tmp/probegate_XXXXXX)" trap 'rm -rf "$TMP"' EXIT _rc=0 @@ -55,7 +57,7 @@ verdict() { # verdict <探针名> <日志路径> <退出码> [签名标记] echo "[probe_gate] FAIL — $name 超时挂死(exit=$st),孤儿已清"; return 1 fi if [ "$st" -ne 0 ]; then - echo "[probe_gate] FAIL — $name 退出码=$st(退出码为唯一权威判据,PASS 串不抵)"; return 1 + echo "[probe_gate] FAIL — $name 退出码=${st}(退出码为唯一权威判据,PASS 串不抵)"; return 1 fi if [ -n "$PY" ] && ! "$PY" "$REPO/tools/error_scan.py" "$log" >"$TMP/err.txt" 2>&1; then echo "[probe_gate] FAIL — $name 脚本错误扫描命中:" @@ -63,17 +65,16 @@ verdict() { # verdict <探针名> <日志路径> <退出码> [签名标记] return 1 fi if ! grep -q "$mark" "$log"; then - echo "[probe_gate] FAIL — $name 未见签名标记「$mark」(exit=$st——退出码 0 但零输出亦判红)"; return 1 + echo "[probe_gate] FAIL — $name 未见签名标记「${mark}」(exit=${st}——退出码 0 但零输出亦判红)"; return 1 fi return 0 } -orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(console wrapper+真引擎都算) - printf "Get-CimInstance Win32_Process -Filter \"Name like 'Godot%%'\" | Where-Object { \$_.CommandLine -like '*%s*' } | ForEach-Object { \$_.ProcessId }\n" "$GAME_WIN" > "$TMP/k.ps1" - powershell -NoProfile -ExecutionPolicy Bypass -File "$(cygpath -w "$TMP/k.ps1")" 2>/dev/null | tr -d '\r' +orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(实现单点在 gate_platform.sh,双平台) + gate_orphans "$GAME_WIN" } kill_orphans() { - local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done + gate_kill_orphans "$GAME_WIN" } # 每段跑批统一走本函数:后台启动记 MSYS PID→WINPID,守卫进程到点先 taskkill /T 整树回收 @@ -81,6 +82,14 @@ kill_orphans() { # 2026-10-25 在册挂死根源),后 kill -9 收 MSYS 侧。正常退出不动进程树;段后 kill_orphans 照跑兜底。 run_suite() { # run_suite <日志> <场景 res:// 路径> local t="$1" log="$2" scene="$3" + if ! gate_is_win; then + # macOS/Linux:无 console wrapper(Godot 单进程),走共享带超时执行+孤儿清杀兜底。 + local st + gate_run_timeout "$t" "$log" "$G" --headless --path "$GAME_WIN" "$scene" + st=$? + gate_kill_orphans "$GAME_WIN" + return "$st" + fi local kf="$TMP/killed.$RANDOM" rm -f "$kf" "$G" --headless --path "$GAME_WIN" "$scene" >"$log" 2>&1 & @@ -108,7 +117,7 @@ run_suite() { # run_suite <日志> <场景 res:// 路径> # 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付) if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then echo "[probe_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)" - timeout 300 "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 + gate_run_timeout 300 "$TMP/refresh.log" "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 kill_orphans fi @@ -183,8 +192,8 @@ fi echo "======== 残留检查(批末闸) ========" left="$(orphans | wc -l | tr -d ' ')" -total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)" -echo "本闸残留 Godot=$left(必须0) 全局 Godot 进程=$total(其他会话的仅报数不算账)" +total="$(gate_godot_count_all)" +echo "本闸残留 Godot=${left}(必须0) 全局 Godot 进程=${total}(其他会话的仅报数不算账)" if [ "$left" -ne 0 ]; then echo "[probe_gate] FAIL — 本闸残留未清零"; kill_orphans; _rc=1; fi if [ "$_rc" -eq 0 ]; then echo "[probe_gate] 全绿"; else echo "[probe_gate] 存在 FAIL,见上"; fi exit "$_rc" diff --git a/tools/render_gate.sh b/tools/render_gate.sh old mode 100644 new mode 100755 index d6443f25..ec25691f --- a/tools/render_gate.sh +++ b/tools/render_gate.sh @@ -27,22 +27,23 @@ # 窗口化运行会短暂弹 Godot 窗口(本机可开窗口径);禁改 --headless 跑本闸 # (headless 无渲染面,探针内 fail-fast 护栏会红——那是护栏生效不是闸红)。 # 用法: tools/render_gate.sh [game目录] 缺省 game 目录=脚本所在仓库的 game/(worktree 内自动指向该 worktree) -# 依赖: Git Bash(coreutils timeout/taskkill) + PowerShell + python(PIL);Godot 路径可用环境变量 GODOT_BIN 覆盖。 +# 依赖: tools/gate_platform.sh(双平台封装)+ python(PIL);Windows 侧另用 taskkill/PowerShell,macOS/Linux 走共享件;Godot 路径可用环境变量 GODOT_BIN 覆盖。 set -u G="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" REPO="$(cd "$(dirname "$0")/.." && pwd)" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行/孤儿清杀) GAME="${1:-$REPO/game}" -GAME_WIN="$(cygpath -m "$GAME")" # Godot --path 需 Windows 形式 +GAME_WIN="$(gate_game_path "$GAME")" # Godot --path 接受形式(Windows 混合斜杠,其他平台原样) +GOLDEN="$REPO/game/tests/golden/$(gate_platform_id)" # 本平台基线目录(按平台各存,比较器同口径) TMP="$(mktemp -d /tmp/rendergate_XXXXXX)" trap 'rm -rf "$TMP"' EXIT _rc=0 -orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(console wrapper+真引擎都算) - printf "Get-CimInstance Win32_Process -Filter \"Name like 'Godot%%'\" | Where-Object { \$_.CommandLine -like '*%s*' } | ForEach-Object { \$_.ProcessId }\n" "$GAME_WIN" > "$TMP/k.ps1" - powershell -NoProfile -ExecutionPolicy Bypass -File "$(cygpath -w "$TMP/k.ps1")" 2>/dev/null | tr -d '\r' +orphans() { # 列出命令行含本 game 路径的 Godot 进程 PID(实现单点在 gate_platform.sh,双平台) + gate_orphans "$GAME_WIN" } kill_orphans() { - local p; for p in $(orphans); do taskkill //F //PID "$p" >/dev/null 2>&1; done + gate_kill_orphans "$GAME_WIN" } scan_log() { # scan_log <段名> <日志>——脚本错误扫描;日志不可读(error_scan exit 2)同判红 @@ -57,7 +58,7 @@ scan_log() { # scan_log <段名> <日志>——脚本错误扫描;日志不 # 冷检出刷导入(.godot 不入 git,fresh worktree 首跑一次性付) if [ ! -f "$GAME/.godot/uid_cache.bin" ]; then echo "[render_gate] 冷检出:editor --quit 刷导入(一次性,可到 1-2 分钟)" - timeout 300 "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 + gate_run_timeout 300 "$TMP/refresh.log" "$G" --headless --path "$GAME_WIN" --editor --quit >/dev/null 2>&1 kill_orphans fi @@ -66,13 +67,13 @@ if [ "$pre" -gt 0 ]; then echo "[render_gate] 预清本闸残留 Godot×$pre"; k echo "======== shot_probe 七屏截图 → shot_checker A-D ========" SHOT="$TMP/shots" -timeout 180 "$G" --path "$GAME_WIN" res://probe/shot_probe.tscn -- "$SHOT" >"$TMP/shot.log" 2>&1 +gate_run_timeout 180 "$TMP/shot.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/shot_probe.tscn -- "$SHOT" st=$?; kill_orphans grep -E "\[shot_probe\]" "$TMP/shot.log" | tail -12 if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then echo "[render_gate] FAIL — shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 elif [ "$st" -ne 0 ] || ! grep -q "问题=0" "$TMP/shot.log"; then - echo "[render_gate] FAIL — shot_probe 未干净完成(exit=$st,缺失屏/空文件/路径断裂见上)"; _rc=1 + echo "[render_gate] FAIL — shot_probe 未干净完成(exit=${st},缺失屏/空文件/路径断裂见上)"; _rc=1 fi if [ "$_rc" -eq 0 ]; then python tools/shot_checker.py "$SHOT" shot_menu.png shot_settings.png shot_career.png shot_camp.png shot_level.png shot_battle.png shot_status.png | sed 's/^/[render_gate] /' @@ -83,13 +84,13 @@ scan_log "shot_probe" "$TMP/shot.log" echo "======== fx_shot_probe 三时刻取证(Q7) ========" FX="$TMP/fxshots" -timeout 180 "$G" --path "$GAME_WIN" res://probe/fx_shot_probe.tscn -- "$FX" >"$TMP/fx.log" 2>&1 +gate_run_timeout 180 "$TMP/fx.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/fx_shot_probe.tscn -- "$FX" st=$?; kill_orphans grep -E "\[fx_shot_probe\]" "$TMP/fx.log" | tail -4 if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then echo "[render_gate] FAIL — fx_shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 elif [ "$st" -ne 0 ]; then - echo "[render_gate] FAIL — fx_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1 + echo "[render_gate] FAIL — fx_shot_probe 退出码=${st}(取证失败=闸红)"; _rc=1 fi scan_log "fx_shot_probe" "$TMP/fx.log" fx_evidence() { # 取证双检:模式文件存在且非零字节(零字节/缺失任一即 FAIL——Q7) @@ -108,13 +109,13 @@ fx_evidence "fx_final.png" 1 echo "======== board_shot_probe L1-L3 取证(Q7) ========" BD="$TMP/boardshots" -timeout 180 "$G" --path "$GAME_WIN" res://probe/board_shot_probe.tscn -- "$BD" >"$TMP/bd.log" 2>&1 +gate_run_timeout 180 "$TMP/bd.log" "$G" --path "$GAME_WIN" --quit-after 3000 res://probe/board_shot_probe.tscn -- "$BD" st=$?; kill_orphans grep -E "\[board_shot\]" "$TMP/bd.log" | tail -6 if [ "$st" -eq 124 ] || [ "$st" -eq 137 ]; then echo "[render_gate] FAIL — board_shot_probe 超时挂死(exit=$st),孤儿已清"; _rc=1 elif [ "$st" -ne 0 ]; then - echo "[render_gate] FAIL — board_shot_probe 退出码=$st(取证失败=闸红)"; _rc=1 + echo "[render_gate] FAIL — board_shot_probe 退出码=${st}(取证失败=闸红)"; _rc=1 fi scan_log "board_shot_probe" "$TMP/bd.log" for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png board_meta.json; do @@ -122,8 +123,8 @@ for b in board_L1_dungeon.png board_L2_underdark.png board_L3_deep_underdark.png done echo "======== 棋盘 golden 逐像素(T2——裁剪→对比,容差 provisional) ========" -if [ ! -d "$REPO/game/tests/golden" ]; then - echo "[render_gate] FAIL — game/tests/golden/ 基线目录缺失(基线在库=验收断言②)"; _rc=1 +if [ ! -d "$GOLDEN" ]; then + echo "[render_gate] FAIL — 本平台($(gate_platform_id))基线目录缺失:${GOLDEN}(基线按平台各存、在库=验收断言②)"; _rc=1 elif [ ! -s "$BD/board_meta.json" ]; then echo "[render_gate] SKIP — board_meta.json 缺失(取证段已红),golden 对比不判" else @@ -136,7 +137,7 @@ else assets/art/board/terrain_wall_n_dungeon.png assets/art/board/terrain_floor_dungeon_1.png \ assets/art/board/terrain_wall_n_underdark.png assets/art/board/terrain_floor_underdark_1.png \ assets/art/board/terrain_wall_n_deep_underdark.png assets/art/board/terrain_floor_deep_underdark_1.png; do - [ -f "$GAME/$a" ] || { echo "[render_gate] FAIL — 棋盘素材缺位: $a(Q5:基线锚素材齐盘态,缺盘不得出验收结论)"; _asset_miss=1; } + [ -f "$GAME/$a" ] || { echo "[render_gate] FAIL — 棋盘素材缺位: ${a}(Q5:基线锚素材齐盘态,缺盘不得出验收结论)"; _asset_miss=1; } done if [ "$_asset_miss" -eq 0 ]; then CROP="$TMP/crop" @@ -154,8 +155,8 @@ else fi echo "======== 菜单 golden 逐像素(迭代 28 T4——启动态全屏基线)========" -if [ ! -s "$REPO/game/tests/golden/menu_boot.png" ]; then - echo "[render_gate] FAIL — game/tests/golden/menu_boot.png 基线缺失(菜单 golden 未建——tools/golden_baseline_refresh.sh --menu)"; _rc=1 +if [ ! -s "$GOLDEN/menu_boot.png" ]; then + echo "[render_gate] FAIL — $GOLDEN/menu_boot.png 基线缺失(菜单 golden 未建——本平台跑 tools/golden_baseline_refresh.sh --menu)"; _rc=1 elif [ ! -s "$SHOT/shot_menu.png" ]; then echo "[render_gate] SKIP — shot_menu.png 缺失(shot_probe 段已红),菜单 golden 不判" elif [ ! -f "$GAME/assets/fonts/ui_font_fusion_pixel_zh_hans.ttf" ] || [ ! -f "$GAME/assets/art/ui/button_stone.png" ]; then @@ -172,8 +173,8 @@ fi echo "======== 残留检查(批末闸) ========" left="$(orphans | wc -l | tr -d ' ')" -total="$(tasklist 2>/dev/null | grep -ci '^Godot' || true)" -echo "本闸残留 Godot=$left(必须0) 全局 Godot 进程=$total(其他会话的仅报数不算账)" +total="$(gate_godot_count_all)" +echo "本闸残留 Godot=${left}(必须0) 全局 Godot 进程=${total}(其他会话的仅报数不算账)" if [ "$left" -ne 0 ]; then echo "[render_gate] FAIL — 本闸残留未清零"; kill_orphans; _rc=1; fi if [ "$_rc" -eq 0 ]; then echo "[render_gate] 全绿"; else echo "[render_gate] 存在 FAIL,见上"; fi exit "$_rc" diff --git a/tools/run_all_gates.sh b/tools/run_all_gates.sh old mode 100644 new mode 100755 index 341b4efe..b2ad7363 --- a/tools/run_all_gates.sh +++ b/tools/run_all_gates.sh @@ -4,8 +4,8 @@ # 漏跑 probe_gate --full 的成因:清单散落三份文档、彼此不一致、且无机器校验)。 # # 跑完写回执 game/tests/.gates/receipt.json(commit+内容树哈希+各闸退出码+存量基线比对+时间戳); -# 本地 CI(tools/ci_runner.sh,2026-10-30 用户裁)读回执写 ci-status.json——强制验证点=合回 master -# 后 post-merge 钩子触发的全量跑批;本脚本手动调用(--code/--docs/全量档)只服务开发期按域跑批。 +# 远端 CI(2026-11-03 用户裁)读回执判定,回执经 tools/ci_watch.sh remote 轮询 Gitea API 写 ci-status.json—— +# 强制验证点=合回 master 后 post-merge 钩子推送触发的远端跑批;本脚本手动调用(--code/--docs/全量档)只服务开发期按域跑批。 # 跑闸时代码面带着未提交改动,回执会标 dirty_game(验的不是 HEAD 字节),CI 判定照常落账留痕。 # # 用法:tools/run_all_gates.sh [--code|--docs|--quick] @@ -44,6 +44,7 @@ GAME="$REPO/game" STAMP_DIR="$GAME/tests/.gates" RECEIPT="$STAMP_DIR/receipt.json" BASELINE="$STAMP_DIR/baseline.json" +. "$REPO/tools/gate_platform.sh" # 跨平台件(平台探测/路径形式/带超时执行) QUICK=0 DOMAIN="full" @@ -93,8 +94,10 @@ fi # ── python 解释器解析:优先带 PIL 的 gamedev venv(渲染闸的 shot_checker/golden_compare 依赖), # 回落 PATH 上的 python。渲染闸的三个脚本要 PIL,用错解释器会假红。 PY="" -for cand in "/d/dev/python/envs/gamedev/Scripts/python.exe" "$(command -v python 2>/dev/null)" "python"; do +for cand in "/d/dev/python/envs/gamedev/Scripts/python.exe" "$(command -v python 2>/dev/null)" "python3" "python"; do [ -z "$cand" ] && continue + # 存在性守卫先行:Windows venv 路径在非 Windows 平台不存在,误选会让 PY 指向幽灵路径整批崩。 + command -v "$cand" >/dev/null 2>&1 || [ -x "$cand" ] || continue if "$cand" -c "import PIL" >/dev/null 2>&1; then PY="$cand"; break; fi [ -z "$PY" ] && PY="$cand" done @@ -282,7 +285,7 @@ if in_domain code; then echo "[gates] 关联档选闸·申报:未找到在册需求卡片(diff 与 roadmap 进行中行均无),申报集为空——仅按 diff 推导" fi if [ "$SCOPE" = related ]; then - echo "[gates] 关联档选闸:02 选套件跑(--only=$ONLY_TERMS)" + echo "[gates] 关联档选闸:02 选套件跑(--only=${ONLY_TERMS})" fi fi fi @@ -338,7 +341,7 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分 local _shards="${GATE_SHARDS:-4}" echo "" echo "════════════════════════════════════════════" - echo " $name(--script 模式,退出码为唯一判据)" + echo " ${name}(--script 模式,退出码为唯一判据)" if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then echo " 关联档选跑:--only=$ONLY_TERMS" fi @@ -348,7 +351,7 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分 echo "════════════════════════════════════════════" local rc=0 if [ "$_shards" -le 1 ]; then - local -a _cmd=(--headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd) + local -a _cmd=(--headless --path "$(gate_game_path "$GAME")" --script res://tests/run_all.gd) [ ${#_user_args[@]} -gt 0 ] && _cmd+=(-- "${_user_args[@]}") ( cd "$REPO" && "$GODOT" "${_cmd[@]}" ) \ >"$STAMP_DIR/unit.log" 2>&1 @@ -360,7 +363,7 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分 local -a _pids=() local k for k in $(seq 1 "$_shards"); do - ( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd \ + ( cd "$REPO" && "$GODOT" --headless --path "$(gate_game_path "$GAME")" --script res://tests/run_all.gd \ -- "${_user_args[@]}" "--shard=$k" "--shard-count=$_shards" ) \ >"$STAMP_DIR/unit_shard_$k.log" 2>&1 & _pids+=($!) diff --git a/tools/run_smoke.py b/tools/run_smoke.py index f6b756cd..cbf34fa8 100644 --- a/tools/run_smoke.py +++ b/tools/run_smoke.py @@ -18,13 +18,15 @@ r"""run_smoke.py — 冒烟测试运行器(脚本错误外部门禁) ③ 输出无「SCRIPT ERROR」「Parse Error」标记行(标记清单供 tools/error_scan.py 复用,禁另写一份)。 """ import argparse +import os import re import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -DEFAULT_GODOT = r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" +# 引擎定位:GODOT_BIN 环境变量优先(远端 CI 与非 Windows 平台的注入口),Windows 本机固定路径保底。 +DEFAULT_GODOT = os.environ.get("GODOT_BIN") or r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" DEFAULT_SLICE = ROOT / "game" DEFAULT_SCENE = "res://tests/smoke_boot.tscn" SUMMARY_RE = re.compile(r"\[smoke\] 摘要:(\d+) 项检查,(\d+) 项失败") diff --git a/tools/run_system_test.py b/tools/run_system_test.py index 9d49bbe2..43711a3c 100644 --- a/tools/run_system_test.py +++ b/tools/run_system_test.py @@ -19,13 +19,15 @@ r"""run_system_test.py — 系统测试运行器(脚本错误外部门禁) ③ 输出无「SCRIPT ERROR」「Parse Error」标记行(标记清单经 tools/error_scan.py 复用,禁另写一份)。 """ import argparse +import os import re import subprocess import sys from pathlib import Path ROOT = Path(__file__).resolve().parent.parent -DEFAULT_GODOT = r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" +# 引擎定位:GODOT_BIN 环境变量优先(远端 CI 与非 Windows 平台的注入口),Windows 本机固定路径保底。 +DEFAULT_GODOT = os.environ.get("GODOT_BIN") or r"D:\code\godot\Godot_v4.7.2-stable_win64_console.exe" DEFAULT_SLICE = ROOT / "game" DEFAULT_SCRIPT = "res://tests/system_test.gd" # 系统测试宿主 SUMMARY_RE = re.compile(r"(?:冒烟完成|\[system_test\] 摘要):(\d+) 项检查,(\d+) 项失败") diff --git a/tools/setup_worktree_userdir.sh b/tools/setup_worktree_userdir.sh old mode 100644 new mode 100755 diff --git a/tools/sync_worktree_assets.sh b/tools/sync_worktree_assets.sh old mode 100644 new mode 100755 diff --git a/体验验证清单.md b/体验验证清单.md index 7c36e08b..71e352e7 100644 --- a/体验验证清单.md +++ b/体验验证清单.md @@ -590,3 +590,17 @@ - **红灯信号**:窗壳挤压屏面、场景几乎不可见、滚动卡滞或内容溢出窗板即报缺陷。 - **结论回流**:观感校准意见回填 camp.md 终值(普通设计决策);呈现缺陷归待办清单登记新条目。 + +## ㊾ CI 迁远端工程批——Mac 端渲染基线目检(2026-11-03 随工程批登记) + +> CI 迁移远端(Mac 本机 host 模式)工程批:渲染基线按平台各存(windows/ 已有,mac/ 待 Mac 端重制入库)。 +> 基线重制与目检在批次端到端验收时执行(Mac 端跑 tools/golden_baseline_refresh.sh 生成后人工判定)。 +> 登记不阻塞(2026-10-12 真人检测立规)。 + +| # | 目检项 | 判据 | +|---|---|---| +| 1 | Mac 端 golden 基线正确性 | mac/ 子目录四张基线(棋盘三图+menu_boot)重制生成后人工判定:布局骨架与现网一致、无素材缺位字形回退、无渲染异常(错位/透明块/花屏);判定通过后经下一批提交入库,此后渲染闸在 Mac 上以该套基线逐像素比对 | +| 2 | Mac 端渲染闸全链目检 | 远端游戏码批跑渲染闸时 Mac 桌面短暂弹 Godot 窗口属预期(窗口化截图取证);七屏截图与三段取证文件齐全、shot_checker A–D 无红 | + +- **红灯信号**:基线帧出现素材缺位字形、明显错位或花屏;渲染闸报「取证缺失/零字节」。 +- **结论回流**:基线异常=登记待办清单(或当场重制复核);渲染缺陷归待办清单登记新条目。