diff --git a/game/presentation/views/battle_stage_view.gd b/game/presentation/views/battle_stage_view.gd index 4bf6e07f..aa65e220 100644 --- a/game/presentation/views/battle_stage_view.gd +++ b/game/presentation/views/battle_stage_view.gd @@ -205,6 +205,33 @@ func enemy_body_at(i: int) -> Node2D: return _cards[i] +# 敌种立绘路径映射(enemy_id → UiKit 纹理常量,键集 = art-bible §4.6.4 八敌种)。显式 match 单点: +# UiKit 常量是逐条 const 声明(非字典),Object.get 不达脚本常量、禁动态拼键取值;放本视图 = +# 素材装载属组合点职责(主角侧同款先例 _init TEX_HERO_PORTRAIT),StandeeBody 保持纯收 tex 面。 +# 素材缺位 load_tex→null→set_portrait_texture 字形回退(表现与素材批前逐位一致);未录键返回 null +# 走默认字形「敌」(与 StandeeBody._portrait_glyph 默认档同口径)——数据表扩键先补此处映射。 +func _enemy_portrait_tex(enemy_id: String) -> Texture2D: + match enemy_id: + "goblin": + return UiKit.load_tex(UiKit.TEX_ENEMY_GOBLIN) + "bugbear": + return UiKit.load_tex(UiKit.TEX_ENEMY_BUGBEAR) + "ogre_chief": + return UiKit.load_tex(UiKit.TEX_ENEMY_OGRE_CHIEF) + "giant_spider": + return UiKit.load_tex(UiKit.TEX_ENEMY_GIANT_SPIDER) + "drider": + return UiKit.load_tex(UiKit.TEX_ENEMY_DRIDER) + "duergar": + return UiKit.load_tex(UiKit.TEX_ENEMY_DUERGAR) + "hook_horror": + return UiKit.load_tex(UiKit.TEX_ENEMY_HOOK_HORROR) + "duergar_king": + return UiKit.load_tex(UiKit.TEX_ENEMY_DUERGAR_KING) + _: + return null + + # 敌方站桩投影更新:数量不变 = 就地更新(死亡灰化/受击闪烁等站桩上动画不被逐行刷新重置——禁瞬消纪律); # 数量变化 = 新战斗域,重建站桩槽(轮内不增删,无回流) func set_enemies(cards_data: Array) -> void: @@ -220,6 +247,8 @@ func set_enemies(cards_data: Array) -> void: world.add_child(body) body.slot_row = _make_slot_companion(enemy_box) body.set_data(d) + # 敌种立绘接线(仅重建拍装载一次;flip=false——敌像素材基准朝右,翻转义务归素材批) + body.set_portrait_texture(_enemy_portrait_tex(String(d.get("enemy_id", ""))), false) _cards.append(body) _sync_layout.call_deferred() return diff --git a/game/presentation/views/board_cell_unit.gd b/game/presentation/views/board_cell_unit.gd index f81e3740..7febe37c 100644 --- a/game/presentation/views/board_cell_unit.gd +++ b/game/presentation/views/board_cell_unit.gd @@ -15,6 +15,11 @@ var edge := 46.0: # 格边长(BoardView.set_cell_area 单点 edge = v queue_redraw() var bg_sb: StyleBoxFlat = null # 格底 StyleBox(BoardView 每次 paint 重建;呼吸微光就地改边框) +# ── 接触带微光绘制面(2026-09-26 批:可挖域微光自"墙格整框"改"英雄格周界空间侧"——art-bible §3.2 v2.8; +# 仅英雄格单元承载,BoardView.refresh 按可挖域写边/角码、_process 逐拍更新呼吸色)── +var contact_bands: Array = [] # 正交接触边码 0=N/1=S/2=W/3=E(该侧整条边) +var contact_corners: Array = [] # 斜向共享角码 0=NW/1=NE/2=SW/3=SE(角点+两侧各半条边) +var contact_col := Color(0, 0, 0, 0) # 当前呼吸色(alpha 0 = 不画) var glyph := "": # 文字位——数字/雷/旗/人/?(现行视觉旗与数字互斥,同位承载) set(v): glyph = v @@ -58,6 +63,49 @@ func _draw() -> void: # 呼吸微光/主角蓝框由 BoardView 改 bg_sb 后触发重绘) if tex_base != null: draw_texture_rect(tex_base, rect, false) + # 瓦片态描边覆盖层(高墙瓦片接线批 2026-09-26):tex_base 在位时 BoardView 传入的 bg_sb + # 已降级为「透明底 + 描边」载体——呼吸微光/主角蓝框画在瓦片之上(art-bible §1.2 叠层序 + # 不变:底 < 纹带 < 数字 < 旗 < 焦点高亮);素材缺位回退走 elif 程序化原样,零观感变化 + if bg_sb != null: + draw_style_box(bg_sb, rect) + + # 接触带微光(英雄格周界空间侧——正交邻墙=整条边;斜向=共享角点+两侧各半条边;光不进墙格): + # 带宽 4px 贴格边内侧(坐标推导:N=[0,0,edge,4] S=[0,edge-4,edge,4] W=[0,0,4,edge] E=[edge-4,0,4,edge]; + # 半条边=沿邻边自角点起 edge/2;角点圆 r=6 贴角点圆心,近白芯 r=2.5 提亮) + if contact_col.a > 0.0: + var band := 4.0 + var half := edge * 0.5 + for b in contact_bands: + var r := Rect2() + match int(b): + 0: r = Rect2(0, 0, edge, band) + 1: r = Rect2(0, edge - band, edge, band) + 2: r = Rect2(0, 0, band, edge) + 3: r = Rect2(edge - band, 0, band, edge) + draw_rect(r, contact_col, true) + for cc in contact_corners: + var pt := Vector2.ZERO + match int(cc): + 0: pt = Vector2(0, 0) + 1: pt = Vector2(edge, 0) + 2: pt = Vector2(0, edge) + 3: pt = Vector2(edge, edge) + draw_circle(pt, 6.0, contact_col) + draw_circle(pt, 2.5, Color(1, 1, 1, minf(1.0, contact_col.a * 0.95))) + # 半条边:角点沿两条邻边各延伸 edge/2(坐标推导:横带沿 N/S 自角点向板心侧 half; + # NW/SE 半带 x=y=角点坐标,NE/SE 半带自 edge-half 收进——正交整边时重叠同色无害) + if int(cc) == 0 or int(cc) == 1: + # N 侧横带:NW 自 x=0、NE 自 x=edge-half + draw_rect(Rect2(pt.x if int(cc) == 0 else pt.x - half, 0.0, half, band), contact_col, true) + if int(cc) == 2 or int(cc) == 3: + # S 侧横带:SW 自 x=0、SE 自 x=edge-half + draw_rect(Rect2(pt.x if int(cc) == 2 else pt.x - half, edge - band, half, band), contact_col, true) + if int(cc) == 0 or int(cc) == 2: + # W 侧竖带:NW 自 y=0、SW 自 y=edge-half + draw_rect(Rect2(0.0, pt.y if int(cc) == 0 else pt.y - half, band, half), contact_col, true) + if int(cc) == 1 or int(cc) == 3: + # E 侧竖带:NE 自 y=0、SE 自 y=edge-half + draw_rect(Rect2(edge - band, pt.y if int(cc) == 1 else pt.y - half, band, half), contact_col, true) elif bg_sb != null: draw_style_box(bg_sb, rect) # 顶面倒角(立体原型批:光源左上——上/左内沿受光 2px、下/右内沿背光 2px;内缩 2px 让 diff --git a/game/presentation/views/board_view.gd b/game/presentation/views/board_view.gd index aa78e702..feb68f0d 100644 --- a/game/presentation/views/board_view.gd +++ b/game/presentation/views/board_view.gd @@ -36,7 +36,7 @@ const COL_HERO := UiKit.HERO_BLUE # 小人边框——小人蓝 #2E6 const COL_FLAG := UiKit.FLAG_RED # 旗朱 #C63D22 const COL_GOLD := UiKit.GOLD # 奖励金 #F0A828 const COL_PATH := UiKit.HERO_BLUE # 路径预览点迹——小人蓝细线 -const COL_DIG_EDGE := UiKit.DIG_EDGE # 可挖域呼吸微光基色(绿相,明度贴格底) +const COL_CONTACT := UiKit.CONTACT_GLOW # 接触带微光基色(蓝族浅一档·近白蓝——§3.2 v2.8;旧绿相退役 2026-09-26) const COL_HL_TARGET := UiKit.GOLD # 悬停目标格边框(金) const REWARD_BADGE := { # 已开格格面资源身份 icon(cell_type 九型键;极性视觉归批 2) "attack": "攻", "defense": "守", "heal": "医", "blast": "爆", "weaken": "弱", "pierce": "穿", "magic": "魔", # 穿/魔 = 伤害道具批 2026-09-18 @@ -70,6 +70,31 @@ const HERO_BOUNCE := 0.82 # 弹跳压缩比(scale.y 1→0.82→1 脚底收 const HERO_SHADOW_PX := Vector2(32, 12) # 脚下椭圆影屏幕尺寸(Godot 组件合成——径向渐变纹理压扁, # 贴图零改动,2026-09-26 用户裁决走引擎侧) +# ── 高墙/地面瓦片纹理路径单点(棋盘高墙瓦片接线批 2026-09-26)── +# 素材 PNG 不入 git(用户裁定)——一律 UiKit.load_tex(exists 守卫)装载,禁 preload; +# 缺位返回 null,整批任一缺位 = 弃用瓦片走程序化平色回退(BRICK/FLOOR,fresh checkout 冒烟不炸)。 +# 路径常量落本文件单点(ui_kit.gd 由并行批占用,任务书 §2.5 禁入)。词表 = +# design/research/board-tile-vocab-plan-a-2026-09-26.md(16 唯一墙瓦片 + 5 地面变体; +# 东/东北/东南/end_e/in_ne/in_se 六镜像件运行时 flip_x 合成,不出文件——词表 §二)。 +const TEX_WALL_CENTER := "res://assets/art/board/terrain_wall_center_dungeon.png" +const TEX_WALL_CENTER_CRACK := "res://assets/art/board/terrain_wall_center_crack_dungeon.png" +const TEX_WALL_CENTER_SPECKLE := "res://assets/art/board/terrain_wall_center_speckle_dungeon.png" +const TEX_WALL_N := "res://assets/art/board/terrain_wall_n_dungeon.png" +const TEX_WALL_S := "res://assets/art/board/terrain_wall_s_dungeon.png" +const TEX_WALL_W := "res://assets/art/board/terrain_wall_w_dungeon.png" # wall_e = 运行时镜像 +const TEX_WALL_NW := "res://assets/art/board/terrain_wall_nw_dungeon.png" # wall_ne = 运行时镜像 +const TEX_WALL_SW := "res://assets/art/board/terrain_wall_sw_dungeon.png" # wall_se = 运行时镜像 +const TEX_WALL_HRUN := "res://assets/art/board/terrain_wall_hrun_dungeon.png" +const TEX_WALL_VRUN := "res://assets/art/board/terrain_wall_vrun_dungeon.png" +const TEX_WALL_END_N := "res://assets/art/board/terrain_wall_end_n_dungeon.png" +const TEX_WALL_END_S := "res://assets/art/board/terrain_wall_end_s_dungeon.png" +const TEX_WALL_END_W := "res://assets/art/board/terrain_wall_end_w_dungeon.png" # wall_end_e = 运行时镜像 +const TEX_WALL_LONE := "res://assets/art/board/terrain_wall_lone_dungeon.png" +const TEX_WALL_IN_NW := "res://assets/art/board/terrain_wall_in_nw_dungeon.png" # wall_in_ne = 运行时镜像 +const TEX_WALL_IN_SW := "res://assets/art/board/terrain_wall_in_sw_dungeon.png" # wall_in_se = 运行时镜像 +const TEX_FLOOR_FMT := "res://assets/art/board/terrain_floor_dungeon_%d.png" # 1..5 变体 +const TILE_FLOOR_VARIANTS := 5 # 地面瓦片变体数(词表 §一 floor_1..5) + var _wrap: CenterContainer var _svp_container: SubViewportContainer var _viewport: SubViewport @@ -81,6 +106,7 @@ var _identity_cb := Callable() # 地图战斗资源 身份解析回调(组 var _mask_cb := Callable() # 地形变异 数字遮蔽回调(组合根注入只读面)——Vector2i → bool(true = 该格数字不显示) var _diggable := {} # 可挖域(8 向相邻未开格)——呼吸微光作用集 var _breath_t := 0.0 +var _contact_unit: Node2D = null # 接触带承载单元(=英雄格 CellUnit;rebuild 随清场复位) var _dig_glow := false # 挖掘期才允许唯一在动元素(art-bible §2.1) var _hover := Vector2i(-9, -9) var _last_click := {"cell": Vector2i(-9, -9), "ms": -100000} @@ -99,6 +125,11 @@ var _hero_move_tw: Tween var _hero_bump_tw: Tween var _hero_dig_tw: Tween +# ── 瓦片状态(高墙瓦片接线批 2026-09-26;素材缺位 = 全 false/空——程序化平色回退)── +var _tiles := {} # 词表键 → Texture2D(镜像件已预翻转;装载见 _load_board_tiles) +var _floor_tiles: Array[Texture2D] = [] # 地面瓦片五变体(下标 0..4 = floor_1..5) +var _tiles_ok := false # false = 素材缺位/未装载——bg_sb 程序化平色原路径 + var cell_size: float: # 只读属性(ui_layout_probe 断言面——旧私有名 _cell 的契约化出口) get: return _cell @@ -125,6 +156,7 @@ func _init() -> void: _desat_mat.set_shader_parameter("strength", 0.0) _hero_tex_idle = UiKit.load_tex(UiKit.TEX_HERO_IDLE) _hero_tex_walk = UiKit.load_tex(UiKit.TEX_HERO_WALK) + _load_board_tiles() # 高墙/地面瓦片整批守卫装载(缺位静默回退——英雄批同款纪律,禁刷屏) func board() -> BoardData: @@ -210,6 +242,7 @@ func rebuild(board: BoardData, minesweeper) -> void: child.queue_free() _world.hero = null # 英雄精灵随清场退役——下拍 refresh 经 ensure_hero 重建(首建即时落位) _cells.clear() + _contact_unit = null _hero_last = Vector2i(-99, -99) _r0.clear() _hover = Vector2i(-9, -9) @@ -227,6 +260,11 @@ func rebuild(board: BoardData, minesweeper) -> void: u.position = Vector2(c) * stride u.bevel = DEPTH_BEVEL # 定稿倒角——新建格单元继承 u.use_parent_material = true # 去饱和材质挂 BoardWorld——单元随世界材质灰化 + if _tiles_ok: + # NEAREST 采样(2026-09-26 用户拍板:32×32 源 × 45/36px 格边非整数缩放接受档, + # 与英雄小人 1.5× 豁免同级——圣经 §8.2 豁免注记归 art-bible 批补写);只在瓦片 + # 模式启用——程序化回退路径渲染零扰动(含格面文字采样不回退不变化) + u.texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST u.font = _world.board_font _world.add_child(u) _cells[c] = u @@ -247,6 +285,7 @@ func refresh() -> void: for x in _board.width: _paint_cell(Vector2i(x, y), hero) _refresh_hover_after_paint() + _apply_contact_bands() _update_hero(hero) # 英雄精灵 diff 驱动(棋盘小人贴图批 2026-09-25) @@ -591,7 +630,18 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: digit_col = COL_FLAG else: bg = COL_BRICK - var sb := UiKit.panel_flat(bg, bg.darkened(0.15), 3) + # 瓦片模式(高墙接线批 2026-09-26):tex_base = 查表瓦片(未开=墙瓦片/已开=地面瓦片), + # bg_sb 降级为透明描边载体——只承载呼吸微光/主角蓝框(CellUnit 瓦片态画在瓦片之上), + # 程序化平色退役;瓦片分配=格坐标哈希,初始化落定后逐拍稳定(禁逐轮重掷——圣经原则三) + var sb: StyleBoxFlat + var tile: Texture2D = null + if _tiles_ok: + # Color.TRANSPARENT 载体(引擎常量——视图禁内联色字面量纪律;透明+零描边=瓦片上不画任何东西) + sb = UiKit.panel_flat(Color.TRANSPARENT, Color.TRANSPARENT, 3, 0) + tile = _floor_tile_for(c) if revealed else _wall_tile_for(c) + else: + # 素材整批缺位回退 = 现行程序化平色路径(fresh checkout 冒烟不炸——任务书 §2.2) + sb = UiKit.panel_flat(bg, bg.darkened(0.15), 3) if is_hero: sb.set_border_width_all(3) sb.border_color = COL_HERO @@ -599,6 +649,7 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: # 贴图缺位回退字形「人」(项目纹理槽惯例——贴图在位=格内字形退役,单位本体由独立精灵承载) glyph = "人" digit_col = COL_HERO + u.tex_base = tile u.bg_sb = sb u.glyph = glyph u.glyph_color = digit_col @@ -606,38 +657,199 @@ func _paint_cell(c: Vector2i, hero: Vector2i) -> void: u.queue_redraw() -# ---- 可挖域 8 向常驻呼吸微光(挖掘期唯一在动元素) ---- +# ---- 可挖域接触带呼吸微光(挖掘期唯一在动元素——§3.2 v2.8 画法:光画英雄格周界空间侧, +# 正交邻墙=整条边、斜向=共享角点+两侧各半条边;旧"墙格整框绿相"退役 2026-09-26 批) ---- func set_dig_glow_enabled(on: bool) -> void: _dig_glow = on - if not on: + if on: + _apply_contact_bands() # 相位重入(结算→挖掘):refresh 未跑也重挂带几何 + else: _reset_breath() func _process(delta: float) -> void: if not _dig_glow or _diggable.is_empty(): return + if _contact_unit == null or not is_instance_valid(_contact_unit): + return _breath_t += delta if UiKit.motion_scale < 1.0: - return # 减动效:呼吸停格(保留当前微光边框——静态可辨、无持续运动) + return # 减动效:呼吸停格(refresh/enable 时已施静态色——静态可辨、无持续运动) var k := 0.5 + 0.5 * sin(_breath_t * 3.0) # 3 rad/s≈0.48Hz——无 >3 次/秒频闪(AC 基线) - var col := COL_DIG_EDGE.lerp(UiKit.DIG_EDGE_DEEP, 0.55 - 0.35 * k) - for c in _diggable: - var u: Node2D = _cells.get(c) - if u == null or u.bg_sb == null: - continue - u.bg_sb.set_border_width_all(2) - u.bg_sb.border_color = col - u.queue_redraw() + _contact_unit.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) + _contact_unit.queue_redraw() + + +# 带几何写入(refresh / set_dig_glow_enabled(true) 挂点):清旧承载件→按英雄格可挖域写边/角码→施色 +func _apply_contact_bands() -> void: + if _contact_unit != null and is_instance_valid(_contact_unit) and (_cells.get(_minesweeper.hero_pos()) != _contact_unit if _minesweeper != null else true): + _contact_unit.contact_col = Color(0, 0, 0, 0) + _contact_unit.queue_redraw() + _contact_unit = null + if _board == null or _minesweeper == null or not _dig_glow: + return + var hu: Node2D = _cells.get(_minesweeper.hero_pos()) + if hu == null: + return + _contact_unit = hu + var bands: Array = [] + var corners: Array = [] + for d in [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)]: + if _diggable.has(_minesweeper.hero_pos() + d): + bands.append(0 if d.y < 0 else (1 if d.y > 0 else (2 if d.x < 0 else 3))) + for d in [Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1)]: + if _diggable.has(_minesweeper.hero_pos() + d): + corners.append(0 if (d.x < 0 and d.y < 0) else (1 if d.x > 0 and d.y < 0 else (2 if d.x < 0 else 3))) + hu.contact_bands = bands + hu.contact_corners = corners + var k := 0.5 + 0.5 * sin(_breath_t * 3.0) + hu.contact_col = COL_CONTACT.lerp(UiKit.CONTACT_GLOW_DEEP, 0.55 - 0.35 * k) + hu.queue_redraw() func _reset_breath() -> void: - for c in _diggable: - var u: Node2D = _cells.get(c) - if u != null and u.bg_sb != null: - u.bg_sb.set_border_width_all(1) - u.bg_sb.border_color = u.bg_sb.bg_color.darkened(0.15) - u.queue_redraw() + if _contact_unit != null and is_instance_valid(_contact_unit): + _contact_unit.contact_col = Color(0, 0, 0, 0) + _contact_unit.queue_redraw() + + +# ---- 高墙/地面瓦片接线(2026-09-26 批;词表 = design/research/board-tile-vocab-plan-a-2026-09-26.md) ---- + +# 瓦片整批守卫装载(_init 单次调用):21 枚全成才启用——任一缺位按整批缺位处理(禁同板混渲染; +# fresh checkout 无素材目录 → 静默全回退)。缺位不 push_error——可选素材缺位是预期路径非故障 +# (英雄批 load_tex 纪律同款,防冒烟刷屏)。镜像件运行时 flip_x 合成,不出文件。 +func _load_board_tiles() -> void: + var t := { + "wall_center": UiKit.load_tex(TEX_WALL_CENTER), + "wall_center_crack": UiKit.load_tex(TEX_WALL_CENTER_CRACK), + "wall_center_speckle": UiKit.load_tex(TEX_WALL_CENTER_SPECKLE), + "wall_n": UiKit.load_tex(TEX_WALL_N), + "wall_s": UiKit.load_tex(TEX_WALL_S), + "wall_w": UiKit.load_tex(TEX_WALL_W), + "wall_nw": UiKit.load_tex(TEX_WALL_NW), + "wall_sw": UiKit.load_tex(TEX_WALL_SW), + "wall_hrun": UiKit.load_tex(TEX_WALL_HRUN), + "wall_vrun": UiKit.load_tex(TEX_WALL_VRUN), + "wall_end_n": UiKit.load_tex(TEX_WALL_END_N), + "wall_end_s": UiKit.load_tex(TEX_WALL_END_S), + "wall_end_w": UiKit.load_tex(TEX_WALL_END_W), + "wall_lone": UiKit.load_tex(TEX_WALL_LONE), + "wall_in_nw": UiKit.load_tex(TEX_WALL_IN_NW), + "wall_in_sw": UiKit.load_tex(TEX_WALL_IN_SW), + } + var floors: Array[Texture2D] = [] + for i in range(1, TILE_FLOOR_VARIANTS + 1): + floors.append(UiKit.load_tex(TEX_FLOOR_FMT % i)) + for k in t: + if t[k] == null: + return + for tex in floors: + if tex == null: + return + # 六镜像件(词表 §二:wall_e←wall_w / wall_ne←wall_nw / wall_se←wall_sw / + # wall_end_e←wall_end_w / wall_in_ne←wall_in_nw / wall_in_se←wall_in_sw;只水平翻转禁旋转 + # ——北受光不受损) + t["wall_e"] = _flip_tex(t["wall_w"] as Texture2D) + t["wall_ne"] = _flip_tex(t["wall_nw"] as Texture2D) + t["wall_se"] = _flip_tex(t["wall_sw"] as Texture2D) + t["wall_end_e"] = _flip_tex(t["wall_end_w"] as Texture2D) + t["wall_in_ne"] = _flip_tex(t["wall_in_nw"] as Texture2D) + t["wall_in_se"] = _flip_tex(t["wall_in_sw"] as Texture2D) + _tiles = t + _floor_tiles = floors + _tiles_ok = true + + +# 水平镜像贴图运行时合成(flip_h 语义):Image 级逐位翻转后重建 ImageTexture——CellUnit 零改动。 +# VRAM 压缩源先解压(flip_x 不支持压缩格式);32×32 原尺寸逐位翻转,无 ±1px 取整问题 +static func _flip_tex(tex: Texture2D) -> Texture2D: + var img := tex.get_image() + if img.is_compressed(): + img.decompress() + img.flip_x() + return ImageTexture.create_from_image(img) + + +# 格坐标确定性散列(禁 RNG/禁 randomize 纪律——「初始化随机落定禁逐轮重掷」圣经原则三落点: +# 同格坐标恒同值,refresh/重绘/逐轮不变)。乘加混合 + 移位雪崩;64 位整型乘法回绕即哈希语义 +#(GDScript int 溢出静默回绕),板域 10×10/12×12 内 mod 10/mod 5 分布均匀 +static func _cell_hash(c: Vector2i) -> int: + var h := c.x * 374761393 + c.y * 668265263 + h = (h ^ (h >> 13)) * 1274126177 + return h ^ (h >> 16) + + +# 四正交邻墙判定(词表 §三.1:板外视为墙;未开格=墙、已开格=地板——用户世界观口径「未开=墙、 +# 已开=挖出的空间」)。唯一调用面 _paint_cell(refresh 对 _minesweeper 非空守卫在先) +func _is_wall(c: Vector2i) -> bool: + if c.x < 0 or c.y < 0 or c.x >= _board.width or c.y >= _board.height: + return true + return not _minesweeper.is_revealed(c) + + +# 墙瓦片查表(词表 §一 16 正交情形映射 + §三.2 凹角回落):nf/ef/sf/wf = 北/东/南/西邻是地板。 +# 16 情形 = 0 地板(center 族)+ 3 地板×4(端头)+ 2 地板×6(条带/凸角)+ 1 地板×4(单缘) +# + 4 地板(lone),逐分支穷尽 +func _wall_tile_for(c: Vector2i) -> Texture2D: + var nf := not _is_wall(c + Vector2i(0, -1)) + var ef := not _is_wall(c + Vector2i(1, 0)) + var sf := not _is_wall(c + Vector2i(0, 1)) + var wf := not _is_wall(c + Vector2i(-1, 0)) + if nf and ef and sf and wf: + return _wall_center_tile(c) # 四邻皆墙 → 对角凹角/中心三变体(下位函数) + if nf and ef and sf: + return _tiles["wall_end_w"] # 仅西墙(端头·东连墙) + if ef and sf and wf: + return _tiles["wall_end_s"] # 仅北墙(端头·南连地板、北连墙) + if nf and sf and wf: + return _tiles["wall_end_e"] # 仅东墙(= end_w 镜像) + if nf and ef and wf: + return _tiles["wall_end_n"] # 仅南墙(端头·北向死头、南连墙) + if nf and sf: + return _tiles["wall_hrun"] # 上下地板 = 横条带中段 + if ef and wf: + return _tiles["wall_vrun"] # 左右地板 = 竖条带中段 + if nf and wf: + return _tiles["wall_nw"] # 凸角(上+左地板) + if nf and ef: + return _tiles["wall_ne"] # 凸角镜像 + if sf and wf: + return _tiles["wall_sw"] # 凸角(下+左地板) + if sf and ef: + return _tiles["wall_se"] # 凸角镜像 + if nf: + return _tiles["wall_n"] # 北缘(上方地板) + if ef: + return _tiles["wall_e"] # 东缘(镜像件) + if sf: + return _tiles["wall_s"] # 南缘(下方地板) + return _tiles["wall_w"] # 西缘(仅西地板——16 情形穷尽收尾) + + +# 四邻皆墙格:对角凹角优先 nw > ne > sw > se(词表 §三.2——双凹角不做专件,多对角地板回落 +# 首中单凹角件,残留 1px 缝为已裁决可接受限);无对角地板 → 中心三变体按格坐标哈希 60/20/20 +func _wall_center_tile(c: Vector2i) -> Texture2D: + if not _is_wall(c + Vector2i(-1, -1)): + return _tiles["wall_in_nw"] # 西北对角是地板 + if not _is_wall(c + Vector2i(1, -1)): + return _tiles["wall_in_ne"] # 东北对角是地板(镜像件) + if not _is_wall(c + Vector2i(-1, 1)): + return _tiles["wall_in_sw"] # 西南对角是地板 + if not _is_wall(c + Vector2i(1, 1)): + return _tiles["wall_in_se"] # 东南对角是地板(镜像件) + var m := posmod(_cell_hash(c), 10) + if m < 6: + return _tiles["wall_center"] # 6/10 = 60% + if m < 8: + return _tiles["wall_center_crack"] # 2/10 = 20% + return _tiles["wall_center_speckle"] # 2/10 = 20% + + +# 地面瓦片五变体按格坐标哈希均匀分配(词表 §一 floor_1..5 / §三.3「同法哈希分配」; +# posmod 保非负下标) +func _floor_tile_for(c: Vector2i) -> Texture2D: + return _floor_tiles[posmod(_cell_hash(c), _floor_tiles.size())] # ---- 自绘光标纹理(镐/旗——引擎内置光标无此语义;程序化占位,headless 跳过) ---- diff --git a/game/presentation/views/ui_kit.gd b/game/presentation/views/ui_kit.gd index b732d8f0..a2adaf96 100644 --- a/game/presentation/views/ui_kit.gd +++ b/game/presentation/views/ui_kit.gd @@ -47,8 +47,9 @@ const DARK_TEXT_FAINT := Color8(160, 150, 138)# 未达节点文字 const BRICK := Color8(201, 175, 131) # 棋盘·未开掩尘地砖 #C9AF83 const FLOOR := Color8(179, 154, 120) # 棋盘·已开裸土 #B39A78 -const DIG_EDGE := Color8(120, 190, 120) # 可挖域呼吸微光绿 -const DIG_EDGE_DEEP := Color8(46, 80, 60) # 可挖域呼吸暗端(lerp 目标) +const CONTACT_GLOW := Color8(214, 236, 255) # 可挖域接触带微光(蓝族浅一档·近白蓝——art-bible §3.2 v2.8 定稿; + # 旧绿相 DIG_EDGE 随画法切换退役 2026-09-26 批) +const CONTACT_GLOW_DEEP := Color8(150, 190, 230) # 接触带呼吸暗端(lerp 目标) const BG_ROOT := Color8(30, 25, 20) # 屏幕根底 const BG_OVERLAY := Color8(28, 22, 18) # 模态遮罩底 @@ -80,6 +81,16 @@ static func motion(dur: float) -> float: const TEX_HERO_IDLE := "res://assets/art/character/character_hero_idle.png" const TEX_HERO_WALK := "res://assets/art/character/character_hero_walk.png" const TEX_HERO_PORTRAIT := "res://assets/art/character/character_hero_portrait.png" # B 区站桩立绘 48×48(源档 candidates/hero-idle/hero_idle_01.png,2026-09-26 用户点名换装) +# 敌种立绘(B 区站桩 2026-09-26 槽接线批)——文件键 = 数据表主键 enemy_id(圣经 §8.3 命名规范), +# 键集 = art-bible §4.6.4 八敌种;素材批入库前全部缺位(load_tex→null→字形回退),入库即显示零代码换装 +const TEX_ENEMY_GOBLIN := "res://assets/art/character/character_enemy_goblin.png" # 地精 +const TEX_ENEMY_BUGBEAR := "res://assets/art/character/character_enemy_bugbear.png" # 熊地精 +const TEX_ENEMY_OGRE_CHIEF := "res://assets/art/character/character_enemy_ogre_chief.png" # 食人魔酋长 +const TEX_ENEMY_GIANT_SPIDER := "res://assets/art/character/character_enemy_giant_spider.png" # 巨蜘蛛 +const TEX_ENEMY_DRIDER := "res://assets/art/character/character_enemy_drider.png" # 蛛化卓尔精灵 +const TEX_ENEMY_DUERGAR := "res://assets/art/character/character_enemy_duergar.png" # 灰矮人 +const TEX_ENEMY_HOOK_HORROR := "res://assets/art/character/character_enemy_hook_horror.png" # 钩爪恐魔 +const TEX_ENEMY_DUERGAR_KING := "res://assets/art/character/character_enemy_duergar_king.png" # 灰矮人石君王 # 素材纹理守卫装载(exists 检查避开缺位树上 ResourceLoader 报错刷屏——冒烟/无素材检出静默回退) diff --git a/production/session-state/active.md b/production/session-state/active.md index 8065eb4b..a10ad4f1 100644 --- a/production/session-state/active.md +++ b/production/session-state/active.md @@ -461,4 +461,5 @@ 171. **站桩本体 Node2D 化批(Phase B T3,2026-09-26,本会话——循环 1 最重卡;worktree battle-stage-node2d-20260926)**:站桩本体自 Control(旧 StandeeView **退役删除**)平移为 Node2D 世界精灵 **standee_body.gd**(新件,无 class_name 经 preload)。**行为面 17 方法契约逐项保持(C3)**——set_data/set_hero/_set_hp/set_idle/set_effects/set_effects_cleared_feedback/set_order/set_pending/_play_intent_motion/set_portrait_texture(tex,flip)/mark_dead/is_dead/flash_hit/_revive/_update_name_line/_refresh_portrait_glyph+is_dead,调用点 hud_presenter:53-54/79-80、main:309/441/475/495/503、main:768/770 零改排。**等价平移清单(C9)**——立绘=set_portrait_texture 惰性 Sprite2D(NEAREST+flip_h+整数 2×=48 源盒 96px)缺位回退字形(人/▂/▆/█/✕/敌 draw_string);底座=UiKit.panel_flat(STONE 系) StyleBox.draw 直绘;血条=直角矩形 hp_value 属性 tween 0.15 递减(引擎默认主题深底/蓝填充近似平移——色值列目检清单);顺位徽标 ①②③ GOLD_DEEP;意图徽标=draw_set_transform 弹入 scale 1.6→1+字形前倾 4°→0(旧只转字形 Label——贴图态无动画等价保持);名字行=护甲图标+纯名 draw_string 居中(AC 不展示 2026-09-26 终裁);受击闪白 modulate 1.6/1.4/1.2→白/死灰;死亡=灰化+rotation 2.5/-1.5/0 摇摆塌陷+意图退位(禁瞬消)+**状态行随 mark_dead 同拍隐藏(代拍板②待追认)**;复活=全态重置。**批内代拍板默认(待追认)**——①slot_row+悬停 tooltip 保留 Control 载体挂壳层(_companion_layer 顶层,敌伴随件宿主=**enemy_box 语义改写「站桩壳层伴随件容器」HBoxContainer→Control**,手动定位随世界同步,探针 :97 计数面不变);②定位同步=world_to_shell 单点(T2 建挂),_sync_layout 挂点=ready/NOTIFICATION_RESIZED/_row.resized(明细带展收改行高随动)/set_enemies 重建,站桩盒高=行高(旧 HBox FILL 拉伸等价)、敌右对齐 +8 间距等价;③hero_standee/enemy 站桩成员类型 StandeeView→Node2D(StandeeBody),**hero_standee_center/enemy_standee_center 改 world_to_shell(本体中心)=壳层口径(C4)**。ui_probe 敌站桩断言等价改写(台账 170 前例):穿透明(enemy_box 子件 _name_label/_armor_icon/_intent_badge/_hp_bar)→本体出口 enemy_body_at(i)+name_line_probe()/intent_probe()/has_world_hp_bar——呈现口径逐字保持。**踩坑记录**——standee_body 漏声明 _portrait_tex 成员→编译连坐 main.gd 假性两错(w/h 推断失败),editor --quit 前置闸暴露后修复。**验证**——editor --quit 0 error+冒烟 557/0(=基线零增删)+五探针全绿(ui_probe/ui_click 问题=0/ui_tune/ui_input 十一断言/layout 外框零漂移 stage 1280×252 逐位=T0 基线)+static_scan exit0+文档三闸零漂移。进入 T4。 172. **信号与演出等价证明批(Phase B T4,2026-09-26,本会话——循环 1;worktree battle-stage-node2d-20260926,零代码变更纯取证)**:等价证明落盘 `production/qa/battle-stage-node2d-等价证明-2026-09-26.md`——①**调用点 grep 对比零 diff**:main.gd→stage 九处调用(309/310/439/441/475/495/503/768/770)与 hud_presenter→hero_standee 四处投喂(53/54/79/80)逐字节一致;hud_presenter/main/core/** 文件级 git diff 基线=零行变更(投喂链零改动实证+核心层零接触)。②**时序常量逐字对照**——血条递减 0.15/意图弹入 0.10+0.16/前倾 0.10+0.14/死亡摇摆 0.12+0.1+0.08 与 modulate (0.62,0.62,0.62,0.9)/受击闪白 0.08+0.2 与 (1.6,1.4,1.2)/复活复位/清空反馈 1.2+0.8 全部逐字同(前倾项注记:旧仅转字形 Label、贴图态无动画——新等价保持)。③**写路径逐行同构**——on_step_row_fx(早退链+flash+killed→mark_dead)/set_enemies 数量分叉/clear_enemies/gray_all_enemies/set_round_text/set_settle_focus 逐行同构;弹道落点 hero/enemy_standee_center 签名不变、实现改 world_to_shell(本体中心)=壳层口径保持(C4)。④结构性差异五笔登记(本体载体/slot_row 壳层化/enemy_box 语义/血条直角矩形呈现/ui_probe 本体探针出口)=非漂移改判,指向台账 170/171。轻闸全绿(冒烟 557/0+ui_probe+ui_input 十一断言+static_scan exit0)。进入 T5。 173. **探针取证与断言同步批(Phase B T5,2026-09-26,本会话——循环 1;worktree battle-stage-node2d-20260926,零常驻代码变更)**:①**临时取证探针**(跑完即删,台账 152 先例;踩坑复录=裸 .gd 无 tscn 载体跑场景探针=静默挂死,补 tscn 包装解):真实战斗态 B 区场景树 dump——**世界子树(SubViewport 内)Button=0/GridContainer=0/HBox=0**(站桩载体归零证);世界站桩本体 hero=true+enemies=1(StandeeBody)、伴随件容器子件=1 与本体数一致;壳层 HBox 逐件语义标注=文本行载体+StatusSlotRow 伴随件+log 内部(零站桩载体残留)。②ui_probe 敌站桩断言等价改写已于 T3 随批落地(enemy_box 子件穿透明→stage.enemy_body_at(i).name_line_probe()/intent_probe()/has_world_hp_bar 本体出口,呈现口径逐字保持——台账 171 改判记录);ui_click_probe :223 hero_standee.slot_row 伴随件引用=零改动即兼容(tooltip 机制零返工——代拍板①实证)。③ui_input_probe 十一断言原样全绿(B 区改造零波及 D 区实证——grep stage/standee/enemy 零命中在案)。取证报告本条即档案。轻闸全绿(冒烟 557/0+五探针 PASS)。进入 T6。 -174. **文档随批与登记批(Phase B T6,2026-09-26,本会话——循环 1)**:①architecture.md——Last Updated 行+D1 层表形态行补 BattleStageView 例外(StageWorld/StandeeBody,slot_row 伴随件留壳层,Phase A/B 双注记引 ADR-0006)。②HUD框架.md——v0.10 渲染技术注记行(Phase B 同构改造/契约不变/伴随件/C6 复用/预算不变/等价证明外链)。③battle.md——2026-09-26 实现注记修订行(B 区 Node2D 化/setter 面保持/弹道锚点壳层口径/外框 252px 零改动)。④board-node2d-migration-plan.md §3——「Phase B 预留」改写为「已落地」(实施面三点+挂点复用)+**样张硬前置条款按裁决①作废落文**(样张冻结只卡换皮/演出批=循环 2 前置,不卡结构迁移)。⑤Alpha 清单状态列更新(A5 已实证转周期化复跑件待建/F1F2 全绿注记/F4 待 T7 对拍+修订记录行)。⑥06 待办核对=本批无解决条目(呈现层测试分级条 B/C 档与本批无涉;样张硬前置非 06 登记项)。验证=文档三闸对 T0 基线逐字节零新增(lint/link/term 三 diff 空)+轻闸随批全绿。进入 T7。 \ No newline at end of file +174. **文档随批与登记批(Phase B T6,2026-09-26,本会话——循环 1)**:①architecture.md——Last Updated 行+D1 层表形态行补 BattleStageView 例外(StageWorld/StandeeBody,slot_row 伴随件留壳层,Phase A/B 双注记引 ADR-0006)。②HUD框架.md——v0.10 渲染技术注记行(Phase B 同构改造/契约不变/伴随件/C6 复用/预算不变/等价证明外链)。③battle.md——2026-09-26 实现注记修订行(B 区 Node2D 化/setter 面保持/弹道锚点壳层口径/外框 252px 零改动)。④board-node2d-migration-plan.md §3——「Phase B 预留」改写为「已落地」(实施面三点+挂点复用)+**样张硬前置条款按裁决①作废落文**(样张冻结只卡换皮/演出批=循环 2 前置,不卡结构迁移)。⑤Alpha 清单状态列更新(A5 已实证转周期化复跑件待建/F1F2 全绿注记/F4 待 T7 对拍+修订记录行)。⑥06 待办核对=本批无解决条目(呈现层测试分级条 B/C 档与本批无涉;样张硬前置非 06 登记项)。验证=文档三闸对 T0 基线逐字节零新增(lint/link/term 三 diff 空)+轻闸随批全绿。进入 T7。 +176. **换肤最小可看批——格面瓦片接线+敌像槽接线+接触带微光切换(2026-09-26,本会话——用户令「其他任务子 agent 委派」;worktree skin-wiring-20260926,两个并行子 agent+主会话微光,主编排者统一跑闸)**:①**格面高墙瓦片接线**(子 agent A,按任务书+词表逐条)——board_view.gd 新增 16 墙+5 地面瓦片路径常量(单点本文件,ui_kit 让位并行避撞)+整批守卫装载 `_tiles_ok`(21 枚全成才启用,任一缺位整批静默回退程序化平色,禁同板混渲染,fresh checkout 安全)+查表合成器(16 正交情形 Python 穷举对词表逐条命中/镜像 6 件 flip_x 运行时合成禁旋转/凹角回落 nw>ne>sw>se/center 变体+地面 5 变体格坐标哈希 60/20/20——纯确定性无 RNG)+`_paint_cell` 瓦片分支(tex_base=查表件,bg_sb 降透明描边载体,呼吸/蓝框通道不变)+cell_unit `_draw` 瓦片态补画 bg_sb 覆盖层;非整数 NEAREST 缩放=台账 166 已拍板档。②**敌人立绘槽接线**(子 agent B)——ui_kit 八敌种 TEX_ENEMY_ 常量(§4.6.4 键集,域_对象键命名)+battle_stage_view `_enemy_portrait_tex` match 映射(显式函数,UiKit const 不可动态 get 已注记)+set_enemies 重建拍 set_portrait_texture 接线(tex=null 字形回退逐位等价——素材零张在库为预期态,入库即自动显示零代码换装);签名零变更,ui_probe 断言面零触碰。③**接触带微光切换**(主会话,§3.2 v2.8 画法落码)——旧「墙格整框绿相」退役(DIG_EDGE/DIG_EDGE_DEEP 常量删)→**英雄格周界空间侧**:正交邻墙=整条边/斜向=共享角点圆+两侧各半条边(坐标推导注释在案,SW/SE+NE/SE 两处半带首版写反已修),带宽 4px 贴格边,蓝族近白蓝 CONTACT_GLOW #D6ECFF↔CONTACT_GLOW_DEEP 呼吸 0.48Hz 保留,减动效停格静态可辨;承载=英雄格 CellUnit 三属性(bands/corners/col),refresh+set_dig_glow_enabled(true) 双挂点,rebuild 复位。**验证**——editor --quit 0 error+冒烟 557/0+五探针全绿(probe stage 外框逐位零漂移)+static_scan exit0+文档三闸对基线零漂移+**MC 12 局 sha256=de8225b3 与基线逐位一致**(渲染批零核心接触铁证)。**遗留知悉**——词表 §五已知限制未修(成对双凹角 1px 缝/混合凹角缺落点/平铺重复感,按纪律不顺手修);瓦片态字形经 NEAREST 采样微锐化=预期;素材缺位回退路径全部保留。**待实机目检**——格面瓦片观感/微光蓝带/墙群拼缝(用户终验,任务书 §3.5)。 \ No newline at end of file