summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/Game.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-22 02:28:31 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-22 02:28:31 +0800
commitef78374c316587fb585b72e6d427cd6e61203655 (patch)
tree9eb176889054c07449debaf2a9d169ffe1851a0d /src/LivingVillage.Desktop/Game.fs
parent5e036da9dfb1fe7f9e4f5e48414a8a1176292655 (diff)
downloadliving-village-ef78374c316587fb585b72e6d427cd6e61203655.tar.gz
feat(mapgen): 参数化确定性生成器与视口裁剪(P20 第一步)
- 新增纯函数 MapGen:尺寸参数化(默认 64x48,可 256x192),splitmix64 随机游走河道 + 2-3 octave value noise + 泊松式布点;核心区整平后以 BFS 校验 5x6=30 个 spawn 全部可达,不可达确定性修整并复验。 - visibleTileRange/visibleTileCount 纯函数,drawWorld 改为调用之(行为不变); drawMapFitted/drawMapViewport 仅证据用。 - 存档沿用 v2/v3 边界字段,补参数化边界 round-trip 测试,未改 Sim 语义。 - 回归:Desktop 109->115、Kernel 89->90;final_digest 与基线一致。
Diffstat (limited to 'src/LivingVillage.Desktop/Game.fs')
-rw-r--r--src/LivingVillage.Desktop/Game.fs125
1 files changed, 110 insertions, 15 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index e48888e..64dc3e2 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -188,11 +188,42 @@ type LivingVillageGame() as this =
[ (1, 1); (255, 191); (509, 381); (256, 60); (256, 300); (80, 200); (430, 300) ]
let mutable tourIndex = 0
let mutable tourFrame = 0
+ // P20 证据钩子:LV_MAPGEN_SHOT=1 时用参数化生成器出整图 + 视口裁剪帧,拍完即退出。
+ let mapGenShotMode = Environment.GetEnvironmentVariable("LV_MAPGEN_SHOT") = "1"
+ let mapGenWidth =
+ match Environment.GetEnvironmentVariable("LV_MAPGEN_WIDTH") with
+ | null | "" -> 256
+ | v -> match Int32.TryParse v with (true, value) when value > 0 -> value | _ -> 256
+ let mapGenHeight =
+ match Environment.GetEnvironmentVariable("LV_MAPGEN_HEIGHT") with
+ | null | "" -> 192
+ | v -> match Int32.TryParse v with (true, value) when value > 0 -> value | _ -> 192
+ let mapGenSeed =
+ match Environment.GetEnvironmentVariable("LV_MAPGEN_SEED") with
+ | null | "" -> 4242UL
+ | v -> match UInt64.TryParse v with (true, value) -> value | _ -> 4242UL
+ let mutable mapGenResult = Unchecked.defaultof<MapGen.Result>
+ let mutable mapGenGenMs = 0.0
do
if scale > 1 then
Sim.configureBounds (64 * scale) (48 * scale)
ProceduralMap.activate (uint64 4242)
+ if mapGenShotMode then
+ let stopwatch = System.Diagnostics.Stopwatch.StartNew()
+ mapGenResult <- MapGen.generateWithSize mapGenWidth mapGenHeight mapGenSeed
+ stopwatch.Stop()
+ mapGenGenMs <- stopwatch.Elapsed.TotalMilliseconds
+ printfn
+ "mapgen-gen size=%dx%d seed=%d gen_ms=%.2f tiles=%d reachable_ok=%b reachable_tiles=%d spawns=%d"
+ mapGenResult.Width
+ mapGenResult.Height
+ (int mapGenResult.Seed)
+ mapGenGenMs
+ mapGenResult.Tiles.Length
+ mapGenResult.ReachabilityOk
+ mapGenResult.ReachableTiles
+ mapGenResult.Spawns.Length
if mapTourMode then
tourWaypoints <-
[ (1, 1); (Sim.mapWidthTiles / 2, Sim.mapHeightTiles / 2); (Sim.mapWidthTiles - 3, Sim.mapHeightTiles - 3)
@@ -659,23 +690,87 @@ type LivingVillageGame() as this =
printfn "menu-shot=done frames=%d" menuShotFrame
this.Exit()
+ /// P20 证据:把一次 MapGen 绘制渲染到离屏 target 并落盘,返回绘制瓦片数。
+ member private this.RenderMapGen (drawFitted: bool) : int * Texture2D =
+ let viewport = this.GraphicsDevice.Viewport
+ use target = new RenderTarget2D(this.GraphicsDevice, viewport.Width, viewport.Height)
+ this.GraphicsDevice.SetRenderTarget target
+ this.GraphicsDevice.Clear(Color(18, 26, 20))
+ let camX = max 0 ((mapGenResult.Width * Sim.tilePixels - viewport.Width) / 2)
+ let camY = max 0 ((mapGenResult.Height * Sim.tilePixels - viewport.Height) / 2)
+ spriteBatch.Begin()
+ let drawn =
+ if drawFitted then
+ VillageArt.drawMapFitted spriteBatch artTextures viewport.Width viewport.Height mapGenResult Color.White
+ else
+ VillageArt.drawMapViewport spriteBatch artTextures (Vector2(float32 camX, float32 camY)) viewport.Width viewport.Height mapGenResult 0L Color.White
+ spriteBatch.End()
+ this.GraphicsDevice.SetRenderTarget null
+ let data = Array.zeroCreate<Color> (viewport.Width * viewport.Height)
+ target.GetData<Color>(data)
+ let texture = new Texture2D(this.GraphicsDevice, viewport.Width, viewport.Height)
+ texture.SetData(data)
+ (drawn, texture)
+
+ /// P20 证据:整图等比渲染 + 1:1 视口裁剪帧各存一张,打印绘制计数后退出。
+ member private this.DrawMapGenShot () =
+ System.IO.Directory.CreateDirectory recordDirectory |> ignore
+ let viewport = this.GraphicsDevice.Viewport
+ let fittedDrawn, fittedTexture = this.RenderMapGen true
+ use _fitted = fittedTexture
+ use stream = System.IO.File.Create(sprintf "%s/mapgen-%dx%d-seed%d.png" recordDirectory mapGenResult.Width mapGenResult.Height (int mapGenResult.Seed))
+ fittedTexture.SaveAsPng(stream, viewport.Width, viewport.Height)
+ printfn "mapgen-fitted drawn=%d viewport=%dx%d" fittedDrawn viewport.Width viewport.Height
+
+ let camX = max 0 ((mapGenResult.Width * Sim.tilePixels - viewport.Width) / 2)
+ let camY = max 0 ((mapGenResult.Height * Sim.tilePixels - viewport.Height) / 2)
+ let culledDrawn, culledTexture = this.RenderMapGen false
+ use _culled = culledTexture
+ use culledStream = System.IO.File.Create(sprintf "%s/mapgen-culled-%dx%d-seed%d.png" recordDirectory mapGenResult.Width mapGenResult.Height (int mapGenResult.Seed))
+ culledTexture.SaveAsPng(culledStream, viewport.Width, viewport.Height)
+ let total = mapGenResult.Width * mapGenResult.Height
+ let visible = MapGen.visibleTileCount mapGenResult.Width mapGenResult.Height camX camY viewport.Width viewport.Height
+ printfn "mapgen-cull drawn=%d visible=%d total=%d culled=%.1f%%" culledDrawn visible total (100.0 * (1.0 - float culledDrawn / float total))
+
+ let smallWatch = System.Diagnostics.Stopwatch.StartNew()
+ let smallMap = MapGen.generateWithSize 64 48 (uint64 4242)
+ smallWatch.Stop()
+ printfn "mapgen-gen-legacy size=%dx%d gen_ms=%.2f tiles=%d reachable_ok=%b" smallMap.Width smallMap.Height smallWatch.Elapsed.TotalMilliseconds smallMap.Tiles.Length smallMap.ReachabilityOk
+
+ let frameWatch = System.Diagnostics.Stopwatch.StartNew()
+ use frameTarget = new RenderTarget2D(this.GraphicsDevice, viewport.Width, viewport.Height)
+ this.GraphicsDevice.SetRenderTarget frameTarget
+ for _ in 1 .. 30 do
+ spriteBatch.Begin()
+ VillageArt.drawMapViewport spriteBatch artTextures (Vector2(float32 camX, float32 camY)) viewport.Width viewport.Height mapGenResult 0L Color.White |> ignore
+ spriteBatch.End()
+ this.GraphicsDevice.SetRenderTarget null
+ frameWatch.Stop()
+ let perFrameMs = frameWatch.Elapsed.TotalMilliseconds / 30.0
+ printfn "mapgen-frame avg_ms=%.2f implied_fps=%.1f iterations=30" perFrameMs (1000.0 / perFrameMs)
+ printfn "mapgen-shot=done"
+ this.Exit()
+
override this.Draw(gameTime: GameTime) =
- if splashActive then
- this.DrawSplash()
- elif menu.Page <> Playing then
- this.DrawMenu()
- elif relationView then
- this.DrawRelationView()
- this.DrawM5Overlay()
+ if mapGenShotMode then
+ this.DrawMapGenShot()
else
- this.DrawWorldView()
- if recordMode && sampleMode then
- recordIndex <- recordIndex + 1
- if recordIndex % recordEvery = 0 then
- System.IO.Directory.CreateDirectory recordDirectory |> ignore
- this.SaveBackBuffer(sprintf "%s/rec-frame-%04d.png" recordDirectory recordIndex)
- if menuShotMode then
- this.CaptureMenuShot()
+ if splashActive then
+ this.DrawSplash()
+ elif menu.Page <> Playing then
+ this.DrawMenu()
+ elif relationView then
+ this.DrawRelationView()
+ this.DrawM5Overlay()
+ else
+ this.DrawWorldView()
+ if recordMode && sampleMode then
+ recordIndex <- recordIndex + 1
+ if recordIndex % recordEvery = 0 then
+ System.IO.Directory.CreateDirectory recordDirectory |> ignore
+ this.SaveBackBuffer(sprintf "%s/rec-frame-%04d.png" recordDirectory recordIndex)
+ if menuShotMode then
+ this.CaptureMenuShot()
/// 启动画面:分层天色 + 屋脊/水面剪影 + 标题淡入上浮 + 跳过提示,全部为帧计数纯函数。
member private this.DrawSplash() =