Files
game-code-4/tests/probe_p3_noise_tiling/probe_p3.gd
T
程基岩(engineering-lead) 856326a766 chore: W1 项目骨架 — 基线 v1.0 架构落盘(Godot 4.7.2 / gl_compatibility)
架构基线:docs/architecture/2026-10-19-F-architecture.md(v1.0)
范围:WRATH OF THE DEAD《怨返》单人挂机 · GDScript · 1280×720 · 12 班底数据驱动

- 骨架:project.godot(4.7 + GL Compatibility)、架构 §2.1 完整目录树(含占位说明)、
  .gitignore/.gitattributes(LF 纪律)、README.md、CLAUDE.md、export_presets.cfg 占位、
  pipeline/README.md(3ds Max 侧契约)
- 数据管线段 1:data/sheets/generators.csv(GDD §3.1 十二班底逐值抄录)
  + data/schema/generators.schema.json(v0)+ src/tools/sheet_importer.gd
  (import_all/import_sheet/validate + ImportReport + .tres stub + 行号/列名/原因失败策略)
  + src/tools/import_main.gd(CI CLI 入口)+ data/build/generators.json(字节级确定性产物)
- 探针:P1(SCREEN_TEXTURE/CanvasLayer 排序)与 P3(NoiseTexture2D seamless)场景代码与
  截图/统计工具;实测手册与结论模板在工作区 docs/engineering/(随仓库外文档树)
- CI:数据门激活(import_all + schema validate + build 漂移门);单测/sim/导出 W3+ 注释占位;
  4.7.2 headless CLI 行为标注待本机验证

待用户侧:P1/P3 实测回填结论;P6 字体许可 / P7 GPU 实测;look-dev 游魂初版(Max 侧)
2026-10-19 15:13:10 +08:00

168 lines
6.2 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
extends Node2D
# ============================================================================
# 探针 P3 —— NoiseTexture2D / FastNoiseLite 平铺语义(架构 §11 P3)
# 依据:管线报告 §6.3;作用于 §5.2 纸纹 pass(双尺度 noise 有序抖动的 tileable 前提)
# 实测手册:docs/engineering/2026-10-19-f-w1-probe-p3-manual.md
# 结论回填:docs/engineering/probes/P3-result.md
#
# 验证:seamless = true 是否真正可平铺
# - 屏上:左右两个 2×2 tile 网格(红线 = tile 边界 @256px)肉直视读接缝
# - 按 S:生成边界差图 PNG(user://p3_diff_*.png)+ 控制台统计
# * wrap_* = |左边缘-右边缘| / |上边缘-下边缘|(平铺接缝强度)
# * grad_mean = 相邻像素平均差(自然梯度基线)
# * seamless 若有效:wrap ≈ grad(同数量级);若无效:wrap >> grad
#
# 知识缺口:FastNoiseLite/NoiseTexture2D 属性名(seamless / seamless_blend_ratio /
# TYPE_SIMPLEX / FRACTAL_FBM)为 4.x 稳定名;4.7.2 若报错按实际属性名修正并记录。
# ============================================================================
const NOISE_SIZE := 256 # 每 tile 256×256;面板 512×512 = 2×2 平铺
const PANEL_SIZE := 512
var _textures: Array = []
func _ready() -> void:
_textures = [_make_texture(true), _make_texture(false)]
var positions := [Vector2(96, 160), Vector2(672, 160)]
var titles := ["seamless = true", "seamless = false"]
for i in _textures.size():
var tr := TextureRect.new()
tr.name = "Panel%d" % i
tr.texture = _textures[i]
tr.position = positions[i]
tr.size = Vector2(PANEL_SIZE, PANEL_SIZE)
tr.stretch_mode = TextureRect.STRETCH_TILE
tr.mouse_filter = Control.MOUSE_FILTER_IGNORE
add_child(tr)
var overlay := SeamOverlay.new()
overlay.position = positions[i]
add_child(overlay)
var t := Label.new()
t.name = "PanelTitle%d" % i
t.text = "%s(红线 = tile 边界;面板内 2×2 平铺)" % titles[i]
t.position = Vector2(int(positions[i].x), 128)
t.add_theme_color_override("font_color", Color.WHITE)
add_child(t)
var hud := Label.new()
hud.name = "HUDHelp"
hud.text = "探针 P3 | 对比两侧红线处接缝(错位/亮度跳变=异常)| S 生成检查图到 user:// 并打印统计 | Esc 退出"
hud.position = Vector2(96, 648)
hud.add_theme_color_override("font_color", Color.WHITE)
add_child(hud)
_print_environment()
func _make_texture(seamless: bool) -> NoiseTexture2D:
var fnl := FastNoiseLite.new()
fnl.seed = 20261019
fnl.noise_type = FastNoiseLite.TYPE_SIMPLEX
fnl.frequency = 0.02
fnl.fractal_type = FastNoiseLite.FRACTAL_FBM
fnl.fractal_octaves = 3
fnl.fractal_lacunarity = 2.0
fnl.fractal_gain = 0.5
var nt := NoiseTexture2D.new()
nt.width = NOISE_SIZE
nt.height = NOISE_SIZE
nt.noise = fnl
nt.seamless = seamless
nt.seamless_blend_ratio = 0.25
nt.generate_mipmaps = false
return nt
func _input(event: InputEvent) -> void:
var key_event := event as InputEventKey
if key_event != null and key_event.pressed and not key_event.echo:
match key_event.keycode:
KEY_S:
_generate_check_images()
KEY_ESCAPE:
get_tree().quit()
func _generate_check_images() -> void:
var names := ["seamless", "periodic"]
print("=== P3 检查图与统计 ===")
for i in _textures.size():
var nt: NoiseTexture2D = _textures[i]
var img := nt.get_image()
if img == null:
push_warning("P3: NoiseTexture2D.get_image() 返回 null(%s)。回退:系统截图(Win+Shift+S)+ 肉眼判读。" % names[i])
continue
var w := img.get_width()
var h := img.get_height()
# 边界差图(×8 放大):wrap 接缝越亮 = 平铺越不连续
var strip_v := Image.create(w, h, false, Image.FORMAT_RGBA8)
var strip_h := Image.create(w, h, false, Image.FORMAT_RGBA8)
for y in h:
var dv := _amp(absf(img.get_pixel(0, y).r - img.get_pixel(w - 1, y).r))
for x in w:
strip_v.set_pixel(x, y, Color(dv, dv, dv, 1.0))
for x in w:
var dh := _amp(absf(img.get_pixel(x, 0).r - img.get_pixel(x, h - 1).r))
for y in h:
strip_h.set_pixel(x, y, Color(dh, dh, dh, 1.0))
strip_v.save_png("user://p3_diff_v_%s.png" % names[i])
strip_h.save_png("user://p3_diff_h_%s.png" % names[i])
var stats := _edge_stats(img)
print("%s: wrap_v mean=%.5f max=%.5f | wrap_h mean=%.5f max=%.5f | grad_mean=%.5f → wrap/grad v=%.2fx h=%.2fx" % [
names[i], stats.wrap_v_mean, stats.wrap_v_max, stats.wrap_h_mean, stats.wrap_h_max, stats.grad_mean,
stats.wrap_v_mean / maxf(stats.grad_mean, 1e-9), stats.wrap_h_mean / maxf(stats.grad_mean, 1e-9)])
print("PNG 位置:%APPDATA%/Godot/app_userdata/<项目名>/p3_diff_*.png")
print("判读:seamless 行 wrap/grad ≈ 1-3x = 可用;periodic 行 wrap/grad ≫ 1 = 对照组生效(说明指标有效)")
func _amp(d: float) -> float:
return clampf(d * 8.0, 0.0, 1.0)
func _edge_stats(img: Image) -> Dictionary:
var w := img.get_width()
var h := img.get_height()
var wrap_v := 0.0
var wrap_v_max := 0.0
for y in h:
var d := absf(img.get_pixel(0, y).r - img.get_pixel(w - 1, y).r)
wrap_v += d
wrap_v_max = maxf(wrap_v_max, d)
var wrap_h := 0.0
var wrap_h_max := 0.0
for x in w:
var d := absf(img.get_pixel(x, 0).r - img.get_pixel(x, h - 1).r)
wrap_h += d
wrap_h_max = maxf(wrap_h_max, d)
var grad := 0.0
for y in h:
for x in w - 1:
grad += absf(img.get_pixel(x, y).r - img.get_pixel(x + 1, y).r)
return {
"wrap_v_mean": wrap_v / h,
"wrap_v_max": wrap_v_max,
"wrap_h_mean": wrap_h / w,
"wrap_h_max": wrap_h_max,
"grad_mean": grad / float((w - 1) * h),
}
func _print_environment() -> void:
var v := Engine.get_version_info()
print("=== P3 probe 环境 ===")
print("godot: %d.%d.%d %s" % [int(v.major), int(v.minor), int(v.patch), str(v.status)])
print("rendering_method: ", ProjectSettings.get_setting("rendering/renderer/rendering_method"))
print("adapter: ", RenderingServer.get_video_adapter_name())
print("noise: FastNoiseLite simplex fbm oct3 f0.02 | tile 256×256 | blend_ratio 0.25")
class SeamOverlay extends Node2D:
const TILE := 256
const PANEL := 512
func _draw() -> void:
var c := Color(1.0, 0.2, 0.2, 0.6)
draw_line(Vector2(TILE, 0), Vector2(TILE, PANEL), c, 1.0)
draw_line(Vector2(0, TILE), Vector2(PANEL, TILE), c, 1.0)
draw_rect(Rect2(Vector2.ZERO, Vector2(PANEL, PANEL)), c, false, 1.0)