## VERTICAL SLICE - NOT FOR PRODUCTION ## 棋盘瓦片件:高墙/地面瓦片整批装载与运行时镜像合成、地板明度归一、数字墨族按板自适应(静态算法族)、 ## 格坐标确定性散列与十六正交情形墙瓦片查表。装载缓存七成员持于件内(同板型零重载、换板重载时序同源), ## 查表族读件持棋盘引用(棋盘视图 rebuild 经 bind_board 同步)。零绘制面、零玩法数据写入。 class_name TilePalette extends RefCounted # ── 高墙/地面瓦片纹理路径单点 ── # 素材 PNG 不入 git——一律 UiKit.load_tex(exists 守卫)装载,禁 preload; # 缺位返回 null,整批任一缺位 = 弃用瓦片走程序化平色回退(棋盘视图 BRICK/FLOOR,fresh checkout 冒烟不炸)。 # 词表 = design/research/board-tile-vocab-plan-a-2026-09-26.md(16 唯一墙瓦片 + 5 地面变体; # 东/东北/东南/end_e/in_ne/in_se 六镜像件运行时 flip_x 合成,不出文件——词表 §二)。 # 路径按 board_type 后缀参数化——terrain_<件名>_.png; # 词表 §一 16 唯一墙瓦片名单单点在本常量数组。underdark/deep_underdark 两套 = # tools/recolor_terrain_tiles.py 程序换色产;任一套缺位 → 整批回落 # dungeon 套(禁同板混渲染);dungeon 套亦缺位 → 程序化平色回退(fresh checkout 冒烟不炸)。 const TILE_WALL_NAMES := ["wall_center", "wall_center_crack", "wall_center_speckle", "wall_n", "wall_s", "wall_w", "wall_nw", "wall_sw", "wall_hrun", "wall_vrun", "wall_end_n", "wall_end_s", "wall_end_w", "wall_lone", "wall_in_nw", "wall_in_sw"] const TILE_WALL_FMT := "res://assets/art/board/terrain_%s_%s.png" # %s=件名 %s=board_type const TILE_SEAL_MARK := "res://assets/art/board/seal_mark.png" # 封印印记纹理(独立可选件) const TILE_FLOOR_FMT := "res://assets/art/board/terrain_floor_%s_%d.png" # %s=board_type 1..%d 变体 const TILE_FLOOR_VARIANTS := 5 # 地面瓦片变体数(词表 §一 floor_1..5) # ---- 数字墨族按板自适应(墨色按关自适应+三关对比断言)---- const DIGIT_INK_CR_P90 := 4.5 # 墨族对地板 p90 亮斑对比度门槛(失效模式=亮白筋纹冲字,WCAG 数字体同源) const DIGIT_INK_CR_MEAN_BRIGHT := 2.5 # 亮族对地板均值门槛(中调防吃字) const DIGIT_INK_CR_MEAN_DARK := 3.0 # 深族对地板均值门槛(大字 AA 级——2.4 的实测观感=隐形) # 墙瓦片 16 正交情形映射(下标 = nf<<3|ef<<2|sf<<1|wf,nf/ef/sf/wf = 北/东/南/西邻是地板)。 # 词表 §一 16 情形 = 0 地板(center 族,键 15 另走对角凹角/中心三变体)+ 3 地板×4(端头)+ # 2 地板×6(条带/凸角)+ 1 地板×4(单缘)+ 4 地板(对角凹角/中心三变体);lone 件与原实现同址 # 不入查表(0 地板收尾落 wall_w) const WALL_MASK_TILE := ["wall_w", "wall_w", "wall_s", "wall_sw", "wall_e", "wall_vrun", "wall_se", "wall_end_s", "wall_n", "wall_nw", "wall_hrun", "wall_end_e", "wall_ne", "wall_end_n", "wall_end_w", "wall_center"] # 四邻皆墙格:对角凹角优先 nw > ne > sw > se(词表 §三.2——双凹角不做专件,多对角地板回落 # 首中单凹角件,残留 1px 缝为可接受限) const WALL_DIAG_DIRS := [Vector2i(-1, -1), Vector2i(1, -1), Vector2i(-1, 1), Vector2i(1, 1)] const WALL_DIAG_TILES := ["wall_in_nw", "wall_in_ne", "wall_in_sw", "wall_in_se"] # 无对角地板 → 中心三变体按格坐标哈希 60/20/20(posmod 保非负下标,10 槽展开免分支) const WALL_CENTER_PICK := ["wall_center", "wall_center", "wall_center", "wall_center", "wall_center", "wall_center", "wall_center_crack", "wall_center_crack", "wall_center_speckle", "wall_center_speckle"] # ── 装载缓存(原棋盘视图同名成员随迁;缺位 = 全 false/空——程序化平色回退)── var tiles: Dictionary[String, Texture2D] = {} # 词表键 → Texture2D(镜像件已预翻转) var floor_tiles: Array[Texture2D] = [] # 地面瓦片五变体(下标 0..4 = floor_1..5) var tiles_ok := false # false = 素材缺位/未装载——调用方走程序化平色原路径 var seal_tex: Texture2D = null # 封印印记纹理(缺盘=null=字形回退——独立可选件不连瓦片整批) var tiles_board_type := "dungeon" # 当前装载瓦片套的 board_type(rebuild 按板切换) var digit_dark_ink := true # 数字墨族按板自适应(true=深墨三档)。缺盘平色回退态 #B39A78 裸土 # 为中明度底——深墨族 CR 4.96 达标、亮墨族仅 2.2,故缺盘默认深墨;瓦片装载后按实测分位数覆写 var digit_plate := false # 数字色牌垫底模式(双峰纹理板——直画两族皆不过门槛时落牌, # 素牌+深墨 12:1 纹理免疫;渲染帧取证 underdark 系深墨直画隐形,故启用色牌) # ── 查表用棋盘态(rebuild 换板时经 bind_board 同步)── var board: BoardData = null var minesweeper: MinesweeperRules = null func bind_board(board_ref: BoardData, minesweeper_ref: MinesweeperRules) -> void: board = board_ref minesweeper = minesweeper_ref # 瓦片整批守卫装载(首次建板与换板型各一次):21 枚全成才启用——任一缺位按整批缺位处理 #(禁同板混渲染;fresh checkout 无素材目录 → 静默全回退)。缺位不 push_error——可选素材缺位是 # 预期路径非故障(英雄 load_tex 纪律同款,防冒烟刷屏)。镜像件运行时 flip_x 合成,不出文件。 func load_board_tiles(board_type: String = "dungeon") -> void: if board_type == "": board_type = "dungeon" # 16 墙瓦片装载循环节(词表 TILE_WALL_NAMES 单点; # 词表本身被 terrain_tile_sets_test 直读,保留) var t: Dictionary[String, Texture2D] = {} for wall_name in TILE_WALL_NAMES: t[wall_name] = UiKit.load_tex(TILE_WALL_FMT % [wall_name, board_type]) var floors: Array[Texture2D] = [] for i in range(1, TILE_FLOOR_VARIANTS + 1): floors.append(UiKit.load_tex(TILE_FLOOR_FMT % [board_type, i])) for k in t: if t[k] == null: _request_dungeon_fallback(board_type) return for tex in floors: if tex == null: _request_dungeon_fallback(board_type) return # 地板归一(圣经 v3.5 冻结①/§1.4——已开格底归一世界底层档 L≈0.10,带 0.08–0.14): # 方向稿 _v3_darken_floors 同式移植;现行瓦片 L≈0.036 低于带下沿故实为提亮; # 本归一为程序代役(逐批装载时对 freshly-loaded 原图测光,幂等)。归一后逐像素明度顺路采集, # 驱动数字墨族按板自适应(digit_ink_mode——均值分位同源同像素,禁二次解码) var lumas: Array[Variant] = [] for i in range(floors.size()): floors[i] = normalize_floor_luma(floors[i], 0.10, lumas) var ink_mode := digit_ink_mode(lumas) digit_dark_ink = bool(ink_mode.dark) digit_plate = bool(ink_mode.plate) # 六镜像件(词表 §二: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"]) t["wall_ne"] = flip_tex(t["wall_nw"]) t["wall_se"] = flip_tex(t["wall_sw"]) t["wall_end_e"] = flip_tex(t["wall_end_w"]) t["wall_in_ne"] = flip_tex(t["wall_in_nw"]) t["wall_in_se"] = flip_tex(t["wall_in_sw"]) tiles = t floor_tiles = floors tiles_board_type = board_type tiles_ok = true # 封印印记纹理(独立可选件:缺盘=字形回退,不触发瓦片整批回落语义) seal_tex = UiKit.load_tex(TILE_SEAL_MARK) # 整批回落:请求套缺位 → 回落 dungeon 套(禁同板混渲染);dungeon 本身缺位 = # 维持程序化平色回退原语义(tiles_ok 恒 false,缺位不刷屏纪律不变) func _request_dungeon_fallback(requested: String) -> void: if requested != "dungeon": load_board_tiles("dungeon") # 水平镜像贴图运行时合成(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) # 地板瓦片明度归一(方向稿 _v3_darken_floors 同式移植):8×8 双线性重采样均值测光 # (sRGB→线性域,Rec.709 系数),增益 k=(目标/实测)^(1/2.2) 钳 [0.6,1.8],逐通道 sRGB 域线性拉伸。 # 输入恒为 freshly-loaded 原图(load_tex 每次盘读)——幂等可重入;32×32 五变体开销可忽略 static func normalize_floor_luma(tex: Texture2D, target_l: float = 0.10, lumas_out: Variant = null) -> Texture2D: var img: Image = tex.get_image() if img.is_compressed(): img.decompress() img.convert(Image.FORMAT_RGBA8) var small: Image = img.duplicate() small.resize(8, 8, Image.INTERPOLATE_BILINEAR) var sum := 0.0 for y in range(8): for x in range(8): var p := small.get_pixel(x, y) sum += 0.2126 * srgb_lin(p.r) + 0.7152 * srgb_lin(p.g) + 0.0722 * srgb_lin(p.b) var k := pow(target_l / maxf(sum / 64.0, 0.001), 1.0 / 2.2) k = clampf(k, 0.6, 1.8) for y in range(img.get_height()): for x in range(img.get_width()): var c := img.get_pixel(x, y) var nr := minf(c.r * k, 1.0) var ng := minf(c.g * k, 1.0) var nb := minf(c.b * k, 1.0) img.set_pixel(x, y, Color(nr, ng, nb, c.a)) if lumas_out != null: lumas_out.append(0.2126 * srgb_lin(nr) + 0.7152 * srgb_lin(ng) + 0.0722 * srgb_lin(nb)) return ImageTexture.create_from_image(img) static func srgb_lin(c: float) -> float: return c / 12.92 if c <= 0.04045 else pow((c + 0.055) / 1.055, 2.4) # 归一后地板逐像素明度 → 选数字呈现模式。三态:亮族直画(暗纹理板)/深族直画(中暗板)/ # 色牌垫底(双峰纹理板——近黑斑与近白筋并存时任何墨族直画必融一峰,渲染帧取证 # 实证 underdark 系深墨隐形,牌上深墨 12:1 纹理免疫)。家族最弱成员(亮族=金亮/深族=锈墨) # 对 p90 与均值双门槛;亮族过选亮族,否则深族(均值门 3.0),两族皆不过落牌。 # 实测:dungeon p90=0.121 亮族 4.79/5.80 ✓ 直画;underdark p90=0.406 亮族 ✗、 # 深族 5.34/**2.41**(均值 <3.0 ✗)→ 落牌;deep_underdark 同→落牌 static func digit_ink_mode(lumas: Array) -> Dictionary: var sorted := lumas.duplicate() sorted.sort() var n := sorted.size() if n == 0: return {"dark": false, "plate": false, "cr_p90_bright": 0.0, "cr_mean_bright": 0.0, "cr_p90_dark": 0.0, "cr_mean_dark": 0.0} var p90: float = sorted[int(n * 0.9)] var crs := _family_contrasts(p90, _mean_of(sorted)) var bright_ok: bool = crs.x >= DIGIT_INK_CR_P90 and crs.z >= DIGIT_INK_CR_MEAN_BRIGHT var dark_ok: bool = crs.y >= DIGIT_INK_CR_P90 and crs.w >= DIGIT_INK_CR_MEAN_DARK return {"dark": not bright_ok and dark_ok, "plate": not bright_ok and not dark_ok, "cr_p90_bright": crs.x, "cr_mean_bright": crs.z, "cr_p90_dark": crs.y, "cr_mean_dark": crs.w} # 均值(明度集合逐元素算术平均) static func _mean_of(values: Array) -> float: var sum := 0.0 for v in values: sum += v return sum / float(values.size()) # 两族对比度四元组:x=亮族 p90 / y=深族 p90 / z=亮族均值 / w=深族均值 static func _family_contrasts(p90: float, mean: float) -> Vector4: return Vector4(contrast_y(UiKit.INK_L_GOLD, p90), contrast_y(UiKit.INK_RUST, p90), contrast_y(UiKit.INK_L_GOLD, mean), contrast_y(UiKit.INK_RUST, mean)) static func contrast_y(ink: Color, bg: float) -> float: var y := ink_y(ink) return (maxf(y, bg) + 0.05) / (minf(y, bg) + 0.05) static func ink_y(c: Color) -> float: return 0.2126 * srgb_lin(c.r) + 0.7152 * srgb_lin(c.g) + 0.0722 * srgb_lin(c.b) # 格坐标确定性散列(禁 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:板外视为墙;未开格=墙、已开格=地板——世界观口径「未开=墙、 # 已开=挖出的空间」)。唯一调用面 wall_tile_for/wall_center_tile(棋盘视图 refresh 对 # 排雷玩法非空守卫在先) 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) # 四邻地板掩码(位序 = nf<<3|ef<<2|sf<<1|wf,见 WALL_MASK_TILE) func _neighbor_mask(c: Vector2i) -> int: var m := 0 if not is_wall(c + Vector2i(0, -1)): m |= 8 if not is_wall(c + Vector2i(1, 0)): m |= 4 if not is_wall(c + Vector2i(0, 1)): m |= 2 if not is_wall(c + Vector2i(-1, 0)): m |= 1 return m # 墙瓦片查表(词表 §一 16 正交情形映射 + §三.2 凹角回落):nf/ef/sf/wf = 北/东/南/西邻是地板。 # 16 情形 = 0 地板(center 族)+ 3 地板×4(端头)+ 2 地板×6(条带/凸角)+ 1 地板×4(单缘) # + 4 地板(对角凹角/中心三变体),逐键穷尽 func wall_tile_for(c: Vector2i) -> Texture2D: var mask := _neighbor_mask(c) if mask == 15: return wall_center_tile(c) # 四邻皆地板 → 对角凹角/中心三变体(下位函数) return tiles[WALL_MASK_TILE[mask]] # 四邻皆地板格:对角凹角优先 nw > ne > sw > se;无对角地板 → 中心三变体按格坐标哈希 60/20/20 func wall_center_tile(c: Vector2i) -> Texture2D: for i in WALL_DIAG_DIRS.size(): var d: Vector2i = WALL_DIAG_DIRS[i] if not is_wall(c + d): return tiles[WALL_DIAG_TILES[i]] return tiles[WALL_CENTER_PICK[posmod(cell_hash(c), 10)]] # 地面瓦片五变体按格坐标哈希均匀分配(词表 §一 floor_1..5 / §三.3「同法哈希分配」; # posmod 保非负下标) func floor_tile_for(c: Vector2i) -> Texture2D: return floor_tiles[posmod(cell_hash(c), floor_tiles.size())]