补提交(布局几何单点化批·代码面+工具面+architecture行·跨会话遗留): battle_layout.tres 新表单行五字段(stage_h/info_panel_w/ap_bar_h/log_band_h_dig/log_band_h_settle)经 schema battle_layout_table.gd+域容器 config_domain_presentation.gd 收进 ConfigDb.presentation(ConfigDB 三容器→四容器,manifest 增 battle_layout 键,B区高度裁决 2026-10-30 用户裁=取代码标准 stage_h);views 五件构造签名增 layout 参(BattleStageView/HeroInfoPanelView/BoardZoneView/ApBarView/BattleLogView 常量高度改表值驱动)+ui_assembler 装配传 layout+probes 两件改址+main.gd 结算带注释去数值;tools/layout_config.py 新件+render_sample_sheet 两版读布局表现算(样张工具面);文档面: architecture.md ConfigDB 行换代(四容器成员 7/7/5/1、类变量 10)+HUD框架.md 三区表/装板不等式/调参表去数值化改指布局表+数据配置.md+interaction-patterns.md 明细带句去数值(battle.md 区表与待办清单登记面已随 1cdef6ba 在库);消费漏网 L1 就地补改九处(构造签名换型与 iter45 断点件收编的测试改址漏网,判据零变化): theme_projection_test:386/player_copy_sweep_test:44,98/adventure_feats_test:772/standee_fx_sweep_test:75/feat_vouchers_test:292 六处补 layout 实参,adventure_feats_test:791/level_shop_economy_test:1480/feat_vouchers_test:81 三处断点成员直读改经 a._bp_store(iter45卡②六成员搬移漏网,02 闸合回后首全量暴露);load_tables 超函数长键清偿(容器装配块抽 _assign_loaded 私有协函数——表行按域收进四容器+resolver 注入单一职责,与 _cross_validate 委托先例同型,S13 新增红修至基线口径内);窄验: 四件修复套件单跑全绿+gdstyle 基线外新违规 0
This commit is contained in:
1 parent
39907d82f6
commit
4b174877be
29 files changed
+231
-118
No files matched your search
@@ -85,7 +85,7 @@
|
||||
|
||||
| 模块 | 独占职责 | 对外暴露 | 消费依赖 | 引擎 API |
|
||||
|---|---|---|---|---|
|
||||
| **ConfigDB**(autoload,D2) | 全部玩法数值表(`data/tables/*.tres` + `data/schemas/*.gd`);SCHEMA_VERSION;加载/校验/override/诊断状态机。**行族访问器归位各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面;表持有成员按域收进三张域容器 Resource(`config_domain_combat/run/crossrun.gd`——已建件(迭代 41),类型化成员 7/7/5);三张 resolver 表(board_gen.resolve_row/enemy.resolve_rowset/difficulty.resolve_modifiers)经 `load_tables` 装配段注入各容器,resolver 文件与键零搬移**——红线不变:本类仍是唯一加载与持有单点,"经 ConfigDB 单点读取"=经其持有的表对象 | 类型化域容器属性(每域一张容器);公开面 8(push_diag/is_fatal/reset_diagnostics/diagnostics/diagnostics_count/load_tables/set_reload_gate/reload)、类变量 9 | 无(基础层顶点) | Resource/.tres 加载、autoload 生命周期——LOW |
|
||||
| **ConfigDB**(autoload,D2) | 全部玩法数值表与表现层布局配置表(`data/tables/*.tres` + `data/schemas/*.gd`);SCHEMA_VERSION;加载/校验/override/诊断状态机。**行族访问器归位各 schema 表类(表管自己的行查询),本类只留加载/校验/override/诊断门面;表持有成员按域收进四张域容器 Resource(`config_domain_combat/run/crossrun/presentation.gd`——前三件迭代 41 已建件,第四件表现层布局域 2026-10-30 建件,类型化成员 7/7/5/1);三张 resolver 表(board_gen.resolve_row/enemy.resolve_rowset/difficulty.resolve_modifiers)经 `load_tables` 装配段注入各容器,resolver 文件与键零搬移**——红线不变:本类仍是唯一加载与持有单点,"经 ConfigDB 单点读取"=经其持有的表对象 | 类型化域容器属性(每域一张容器);公开面 8(push_diag/is_fatal/reset_diagnostics/diagnostics/diagnostics_count/load_tables/set_reload_gate/reload)、类变量 10 | 无(基础层顶点) | Resource/.tres 加载、autoload 生命周期——LOW |
|
||||
| **SlotStore**(`core/slot_store.gd`,D3 存档槽位域件,已建件——迭代 41 存量债批) | 存档三槽位文件域唯一所有者(自 save_manager 收编槽位族):槽位挂载与模板、激活与建删、槽位摘要窥视(peek 族);改名覆盖原语(_rename_overwrite)留 save_manager(IO 原语单点) | 槽位读写原语族+摘要投影(公开 10/类变量 4;SaveManager 与菜单消费方经 `save.slots.` 直连) | save_manager(注入路径与模板配置);FileAccess——LOW |
|
||||
| **MetaStateStore**(`core/meta_state_store.gd`,D3 存档内元进度数据件,已建件——迭代 41 存量债批) | 存档内元进度数据的读写唯一层(自 save_manager 收编 accessor 族:钥石/道具解锁/树节点与叉侧/锻造路线/同调/真视/计数器/摘要——数据读写原语,不含业务规则);save_manager 与 meta_progression 双 God Class 的共同病根=该数据域摊在两文件,本件即其归一 | 写口原语 13+只读投影 6(tree_state/forge_state/attune_state/career_state 四投影+keystone_balance/item_unlocked)=公开 19(add_keystone 私有 `_add_keystone`),零 _data 副本零缓存;MetaProgression 业务层与营地/锻造 UI 消费方经 `save.meta_state.` 直连(meta 的纯转发读方法已退役) | save_manager(序列化接线);FileAccess——LOW |
|
||||
| **RngService**(autoload,D2) | master_seed、各流 scope 计数器(封闭流词表现值以 `RngServiceCore.PURPOSE_TAGS` 为准;增流走 P1 评审) | `set_master_seed()`(唯一调用方 C2);`open_stream(stream)` 有状态门面;`derive_at(stream, scope)` 纯函数;只读计数器 | ConfigDB(无) | 无——纯逻辑 |
|
||||
|
||||
@@ -55,13 +55,13 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
|
||||
三区各管一块:
|
||||
|
||||
| 区 | 位置与尺寸 | 管什么 |
|
||||
| 区 | 位置与几何 | 管什么 |
|
||||
|---|---|---|
|
||||
| B 战斗画面 | 全宽 × 270px | 主角站桩立绘、敌人站桩立绘 ×1–3(**全站桩形态,2026-09-20 用户拍板:无框立绘+底座、状态小图标挂血条下,英雄卡/敌人卡退役**)、轮次阶段指示、结算明细带 |
|
||||
| C 角色信息 | 左下 240×530 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 |
|
||||
| D 扫雷图 | 中下约 1036×530(原右下面板删除后横向加宽) | 行动点条与按钮组、棋盘本体、toast 槽 |
|
||||
| B 战斗画面 | 全宽,高读布局表 `stage_h` | 主角站桩立绘、敌人站桩立绘 ×1–3(**全站桩形态,2026-09-20 用户拍板:无框立绘+底座、状态小图标挂血条下,英雄卡/敌人卡退役**)、轮次阶段指示、结算明细带 |
|
||||
| C 角色信息 | 左下,宽读 `info_panel_w`,高 = 基准屏高 − B 区高 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 |
|
||||
| D 扫雷图 | 中下,宽 = 基准屏宽 − C 区宽 − 列间距(原右下面板删除后横向加宽),高同 C 区 | 行动点条与按钮组、棋盘本体、toast 槽 |
|
||||
|
||||
分区尺寸与组件落位的像素级裁决归 [battle.md](../../ux/battle.md);三区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。节点链进度不在战斗屏呈现——它由关卡画面(选路覆盖层常驻形态)的节点态承载,见[关卡节点地图](../冒险循环/关卡节点地图.md)。
|
||||
分区几何的唯一事实源 = `game/data/tables/battle_layout.tres`(经 `ConfigDb.presentation.battle_layout` 读取);分区尺寸与组件落位的像素级裁决归 [battle.md](../../ux/battle.md),两份文档只写结构关系、不复写数值。三区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。节点链进度不在战斗屏呈现——它由关卡画面(选路覆盖层常驻形态)的节点态承载,见[关卡节点地图](../冒险循环/关卡节点地图.md)。
|
||||
|
||||
**结论**:棋盘格边长由 [battle.md](../../ux/battle.md)「棋盘格尺寸公式」按棋盘区实测面积与板型算出;本文只判"塞不塞得进",不复制算式与算例。**分辨率档位(2026-09-30 迭代 9 三裁决案甲修订)**:两档=1280×800 基准档(本表全部定值的口径)+1920×1080 档=**全局等比**(stretch canvas_items+aspect keep——16:10 设计在 16:9 窗口为等比 1.35×+左右黑边各 ~96px);本节与各屏定值一律按 1280 基准档书写,1920 档由引擎等比承载、零重排。
|
||||
|
||||
@@ -74,9 +74,9 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
| 变量 | 含义 | 现行值 |
|
||||
|---|---|---|
|
||||
| N | 棋盘边长格数 | 读[棋盘生成](../排雷循环/棋盘生成.md)(现行四档;事实源 board_gen.tres) |
|
||||
| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 由 D 区尺寸扣行动点条 44px 得出,本表不复制现算值 |
|
||||
| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 由 D 区尺寸扣行动点条高(读 `ap_bar_h`)得出,本表不复制现算值 |
|
||||
|
||||
装板可行性由上面的不等式保证;格边长的现行算例与可读下限见 [battle.md](../../ux/battle.md)。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。
|
||||
装板可行性由上面的不等式保证;格边长的可读下限见 [battle.md](../../ux/battle.md),算例由样张渲染脚本按布局表现算、不入文档。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。
|
||||
|
||||
### 提示与弹层:轻提示随起随消、重弹层排队进出
|
||||
|
||||
@@ -244,7 +244,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
|
||||
## 调参表
|
||||
|
||||
本系统的参数全部是呈现节奏与可读性常量,不是玩法数值——玩法数值一律归[数据配置](数据配置.md)的表,与本表无关。修订本文常量走文档修订流程。**本表常量无数据表承载,为文档面唯一事实源;入驻 .tres 时本列改指路。**
|
||||
本系统的参数全部是呈现节奏与可读性常量,不是玩法数值——玩法数值一律归[数据配置](数据配置.md)的表,与本表无关。修订本文常量走文档修订流程。**本表常量无数据表承载,为文档面唯一事实源;入驻 .tres 时本列改指路。** 布局几何(B 区高 / C 区宽 / AP 条高 / 明细带两态高)已入驻 `game/data/tables/battle_layout.tres`,不在本表重复。
|
||||
|
||||
| 参数 | 域 | 现行值(文档自持,无表承载) | 说明 |
|
||||
|---|---|---|---|
|
||||
@@ -254,7 +254,6 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
| clear_celebration_ms | 每次清图 | 3000–5000 | 上限约束:三五秒即收 |
|
||||
| font_min_board / font_min_detail / font_min_tooltip | 1280×800 基座 | 18 / 14 / 13 px | 可访问性基线 |
|
||||
| reduce_motion_scale | 每段动画 | 0.5 或跳终态 | 减动效开关系数 |
|
||||
| detail_band_height | 结算明细带 | 24 / 84 px | 摘要 / 实时流两态高度 |
|
||||
|
||||
结构冻结(非调参项,改动走文档修订):三区布局与分区尺寸、双舞台常驻、单意图帧纪律、弹层层级纪律、图标键映射契约、程序化占位策略、订阅纪律。性能预算为引用值:同屏 draw calls < 200、进入战斗屏 500ms 内可交互。
|
||||
|
||||
@@ -262,7 +261,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
|
||||
以下判据逐条可测,全部通过才算闭环达标。
|
||||
|
||||
- 三区无遮挡、无重叠、无内容裁切(棋盘全部格子与数字完整可见),分区尺寸与 battle.md 布局表一致;同屏 draw calls < 200;进入战斗屏 500ms 内首格可点。
|
||||
- 三区无遮挡、无重叠、无内容裁切(棋盘全部格子与数字完整可见),分区几何与布局表 `battle_layout.tres` 一致;同屏 draw calls < 200;进入战斗屏 500ms 内首格可点。
|
||||
- 任意板型装板后网格实高不超出棋盘区:N × 格边长 + (N−1) × 2px ≤ 棋盘区可用高;数字字号与整数倍缩放判据见 battle.md。
|
||||
- 已开 / 未开格态在灰度截图下可逐格分类;已引爆危险格"可通行可读"(已开底加巢穴纹、可作移动目标)。
|
||||
- 类型微光:微光只在英雄周围 8 格内的未开格显示;出圈即灭、入圈即亮;已开格与封锁格恒无微光;微光呈现不随格是雷是安变化(不泄危险格与安全格)。(迭代 30 卡①实装后转正式验收)
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
| 关卡表 | 逐关节点图与层名 | [冒险结构](../冒险循环/冒险结构.md) |
|
||||
| 冒险表 | 关卡大奖励与钥石入账(单行全局) | [冒险结构](../冒险循环/冒险结构.md) |
|
||||
| 元进度表 | 元进度字段(已建表 2026-09-21——现行五行族:属性锻造 / Boss 钥石 / 工坊阈值 / 换算率 / 同调刷新升级行族,顺带扩列) | [元进度](../跨局循环/元进度.md) |
|
||||
| 战斗屏布局表(`battle_layout.tres`) | 分区几何与明细带两态高(单行;呈现层布局配置,非玩法数值——供呈现层与样张工具同源读取) | [战斗屏规格](../../ux/battle.md)(结构)/[HUD框架](HUD框架.md)(呈现常量) |
|
||||
|
||||
举证:本节为数据纪律约定,无计算。违规检测 = 静态扫描加人工复核,随各表回填定稿与里程碑检查执行。
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
|
||||
**Category**: Data Display
|
||||
**Used In**: 战斗画面区底部(结算期第一注视点)
|
||||
|
||||
**Description**: 轮末结算逐行流水实时呈现 + 可回看——「扫雷之因逐行兑现为战果」(P2 归因)的承载面。挖掘期收起为 1 行上轮摘要;结算期展开 84px 实时流;点击开完整回看 overlay。
|
||||
**Description**: 轮末结算逐行流水实时呈现 + 可回看——「扫雷之因逐行兑现为战果」(P2 归因)的承载面。挖掘期收起为 1 行上轮摘要;结算期展开为实时流(带高读 `log_band_h_settle`);点击开完整回看 overlay。
|
||||
|
||||
**Specification**:
|
||||
- 数据源:[战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行封闭协议(现行);公共字段:来源类别 / 来源格 / 轮次 / 显示名 / 数值 / 可空高光标记);**无来源行 = 断言失败**
|
||||
|
||||
+15
-5
@@ -1,8 +1,9 @@
|
||||
## VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
## 数值表单点访问入口:全部 .tres 表的加载、校验诊断与门面(垂直切片验证:改表即改玩法,数值零硬编码)。
|
||||
## 玩法代码禁止绕过本单点自建 load();行查询归各表类(表管自己的行),
|
||||
## 表持有按域收进三容器(combat 战斗编排域/run 冒险 runner 域/crossrun 跨局元进度域),
|
||||
## 消费面经 db.<容器>.<表> 直连。「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88)。
|
||||
## 表持有按域收进四容器(combat 战斗编排域/run 冒险 runner 域/crossrun 跨局元进度域/
|
||||
## presentation 表现层布局域),消费面经 db.<容器>.<表> 直连。
|
||||
## 「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88)。
|
||||
## 冒烟脚本可显式实例化本类(headless --script 亦会加载 autoload——共享 .tres 上的 resolver 归最后装载的实例);
|
||||
## 加载失败 is_ready=false 加 fatal 封印,调用方须查。
|
||||
class_name ConfigDb
|
||||
@@ -28,11 +29,13 @@ const TABLE_MANIFEST := {
|
||||
"adventure": "adventure.tres", # AdventureTable(大奖励与钥石行值单点)
|
||||
"feats": "feats.tres", # FeatTable 规则改写型特性(定义表 Rule 4 豁免——TreasureItemTable 先例;空表合法态=主时间线零扰动)
|
||||
"forge": "forge.tres", # ForgeTable——两根类型轴默认值+六路线行族;攻防所有权切分唯一数据面
|
||||
"battle_layout": "battle_layout.tres", # BattleLayoutTable——战斗屏分区几何与明细带高度单行
|
||||
}
|
||||
|
||||
var combat: ConfigDomainCombat # 战斗编排域七表(board_gen/variation/status_effects/enemy/skill/hero/settlement)
|
||||
var run: ConfigDomainRun # 冒险 runner 域七表(level/difficulty/level_gen_params/reward/adventure/action_economy/shop)
|
||||
var crossrun: ConfigDomainCrossrun # 跨局元进度域五表(meta/meta_tree/treasure_items/feats/forge)
|
||||
var presentation: ConfigDomainPresentation # 表现层布局域一表(battle_layout)
|
||||
|
||||
var is_ready := false
|
||||
var _diagnostics: Array[Dictionary] = [] # {severity, category, message}
|
||||
@@ -45,6 +48,7 @@ func _init() -> void:
|
||||
combat = ConfigDomainCombat.new()
|
||||
run = ConfigDomainRun.new()
|
||||
crossrun = ConfigDomainCrossrun.new()
|
||||
presentation = ConfigDomainPresentation.new()
|
||||
_resolver = ConfigDbResolver.new(push_diag,
|
||||
func() -> Variant: return combat.board_gen.rows, func() -> Variant: return combat.enemy.rows, func() -> Variant: return run.difficulty) # 协作类持门面引用(诊断/致命通道回呼)
|
||||
|
||||
@@ -117,7 +121,14 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
||||
if has_errors and not collect_only:
|
||||
# 游戏通道:致命(Rule 6)——is_ready 恒 false,调用方(主场景)中止构建
|
||||
return false
|
||||
# 容器装配(19 表成员按域收进三容器——2026-10-27 全清设计定稿)
|
||||
# 容器装配(表行按域收进四容器+resolver 注入)归私有协函数——load_tables 只留加载/校验/致命门面
|
||||
_assign_loaded(loaded)
|
||||
is_ready = not has_errors
|
||||
return not has_errors
|
||||
|
||||
|
||||
# 容器装配协函数:20 表成员按域收进四容器(2026-10-27 全清设计定稿,2026-10-31 增表现层布局域)
|
||||
func _assign_loaded(loaded: Dictionary) -> void:
|
||||
combat.board_gen = loaded.get("board_gen")
|
||||
combat.variation = loaded.get("variation")
|
||||
combat.status_effects = loaded.get("status_effects")
|
||||
@@ -137,12 +148,11 @@ func load_tables(root: String, collect_only: bool) -> bool:
|
||||
crossrun.treasure_items = loaded.get("treasure_items")
|
||||
crossrun.feats = loaded.get("feats")
|
||||
crossrun.forge = loaded.get("forge")
|
||||
presentation.battle_layout = loaded.get("battle_layout")
|
||||
# resolver 协作件注入三表(resolve 委托承载——resolver 文件与键零搬移)
|
||||
combat.board_gen._resolver = _resolver
|
||||
combat.enemy._resolver = _resolver
|
||||
run.difficulty._resolver = _resolver
|
||||
is_ready = not has_errors
|
||||
return not has_errors
|
||||
|
||||
|
||||
# 跨表校验(悬空引用 / 池护栏——全部加载期致命)本体:
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
## 表现层布局域容器(D2 域容器——config_db 表持有按域收敛):战斗屏布局一表类型化持有。
|
||||
## 零方法零行为——纯持有结构;「经 ConfigDB 单点读取」=经其持有的容器与表对象(架构 :88 定稿注记)。
|
||||
class_name ConfigDomainPresentation
|
||||
extends Resource
|
||||
|
||||
var battle_layout: BattleLayoutTable
|
||||
@@ -0,0 +1 @@
|
||||
uid://bay1b1s3lrd1e
|
||||
@@ -0,0 +1,30 @@
|
||||
## 战斗屏布局表 schema:分区几何与明细带高度的单行类型化读取(呈现层布局配置,非玩法数值)。
|
||||
## 消费面 = BattleStageView / BattleLogView / HeroInfoPanelView / ApBarView(经 ConfigDb.presentation.battle_layout);
|
||||
## 文档只写结构关系与表名,不复制数值。validate() 违例加载期致命(fail-fast)。
|
||||
class_name BattleLayoutTable
|
||||
extends Resource
|
||||
|
||||
@export var stage_h: int = 252 # B 区高(全宽战斗画面,含底部明细带)
|
||||
@export var info_panel_w: int = 240 # C 区宽(左下角色信息列)
|
||||
@export var ap_bar_h: int = 44 # D 区 AP 条高
|
||||
@export var log_band_h_dig: int = 24 # 明细带挖掘期收起高(单行上轮摘要)
|
||||
@export var log_band_h_settle: int = 84 # 明细带结算期展开高(实时结算流)
|
||||
|
||||
|
||||
func validate() -> Array:
|
||||
var errs: Array[Dictionary] = []
|
||||
if stage_h < 120 or stage_h > 600:
|
||||
errs.append({"category": "domain", "message": "stage_h 越域 [120,600]: %d" % stage_h})
|
||||
if info_panel_w < 120 or info_panel_w > 480:
|
||||
errs.append({"category": "domain", "message": "info_panel_w 越域 [120,480]: %d" % info_panel_w})
|
||||
if ap_bar_h < 24 or ap_bar_h > 96:
|
||||
errs.append({"category": "domain", "message": "ap_bar_h 越域 [24,96]: %d" % ap_bar_h})
|
||||
if log_band_h_dig < 16 or log_band_h_dig > 80:
|
||||
errs.append({"category": "domain", "message": "log_band_h_dig 越域 [16,80]: %d" % log_band_h_dig})
|
||||
if log_band_h_settle < 24 or log_band_h_settle > 160:
|
||||
errs.append({"category": "domain", "message": "log_band_h_settle 越域 [24,160]: %d" % log_band_h_settle})
|
||||
if log_band_h_settle < log_band_h_dig:
|
||||
errs.append({"category": "cross_field", "message": "log_band_h_settle(%d) < log_band_h_dig(%d)——展开态不得矮于收起态" % [log_band_h_settle, log_band_h_dig]})
|
||||
if log_band_h_settle >= stage_h:
|
||||
errs.append({"category": "cross_field", "message": "log_band_h_settle(%d) ≥ stage_h(%d)——明细带不得占满 B 区" % [log_band_h_settle, stage_h]})
|
||||
return errs
|
||||
@@ -0,0 +1 @@
|
||||
uid://b3jh66yq4b7rp
|
||||
@@ -0,0 +1,11 @@
|
||||
[gd_resource type="Resource" script_class="BattleLayoutTable" load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://data/schemas/battle_layout_table.gd" id="1"]
|
||||
|
||||
[resource]
|
||||
script = ExtResource("1")
|
||||
stage_h = 252
|
||||
info_panel_w = 240
|
||||
ap_bar_h = 44
|
||||
log_band_h_dig = 24
|
||||
log_band_h_settle = 84
|
||||
@@ -634,7 +634,7 @@ func _refresh_presentations() -> void:
|
||||
|
||||
|
||||
func _sync_settlement_visuals() -> void:
|
||||
# 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开 84px
|
||||
# 结算阶段(round_end → 下一轮):D 区去饱和+金橙偏移锁输入(明度不变)+ 明细带展开(高度读 battle_layout)
|
||||
var settling := _is_settling()
|
||||
if settling and not run_scope.was_settling:
|
||||
ui.battle.settlement_fx.play_round_sweep(ui.battle.board_zone.board_view.get_global_rect()) # 结算轮光带——时序敏感演出触发,例外留在组合根
|
||||
|
||||
@@ -6,8 +6,8 @@ extends RefCounted
|
||||
|
||||
|
||||
# 战斗屏三区(B 战斗画面 / C·D 两列——节点链
|
||||
# 进度由关卡画面本体(选路覆盖层整图)承载,28px 纵向空间拨给 C/D 底行 520→548;
|
||||
# 无顶栏进度条、无道具面板区)。
|
||||
# 进度由关卡画面本体(选路覆盖层整图)承载,纵向空间归 C/D 底行;
|
||||
# 无顶栏进度条、无道具面板区)。分区几何读 ConfigDb.presentation.battle_layout。
|
||||
# + 结算演出覆盖层(A–D 之上、模态之下——跨区弹道/光带载体)。
|
||||
# 返回句柄字典:{stage, log_view, info_panel, board_zone, board_view, ap_bar, settlement_fx}
|
||||
# (返回句柄不含 gp_label/keystone_label——GP/钥石不在战斗屏呈现)
|
||||
@@ -17,20 +17,22 @@ static func build_battle_ui(root: Control, db: ConfigDb) -> Dictionary:
|
||||
root.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT)
|
||||
root.add_theme_constant_override("separation", 0)
|
||||
|
||||
# B 区:战斗画面(全宽 × 252px——常驻双舞台,布局决策 D1)
|
||||
var stage := BattleStageView.new(db)
|
||||
var layout: BattleLayoutTable = db.presentation.battle_layout
|
||||
|
||||
# B 区:战斗画面(全宽 × battle_layout.stage_h——常驻双舞台,布局决策 D1)
|
||||
var stage := BattleStageView.new(db, layout)
|
||||
root.add_child(stage)
|
||||
|
||||
# C/D 两列(548px——含顶栏让出的 28px 纵向空间)
|
||||
# C/D 两列(高度 = 基座高 − B 区高——底行承载)
|
||||
var bottom := HBoxContainer.new()
|
||||
bottom.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
bottom.add_theme_constant_override("separation", 4)
|
||||
root.add_child(bottom)
|
||||
|
||||
var info_panel := HeroInfoPanelView.new()
|
||||
var info_panel := HeroInfoPanelView.new(layout)
|
||||
bottom.add_child(info_panel)
|
||||
|
||||
var board_zone := BoardZoneView.new()
|
||||
var board_zone := BoardZoneView.new(layout)
|
||||
bottom.add_child(board_zone)
|
||||
board_zone.ap_bar.set_refresh_cost(int(db.run.action_economy.rap_refresh_cost)) # RAP 读数表值驱动
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ var _arm_timer := ArmedTimer.new() # 弃图刷新确认窗(T3/D3 P4 乙—
|
||||
const REFRESH_CONFIRM_WINDOW_S := 4.0 # 弃图刷新确认窗(原 tween_interval 字面量升常量——语义零变化)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
custom_minimum_size = Vector2(0, 44)
|
||||
func _init(layout: BattleLayoutTable) -> void:
|
||||
custom_minimum_size = Vector2(0, layout.ap_bar_h)
|
||||
_arm_timer.expired.connect(_disarm)
|
||||
var sb := UiKit.panel_flat(UiKit.PANEL_DATA, UiKit.PANEL_BORDER, 0, 1, Vector4(8, 4, 8, 4))
|
||||
add_theme_stylebox_override("panel", sb)
|
||||
|
||||
@@ -4,12 +4,12 @@ class_name BattleLogView
|
||||
extends PanelContainer
|
||||
|
||||
|
||||
const BAND_H_DIG := 24
|
||||
const BAND_H_SETTLE := 84
|
||||
const AGG_TYPES := ["reward_ineffective"]
|
||||
const COL_GOLD := UiKit.HEX_GOLD
|
||||
|
||||
var _db
|
||||
var _band_h_dig := 0
|
||||
var _band_h_settle := 0
|
||||
var _log: RichTextLabel
|
||||
var _summary: Label
|
||||
var _hint: Label
|
||||
@@ -18,9 +18,11 @@ var _round_rows: Array[String] = [] # 本结算轮行缓冲(上轮摘要
|
||||
var _pending_agg := {} # 聚合计数行 pending(type+batch_key → {row, count})
|
||||
|
||||
|
||||
func _init(db_ref) -> void:
|
||||
func _init(db_ref, layout: BattleLayoutTable) -> void:
|
||||
_db = db_ref
|
||||
custom_minimum_size = Vector2(0, BAND_H_DIG)
|
||||
_band_h_dig = layout.log_band_h_dig
|
||||
_band_h_settle = layout.log_band_h_settle
|
||||
custom_minimum_size = Vector2(0, _band_h_dig)
|
||||
size_flags_vertical = Control.SIZE_SHRINK_END
|
||||
var vb := VBoxContainer.new()
|
||||
vb.add_theme_constant_override("separation", 2)
|
||||
@@ -29,7 +31,7 @@ func _init(db_ref) -> void:
|
||||
vb.add_child(head)
|
||||
# 明细条灰字对深灰底实测 2.63:1 → 暗底双色墨制=暖白族
|
||||
_summary = UiKit.label("",UiKit.FS_BODY, false, UiKit.WARM)
|
||||
_summary.custom_minimum_size = Vector2(0, BAND_H_DIG - 4)
|
||||
_summary.custom_minimum_size = Vector2(0, _band_h_dig - 4)
|
||||
_summary.clip_text = true
|
||||
_summary.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS
|
||||
head.add_child(_summary)
|
||||
@@ -39,7 +41,7 @@ func _init(db_ref) -> void:
|
||||
_log = RichTextLabel.new()
|
||||
_log.bbcode_enabled = true
|
||||
_log.scroll_following = true
|
||||
_log.custom_minimum_size = Vector2(0, BAND_H_SETTLE - 26)
|
||||
_log.custom_minimum_size = Vector2(0, _band_h_settle - 26)
|
||||
_log.clip_contents = true
|
||||
_log.scroll_active = true
|
||||
vb.add_child(_log)
|
||||
@@ -53,7 +55,7 @@ func set_mode(mode: String) -> void:
|
||||
var settle := mode == "settle"
|
||||
_log.visible = settle
|
||||
_summary.visible = not settle
|
||||
custom_minimum_size = Vector2(0, BAND_H_SETTLE if settle else BAND_H_DIG)
|
||||
custom_minimum_size = Vector2(0, _band_h_settle if settle else _band_h_dig)
|
||||
|
||||
|
||||
func mode() -> String:
|
||||
|
||||
@@ -24,11 +24,11 @@ var _viewport: SubViewport
|
||||
var world: Node2D # StageWorld(preload 脚本实例——Node2D 世界根/字体挂点)
|
||||
|
||||
|
||||
func _init(db_ref) -> void:
|
||||
custom_minimum_size = Vector2(0, 252)
|
||||
func _init(db_ref, layout: BattleLayoutTable) -> void:
|
||||
custom_minimum_size = Vector2(0, layout.stage_h)
|
||||
_build_backdrop()
|
||||
_build_world_stage()
|
||||
_build_text_half(db_ref)
|
||||
_build_text_half(db_ref, layout)
|
||||
_build_companion_layer(db_ref)
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ func _build_world_stage() -> void:
|
||||
|
||||
|
||||
## Control 文本半区域(round_label / stage_center / log_view 原样保留——C1 永久 Control)
|
||||
func _build_text_half(db_ref) -> void: # type-hint: ignore(db 鸭子型注入——单测以最小 stub 构造)
|
||||
func _build_text_half(db_ref, layout: BattleLayoutTable) -> void: # type-hint: ignore(db 鸭子型注入——单测以最小 stub 构造)
|
||||
var vb := VBoxContainer.new()
|
||||
vb.add_theme_constant_override("separation", 4)
|
||||
vb.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
@@ -86,7 +86,7 @@ func _build_text_half(db_ref) -> void: # type-hint: ignore(db 鸭子型注入
|
||||
add_child(vb)
|
||||
_build_narr_row(vb)
|
||||
_build_center_row(vb)
|
||||
log_view = BattleLogView.new(db_ref)
|
||||
log_view = BattleLogView.new(db_ref, layout)
|
||||
vb.add_child(log_view)
|
||||
|
||||
|
||||
|
||||
@@ -18,17 +18,17 @@ signal hover_forbidden(cell: Vector2i)
|
||||
signal chord_attempted(cell: Vector2i)
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
func _init(layout: BattleLayoutTable) -> void:
|
||||
size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
||||
var vb := VBoxContainer.new()
|
||||
vb.add_theme_constant_override("separation", 4)
|
||||
add_child(vb)
|
||||
_build_views(vb)
|
||||
_build_views(vb, layout)
|
||||
|
||||
|
||||
## 视图三件域:AP 条+棋盘区(含动效与 toast 叠层)
|
||||
func _build_views(vb: VBoxContainer) -> void:
|
||||
ap_bar = ApBarView.new()
|
||||
func _build_views(vb: VBoxContainer, layout: BattleLayoutTable) -> void:
|
||||
ap_bar = ApBarView.new(layout)
|
||||
vb.add_child(ap_bar)
|
||||
var board_area := Control.new()
|
||||
board_area.size_flags_vertical = Control.SIZE_EXPAND_FILL
|
||||
|
||||
@@ -21,8 +21,8 @@ var _board_variant_section: VBoxContainer # 地形变异常驻角标(空态
|
||||
var _board_variant_list: VBoxContainer
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
custom_minimum_size = Vector2(240, 0)
|
||||
func _init(layout: BattleLayoutTable) -> void:
|
||||
custom_minimum_size = Vector2(layout.info_panel_w, 0)
|
||||
clip_contents = true
|
||||
# 主面板穿石板戏服(纹理本色;数据框仍素面 §7.1;缺位回退 flat)
|
||||
var sb := UiKit.panel_skinned(Vector4(8, 6, 8, 6))
|
||||
|
||||
@@ -136,7 +136,7 @@ func _ready() -> void:
|
||||
# ---- ⑥ 主角受击/死亡路由(battle_stage 主角走完整呈现链的断言) ----
|
||||
var db2 = load("res://core/config_db.gd").new()
|
||||
if db2.load_tables("res://data/tables", false):
|
||||
var stage2 = BattleStageView.new(db2)
|
||||
var stage2 = BattleStageView.new(db2, db2.presentation.battle_layout)
|
||||
add_child(stage2)
|
||||
var flash_before: int = int(stage2.hero_standee.fx_probe().get("flash_count", 0))
|
||||
stage2.on_step_row_fx({"type": "damage", "target": "hero", "killed": false})
|
||||
|
||||
@@ -530,7 +530,7 @@ func _career_nojump_contract(main: Node) -> void:
|
||||
problems.append("[career][nojump] 页内出现导航入口「%s」(零跳转违约——生涯纯只读)" % nav)
|
||||
|
||||
|
||||
# ⑥standee 专项:B 区站桩内容矩形 ⊆ 252px 舞台矩形(内缩 8px)且不被裁切
|
||||
# ⑥standee 专项:B 区站桩内容矩形 ⊆ 舞台矩形(高度读布局表;内缩 8px)且不被裁切
|
||||
# (名行基线离盒底 1px 同型高危写法返修回归闸;
|
||||
# 内容矩形=StandeeBody.content_rect(),最低绘制元素=底座下缘)
|
||||
func _standeefit_contract(main: Node) -> void:
|
||||
|
||||
@@ -769,7 +769,7 @@ func _smoke_feats() -> void:
|
||||
and orch0.fx_host().fx_emit("mine_hit", {"damage": 5}).is_empty()
|
||||
and orch0.fx_host().fx_emit("battle_won", {"round": 1}).is_empty())
|
||||
# ⑦c 特性持有呈现段(C 区「特性」节;呈现面在 06 在册)
|
||||
var panel = load("res://presentation/views/hero_info_panel_view.gd").new()
|
||||
var panel = load("res://presentation/views/hero_info_panel_view.gd").new(_db.presentation.battle_layout)
|
||||
panel.set_feat_holdings([])
|
||||
Kit.check("特性段空态隐藏(视图空态纪律同特性段)", not panel._feat_section.visible)
|
||||
panel.set_feat_holdings(["疼痛记忆 ×1", "双管镐 ×1"])
|
||||
@@ -788,7 +788,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
||||
autoplay._drive_level(a.runner(), st, null, "normal", "min")
|
||||
if String(st["result"]) != "":
|
||||
return false
|
||||
if a._pending_next_level:
|
||||
if a._bp_store._pending_next_level:
|
||||
# 三选一停点确认(确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
|
||||
if not a.stop()._pending.is_empty():
|
||||
var bcands: Array = a.stop()._pending["candidates"]
|
||||
|
||||
@@ -78,7 +78,7 @@ func _init() -> void:
|
||||
# 账本保留(契约 1):主动件充能与每冒险种行入口零改动(单源化后=差额种行幂等——不吞装配)
|
||||
a._attunement = ["incense_stick"]
|
||||
a._loadout.set_attunement(["incense_stick"])
|
||||
a._loadout_seeded = false # 模拟首关 bind 时点(分派走 seed_for_adventure 分支)
|
||||
a._bp_store._loadout_seeded = false # 模拟首关 bind 时点(分派走 seed_for_adventure 分支——成员随断点件收编改址)
|
||||
a._loadout_on_level_inventory() # 种行(首关 bind 时点语义)
|
||||
var incense_left := 0
|
||||
for r in a.runner().treasure_inventory.snapshot_inventory():
|
||||
@@ -289,7 +289,7 @@ func _init() -> void:
|
||||
|
||||
# A12 呈现两态文案与 _format_row 一致:两态均零「雷」字面
|
||||
var LogViewScript := load("res://presentation/views/battle_log_view.gd")
|
||||
var lv = LogViewScript.new(db)
|
||||
var lv = LogViewScript.new(db, db.presentation.battle_layout)
|
||||
var broken_row := {"type": "seal_miss", "source_class": "seal_miss", "origin_cell": [0, 0],
|
||||
"round_index": 1, "display_name": "封印未中", "value": 0, "highlight": "perfect_broken"}
|
||||
var forgiven_row := {"type": "seal_miss", "source_class": "seal_miss", "origin_cell": [0, 0],
|
||||
|
||||
@@ -1477,7 +1477,7 @@ func _adv_drive_to_shop(a, st: Dictionary, autoplay) -> bool:
|
||||
autoplay._drive_level(a.runner(), st, null, "normal", "min")
|
||||
if String(st["result"]) != "":
|
||||
return false
|
||||
if a._pending_next_level:
|
||||
if a._bp_store._pending_next_level:
|
||||
# 三选一停点确认(确定性选件后推进,与 run_adventure 自动策略同口径;TP0 令牌化)
|
||||
if not a.stop()._pending.is_empty():
|
||||
var bcands: Array = a.stop()._pending["candidates"]
|
||||
|
||||
@@ -126,7 +126,7 @@ func _init() -> void:
|
||||
UiKit.motion_scale = 1.0
|
||||
# ---- 断言④:排播两处取用按行 target_id 解析站桩(d12 排播半——假 stage 记参)----
|
||||
var rp = RowPacerScript.new()
|
||||
var fstage := FakeStage.new(null)
|
||||
var fstage := FakeStage.new(null, root.get_node("/root/ConfigDB").presentation.battle_layout)
|
||||
var ffx := FakeFx.new()
|
||||
rp.stage = fstage
|
||||
rp.board_view = main.ui.battle.board_zone.board_view
|
||||
|
||||
@@ -41,7 +41,7 @@ func _init() -> void:
|
||||
|
||||
# ① C 区两段头(u1/u2——直建 hero 面板,adventure_feats_test ⑦c 族同构)
|
||||
func _sweep_hero_panel() -> void:
|
||||
var panel = load("res://presentation/views/hero_info_panel_view.gd").new()
|
||||
var panel = load("res://presentation/views/hero_info_panel_view.gd").new(_db.presentation.battle_layout)
|
||||
var feat_head := _first_label(panel._feat_section)
|
||||
var curio_head := _first_label(panel._curio_section)
|
||||
Kit.check("C 区专长段头==「专长」(u1 换代——段头文案钉值)〔wa1〕",
|
||||
@@ -95,7 +95,7 @@ func _sweep_source_names() -> void:
|
||||
row_view._hide_tip()
|
||||
Kit.check("结算表 source_label_map[mine_hit]==「危险格」(u6 换代——ConfigDB 真装载)〔wa7〕",
|
||||
String(_db.combat.settlement.core.source_label_map.get("mine_hit", "")) == "危险格")
|
||||
var log_view = load("res://presentation/views/battle_log_view.gd").new(_db)
|
||||
var log_view = load("res://presentation/views/battle_log_view.gd").new(_db, _db.presentation.battle_layout)
|
||||
var row_txt := String(log_view._format_row({"type": "effect", "source_class": "mine_hit",
|
||||
"display_name": "挖掘反噬", "value": -5, "pool_value": -5, "round_index": 1}))
|
||||
Kit.check("战斗日志 mine_hit 来源行含「(危险格)」(u6 消费半——_format_row 生产路径)〔wa7〕",
|
||||
|
||||
@@ -72,7 +72,7 @@ func _init() -> void:
|
||||
UiKit.motion_scale = 1.0
|
||||
|
||||
# ---- ⑤ 站桩按 id 解析(两口共用同名字敌载体)----
|
||||
var stage = StageScript.new(null)
|
||||
var stage = StageScript.new(null, load("res://data/tables/battle_layout.tres"))
|
||||
root.add_child(stage)
|
||||
var s1 = StandeeBodyScript.new("enemy")
|
||||
var s2 = StandeeBodyScript.new("enemy")
|
||||
|
||||
@@ -383,7 +383,7 @@ func _smoke_type_glow() -> void:
|
||||
# 本段把现行行形态逐型喂进真实视图:拆行残留与行字段错位在此转红)----
|
||||
func _smoke_battle_log_view() -> void:
|
||||
print("--- 战斗日志视图行格式化(行协议 9 类呈现层同步闸)---")
|
||||
var view = load("res://presentation/views/battle_log_view.gd").new(_db)
|
||||
var view = load("res://presentation/views/battle_log_view.gd").new(_db, _db.presentation.battle_layout)
|
||||
# 敌方行动伤害行(现行形态:baseline 在场、attach 已出协议——修复前此行即 Invalid access)
|
||||
view.append_row({"type": "damage", "display_name": "地精 撕咬", "value": 2, "target": "主角",
|
||||
"baseline": 3, "roll": 15, "ac": 12, "tier": 100,
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""战斗屏布局表读取(`game/data/tables/battle_layout.tres` 单点)。
|
||||
|
||||
样张渲染工具不另立布局常量:B 区高 / C 区宽 / AP 条高 / 明细带两态高一律读该表,
|
||||
与游戏侧 `ConfigDb.presentation.battle_layout` 同源。
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
TABLE_PATH = os.path.join(ROOT, "game", "data", "tables", "battle_layout.tres")
|
||||
FIELDS = ("stage_h", "info_panel_w", "ap_bar_h", "log_band_h_dig", "log_band_h_settle")
|
||||
|
||||
|
||||
def load(path=TABLE_PATH):
|
||||
with open(path, encoding="utf-8") as f:
|
||||
text = f.read()
|
||||
vals = {}
|
||||
for key in FIELDS:
|
||||
m = re.search(r"^%s\s*=\s*(\d+)\s*$" % key, text, re.M)
|
||||
if m is None:
|
||||
raise ValueError("battle_layout.tres 缺字段或非整数值: %s" % key)
|
||||
vals[key] = int(m.group(1))
|
||||
return vals
|
||||
@@ -35,9 +35,21 @@ import re
|
||||
import hashlib
|
||||
from collections import deque
|
||||
|
||||
from layout_config import load as load_layout
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
MAIN_ROOT = "D:/code/g-plan2"
|
||||
|
||||
CANVAS_W = 1280
|
||||
CANVAS_H = 800
|
||||
_LAYOUT = load_layout()
|
||||
B_H = _LAYOUT["stage_h"] # B 区高(布局表单点——与游戏侧同源)
|
||||
C_W = _LAYOUT["info_panel_w"] # C 区宽
|
||||
D_X = C_W + 4 # D 区左边 = C 区宽 + 列间距
|
||||
D_W = CANVAS_W - D_X # D 区宽
|
||||
BAND_DIG = _LAYOUT["log_band_h_dig"] # 明细带收起高
|
||||
AP_H = _LAYOUT["ap_bar_h"] # D 区 AP 条高
|
||||
|
||||
|
||||
def asset_root(*parts):
|
||||
"""素材路径两级回退:worktree → 主检出(素材不入 git,fresh worktree 缺位)。"""
|
||||
@@ -638,10 +650,11 @@ def draw_standee(canvas, ox, oy, kind, name, enemy_id, order=1, armor="light",
|
||||
|
||||
def render_battle_screen():
|
||||
"""B/C/D 三区全内容 1280×800 mockup。"""
|
||||
scr = Image.new("RGBA", (1280, 800), (24, 20, 17, 255))
|
||||
scr = Image.new("RGBA", (CANVAS_W, CANVAS_H), (24, 20, 17, 255))
|
||||
d = ImageDraw.Draw(scr)
|
||||
# ── B 区 252px 全宽 ──
|
||||
d.rectangle([0, 0, 1280, 252], fill=UIKIT["PANEL_STAGE"], outline=UIKIT["PANEL_BORDER"])
|
||||
c_y = B_H + 4
|
||||
# ── B 区(高读布局表)全宽 ──
|
||||
d.rectangle([0, 0, CANVAS_W, B_H], fill=UIKIT["PANEL_STAGE"], outline=UIKIT["PANEL_BORDER"])
|
||||
d.text((640, 14), "第 3 轮 · 挖掘", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22), anchor="mm")
|
||||
draw_standee(scr, 40, 34, "hero", "主角", "", hp=0.7, statuses=("attack_up", "ac_up"))
|
||||
d.text((560, 120), "兑现演出区\n(结算期:光带 → 弹道 → 受击)", font=ImageFont.truetype(ZH_FONT, 13),
|
||||
@@ -650,33 +663,34 @@ def render_battle_screen():
|
||||
draw_standee(scr, 973, 34, "enemy", "熊地精", "bugbear", order=2, armor="light", hp=0.8, intent="charge",
|
||||
statuses=("alert",))
|
||||
draw_standee(scr, 1128, 34, "enemy", "食人魔酋长", "ogre_chief", order=3, armor="light", hp=1.0, intent="buff")
|
||||
# 明细带(挖掘期收起 24px)
|
||||
d.rectangle([0, 228, 1280, 252], fill=(90, 80, 66))
|
||||
d.text((10, 240), "── 第 3 轮 ── 地精 受到 4 点伤害(增伤 ×1)· 熊地精 蓄力(重击预告)…", font=F_LBL2,
|
||||
# 明细带(挖掘期收起——高读布局表)
|
||||
d.rectangle([0, B_H - BAND_DIG, CANVAS_W, B_H], fill=(90, 80, 66))
|
||||
d.text((10, B_H - BAND_DIG // 2), "── 第 3 轮 ── 地精 受到 4 点伤害(增伤 ×1)· 熊地精 蓄力(重击预告)…", font=F_LBL2,
|
||||
fill=(232, 222, 200), anchor="lm")
|
||||
# ── C 区 240×548 ──
|
||||
d.rectangle([0, 256, 240, 800], fill=UIKIT["PANEL_HERO"], outline=UIKIT["PANEL_BORDER"])
|
||||
d.rectangle([8, 264, 232, 340], outline=(120, 106, 88), width=1) # 攻击数据框(直角细线)
|
||||
d.text((16, 276), "攻击 6", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((16, 298), "(+50% 增伤 ×1)", font=F_LBL2, fill=(140, 90, 30))
|
||||
d.text((16, 318), "护甲 AC 12", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((8, 352), "魔法道具槽(预留)", font=F_LBL2, fill=(120, 110, 98))
|
||||
# ── C 区(宽读布局表)──
|
||||
d.rectangle([0, c_y, C_W, CANVAS_H], fill=UIKIT["PANEL_HERO"], outline=UIKIT["PANEL_BORDER"])
|
||||
d.rectangle([8, c_y + 8, C_W - 8, c_y + 84], outline=(120, 106, 88), width=1) # 攻击数据框(直角细线)
|
||||
d.text((16, c_y + 20), "攻击 6", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((16, c_y + 42), "(+50% 增伤 ×1)", font=F_LBL2, fill=(140, 90, 30))
|
||||
d.text((16, c_y + 62), "护甲 AC 12", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((8, c_y + 96), "魔法道具槽(预留)", font=F_LBL2, fill=(120, 110, 98))
|
||||
for i in range(4):
|
||||
d.rectangle([8 + i * 40, 368, 8 + i * 40 + 34, 402], outline=(120, 106, 88), width=1)
|
||||
d.text((8, 416), "本关强化列表(预留)", font=F_LBL2, fill=(120, 110, 98))
|
||||
d.rectangle([8 + i * 40, c_y + 112, 8 + i * 40 + 34, c_y + 146], outline=(120, 106, 88), width=1)
|
||||
d.text((8, c_y + 160), "本关强化列表(预留)", font=F_LBL2, fill=(120, 110, 98))
|
||||
# ── D 区 ──
|
||||
d.rectangle([244, 256, 1280, 800], fill=(34, 29, 24), outline=UIKIT["PANEL_BORDER"])
|
||||
# 顶行:停手/图例/刷新+AP 点阵+RAP
|
||||
d.rectangle([252, 262, 304, 284], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((278, 273), "停手", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([310, 262, 354, 284], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((332, 273), "?图例", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([360, 262, 428, 284], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((394, 273), "刷新 8|剩2", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([D_X, c_y, CANVAS_W, CANVAS_H], fill=(34, 29, 24), outline=UIKIT["PANEL_BORDER"])
|
||||
# 顶行:停手/图例/刷新+AP 点阵+RAP(坐标随 C 区宽平移)
|
||||
dx = D_X + 8
|
||||
d.rectangle([dx, c_y + 6, dx + 52, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((dx + 26, c_y + 17), "停手", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([dx + 58, c_y + 6, dx + 102, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((dx + 80, c_y + 17), "?图例", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([dx + 108, c_y + 6, dx + 176, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((dx + 142, c_y + 17), "刷新 8|剩2", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
for i in range(6):
|
||||
col = (232, 200, 120) if i < 4 else (120, 106, 88)
|
||||
d.ellipse([440 + i * 18, 266, 452 + i * 18, 278], fill=col)
|
||||
d.text((552, 273), "RAP 6/6", font=F_LBL2, fill=(200, 180, 140), anchor="lm")
|
||||
d.ellipse([dx + 188 + i * 18, c_y + 10, dx + 200 + i * 18, c_y + 22], fill=col)
|
||||
d.text((dx + 300, c_y + 17), "RAP 6/6", font=F_LBL2, fill=(200, 180, 140), anchor="lm")
|
||||
return scr
|
||||
|
||||
|
||||
@@ -938,8 +952,8 @@ def main():
|
||||
# 三区 mockup
|
||||
scr = render_battle_screen()
|
||||
# D 区主样张板(10×10@45px=450×450)嵌进 mockup D 区(工具行之下居中)
|
||||
bx = 244 + (1036 - board.size[0]) // 2
|
||||
by = 256 + 34 + (800 - 256 - 34 - board.size[1]) // 2
|
||||
bx = D_X + (D_W - board.size[0]) // 2
|
||||
by = B_H + 4 + AP_H + (CANVAS_H - B_H - 4 - AP_H - board.size[1]) // 2
|
||||
scr.paste(board.convert("RGB"), (bx, by), board)
|
||||
canvas.paste(scr, (20, 52))
|
||||
d.text((20, 866), "三区 mockup(1280×800 实比):B 区站桩(主角立绘 48×48 朝右/敌字形占位 ▂▆█/顺位/意图/名行=护甲图标+纯名字)",
|
||||
|
||||
@@ -40,7 +40,7 @@ v3.0 口径(方向稿=目检先行闸,冻结归机检):
|
||||
迭代 43 扩展(2026-10-29 战斗一屏样张批):整屏样张两版出口(1280×800 基座 + 1920×1080 全局等比
|
||||
1.35×+左右黑边,§8.2 1920 档策略)+ art-bible §1.4 整屏四项断言(S1–S4)+ 战斗表现五项断言(P1–P5)
|
||||
+ S3b 图类型色板变体第二套 + S5 三组关键帧条 + S6 描边同屏对照与遮挡对照组 + V1 两版尺寸断言
|
||||
+ V10 改前/改后基线并排对拍;B 区高按代码现状 252 出样(art-bible §8.2 文 270 差异只登记不修)。
|
||||
+ V10 改前/改后基线并排对拍;分区几何读 game/data/tables/battle_layout.tres(与游戏侧同源)。
|
||||
"""
|
||||
from PIL import Image, ImageDraw, ImageFilter, ImageChops, ImageFont
|
||||
import os
|
||||
@@ -50,6 +50,8 @@ import re
|
||||
import hashlib
|
||||
from collections import deque
|
||||
|
||||
from layout_config import load as load_layout
|
||||
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
MAIN_ROOT = "D:/code/g-plan2"
|
||||
|
||||
@@ -845,14 +847,22 @@ def draw_standee(canvas, ox, oy, kind, name, enemy_id, order=1, armor="light",
|
||||
canvas.alpha_composite(gray, (ox, oy))
|
||||
|
||||
|
||||
B_H = 252 # B 区高=代码现状(battle_stage_view custom_minimum_size 252;§8.2 文 270 差异只登记不修)
|
||||
CANVAS_W = 1280 # 基座档(art-bible §8.1)
|
||||
CANVAS_H = 800
|
||||
_LAYOUT = load_layout()
|
||||
B_H = _LAYOUT["stage_h"] # B 区高(布局表单点——与游戏侧同源)
|
||||
C_W = _LAYOUT["info_panel_w"] # C 区宽
|
||||
D_X = C_W + 4 # D 区左边 = C 区宽 + 列间距
|
||||
D_W = CANVAS_W - D_X # D 区宽
|
||||
BAND_DIG = _LAYOUT["log_band_h_dig"] # 明细带收起高
|
||||
BAND_SETTLE = _LAYOUT["log_band_h_settle"]
|
||||
AP_H = _LAYOUT["ap_bar_h"] # D 区 AP 条高
|
||||
|
||||
|
||||
def draw_base(canvas_w=CANVAS_W, canvas_h=800, b_h=B_H):
|
||||
"""样张底板(T1):B/C/D 三区骨架——分区数值按 battle.md 指路、B 区按代码 252;格边长=CELL_D 45
|
||||
def draw_base(canvas_w=CANVAS_W, canvas_h=CANVAS_H, b_h=B_H):
|
||||
"""样张底板(T1):B/C/D 三区骨架——分区几何全部读布局表 battle_layout.tres;格边长=CELL_D 45
|
||||
(D 区 10×10 板实比,§8.1)。返回 (img, zones)——zones 供 S/P 断言取样:
|
||||
b=(0,0,w,b_h) 含明细带;band=明细带 24px;c=(0,b_h+4,240,h);d=(244,b_h+4,w,h)。"""
|
||||
b=(0,0,w,b_h) 含明细带;band=明细带收起高;c=(0,b_h+4,C_W,h);d=(D_X,b_h+4,w,h)。"""
|
||||
img = Image.new("RGBA", (canvas_w, canvas_h), (24, 20, 17, 255))
|
||||
d = ImageDraw.Draw(img)
|
||||
c_y = b_h + 4
|
||||
@@ -865,25 +875,26 @@ def draw_base(canvas_w=CANVAS_W, canvas_h=800, b_h=B_H):
|
||||
a = int(26 * (1 - i / 60.0))
|
||||
db.rectangle([bx0 - 46 + i // 3, 0, bx0 + 46 - i // 3, 40 + i * 3], fill=(255, 236, 200, a))
|
||||
img.alpha_composite(beam, (0, 0))
|
||||
# 明细带(挖掘期收起 24px)
|
||||
d.rectangle([0, b_h - 24, canvas_w, b_h], fill=(46, 39, 32))
|
||||
# 明细带(挖掘期收起——高读布局表)
|
||||
d.rectangle([0, b_h - BAND_DIG, canvas_w, b_h], fill=(46, 39, 32))
|
||||
# C 区
|
||||
d.rectangle([0, c_y, 240, canvas_h], fill=UIKIT["PANEL_HERO"], outline=UIKIT["PANEL_BORDER"])
|
||||
d.rectangle([0, c_y, C_W, canvas_h], fill=UIKIT["PANEL_HERO"], outline=UIKIT["PANEL_BORDER"])
|
||||
# D 区
|
||||
d.rectangle([244, c_y, canvas_w, canvas_h], fill=(34, 29, 24), outline=UIKIT["PANEL_BORDER"])
|
||||
# D 区顶行工具件(停手/图例/刷新+AP 点阵+RAP)
|
||||
d.rectangle([252, c_y + 6, 304, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((278, c_y + 17), "停手", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([310, c_y + 6, 354, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((332, c_y + 17), "?图例", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([360, c_y + 6, 428, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((394, c_y + 17), "刷新 8|剩2", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([D_X, c_y, canvas_w, canvas_h], fill=(34, 29, 24), outline=UIKIT["PANEL_BORDER"])
|
||||
# D 区顶行工具件(停手/图例/刷新+AP 点阵+RAP——坐标随 C 区宽平移)
|
||||
dx = D_X + 8
|
||||
d.rectangle([dx, c_y + 6, dx + 52, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((dx + 26, c_y + 17), "停手", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([dx + 58, c_y + 6, dx + 102, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((dx + 80, c_y + 17), "?图例", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
d.rectangle([dx + 108, c_y + 6, dx + 176, c_y + 28], fill=(90, 80, 66), outline=(120, 106, 88))
|
||||
d.text((dx + 142, c_y + 17), "刷新 8|剩2", font=F_LBL2, fill=(232, 222, 200), anchor="mm")
|
||||
for i in range(6):
|
||||
col = (232, 200, 120) if i < 4 else (120, 106, 88)
|
||||
d.ellipse([440 + i * 18, c_y + 10, 452 + i * 18, c_y + 22], fill=col)
|
||||
d.text((552, c_y + 17), "RAP 6/6", font=F_LBL2, fill=(200, 180, 140), anchor="lm")
|
||||
zones = {"b": (0, 0, canvas_w, b_h), "band": (0, b_h - 24, canvas_w, b_h),
|
||||
"c": (0, c_y, 240, canvas_h), "d": (244, c_y, canvas_w, canvas_h)}
|
||||
d.ellipse([dx + 188 + i * 18, c_y + 10, dx + 200 + i * 18, c_y + 22], fill=col)
|
||||
d.text((dx + 300, c_y + 17), "RAP 6/6", font=F_LBL2, fill=(200, 180, 140), anchor="lm")
|
||||
zones = {"b": (0, 0, canvas_w, b_h), "band": (0, b_h - BAND_DIG, canvas_w, b_h),
|
||||
"c": (0, c_y, C_W, canvas_h), "d": (D_X, c_y, canvas_w, canvas_h)}
|
||||
return img, zones
|
||||
|
||||
|
||||
@@ -901,24 +912,25 @@ def render_battle_screen(board_img=None):
|
||||
statuses=("alert",))
|
||||
draw_standee(scr, 1128, 34, "enemy", "食人魔酋长", "ogre_chief", order=3, armor="light", hp=1.0, intent="buff")
|
||||
# 明细带摘要行 + 结算语境 hero 剪影 mini(P2 三语境之一;右端贴装不越缘)
|
||||
d.text((10, 240), "── 第 3 轮 ── 地精 受到 4 点伤害(增伤 ×1)· 熊地精 蓄力(重击预告)…", font=F_LBL2,
|
||||
d.text((10, b_h - BAND_DIG // 2), "── 第 3 轮 ── 地精 受到 4 点伤害(增伤 ×1)· 熊地精 蓄力(重击预告)…", font=F_LBL2,
|
||||
fill=(240, 226, 200), anchor="lm")
|
||||
mini = HERO.resize((24, int(24 * HERO.size[1] / HERO.size[0])), Image.NEAREST)
|
||||
scr.alpha_composite(mini, (1236, b_h - 24))
|
||||
# ── C 区内容(次级文对齐 §7.2 ≥4.5:1——STONE_DEEP/GOLD_INK 在册墨) ──
|
||||
d.rectangle([8, 264, 232, 340], outline=(120, 106, 88), width=1) # 攻击数据框(直角细线)
|
||||
d.text((16, 276), "攻击 6", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((16, 298), "(+50% 增伤 ×1)", font=F_LBL2, fill=(116, 80, 11))
|
||||
d.text((16, 318), "护甲 AC 12", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((8, 352), "魔法道具槽(预留)", font=F_LBL2, fill=(85, 77, 65))
|
||||
scr.alpha_composite(mini, (CANVAS_W - 44, b_h - BAND_DIG))
|
||||
# ── C 区内容(次级文对齐 §7.2 ≥4.5:1——STONE_DEEP/GOLD_INK 在册墨;y 随 B 区高平移) ──
|
||||
cy = b_h + 4
|
||||
d.rectangle([8, cy + 8, C_W - 8, cy + 84], outline=(120, 106, 88), width=1) # 攻击数据框(直角细线)
|
||||
d.text((16, cy + 20), "攻击 6", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((16, cy + 42), "(+50% 增伤 ×1)", font=F_LBL2, fill=(116, 80, 11))
|
||||
d.text((16, cy + 62), "护甲 AC 12", font=ImageFont.truetype(ZH_FONT, 15), fill=(30, 26, 22))
|
||||
d.text((8, cy + 96), "魔法道具槽(预留)", font=F_LBL2, fill=(85, 77, 65))
|
||||
for i in range(4):
|
||||
d.rectangle([8 + i * 40, 368, 8 + i * 40 + 34, 402], outline=(120, 106, 88), width=1)
|
||||
d.text((8, 416), "本关强化列表(预留)", font=F_LBL2, fill=(85, 77, 65))
|
||||
d.rectangle([8 + i * 40, cy + 112, 8 + i * 40 + 34, cy + 146], outline=(120, 106, 88), width=1)
|
||||
d.text((8, cy + 160), "本关强化列表(预留)", font=F_LBL2, fill=(85, 77, 65))
|
||||
# ── D 区主样张板(10×10@45px=450×450+悬崖 8)嵌进工具行之下居中 ──
|
||||
board_origin = None
|
||||
if board_img is not None:
|
||||
bx = 244 + (1036 - board_img.size[0]) // 2
|
||||
by = b_h + 4 + 34 + (800 - b_h - 4 - 34 - board_img.size[1]) // 2
|
||||
bx = D_X + (D_W - board_img.size[0]) // 2
|
||||
by = cy + AP_H + (CANVAS_H - cy - AP_H - board_img.size[1]) // 2
|
||||
scr.paste(board_img.convert("RGB"), (bx, by), board_img)
|
||||
board_origin = (bx, by)
|
||||
return scr, zones, board_origin
|
||||
@@ -1644,7 +1656,7 @@ def main():
|
||||
d.text((20, 14), "战斗一屏样张方向稿 v3-draft(2026-09-29,art-bible v3.0;迭代 43 扩展:整屏两版+S1–S4/P1–P5 断言)——机检 %s(方向稿参考,冻结归目检后机检批)" %
|
||||
("全绿" if ok else "有 FAIL"), col=(232, 222, 200), font=F_TITLE)
|
||||
canvas.paste(scr, (20, 52))
|
||||
d.text((20, 866), "三区 mockup(1280×800 实比,B 区按代码 252):B 区站桩(主角立绘 96 朝右/敌字形占位/顺位 FS_HINT/意图素牌/血条=危险红/名行=素底牌+护甲 PNG)",
|
||||
d.text((20, 866), "三区 mockup(1280×800 实比,分区几何读布局表):B 区站桩(主角立绘 96 朝右/敌字形占位/顺位 FS_HINT/意图素牌/血条=危险红/名行=素底牌+护甲 PNG)",
|
||||
col=(225, 215, 195), font=F_LBL)
|
||||
d.text((20, 886), "C 区攻击数据框+预留槽 · D 区主样张板(方案甲拼装/接触带微光/悬停金框/封印印记/危险格巢穴占位)",
|
||||
col=(225, 215, 195), font=F_LBL)
|
||||
@@ -1732,7 +1744,7 @@ def main():
|
||||
with open(OUT_REPORT, "w", encoding="utf-8") as f:
|
||||
f.write("# 样张 v3 机检报告(2026-09-30 批 A 断言定值 + 迭代 43 整屏扩展 S1–S4/P1–P5/V1/V10/A7/A5b)\n\n")
|
||||
f.write("生成:`python tools/render_sample_sheet_v3.py`;v3.5 冻结口径:格底分档/亮墨直画三档/色牌后备/物件级装饰/B 区舞台;A3b 图底分离 Δ≥30;A4 尺寸基线(立绘 46×48/小人源 32×32 硬等)。"
|
||||
"迭代 43 扩展:B 区按代码 252 出样;整屏四项与战斗表现五项断言落 CHECKS;两版样张出口与基线并排对拍。方向稿目检先行,机检冻结归目检通过后。\n\n")
|
||||
"迭代 43 扩展:B 区高读布局表出样;整屏四项与战斗表现五项断言落 CHECKS;两版样张出口与基线并排对拍。方向稿目检先行,机检冻结归目检通过后。\n\n")
|
||||
f.write("## 断言明细\n\n")
|
||||
f.write("| 断言 | 判据 | 结果 | 明细 |\n|---|---|---|---|\n")
|
||||
for cid, name, okk, detail in CHECKS:
|
||||
|
||||
Reference in new issue
Block a user