summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/Game.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop/Game.fs')
-rw-r--r--src/LivingVillage.Desktop/Game.fs131
1 files changed, 127 insertions, 4 deletions
diff --git a/src/LivingVillage.Desktop/Game.fs b/src/LivingVillage.Desktop/Game.fs
index 8c1ea21..a83f04c 100644
--- a/src/LivingVillage.Desktop/Game.fs
+++ b/src/LivingVillage.Desktop/Game.fs
@@ -396,6 +396,10 @@ type LivingVillageGame() as this =
// 6/12/18/22 时各拍一帧(同机位),四张真实帧用于四时段光照档差异核验。
let p73ShotMode = Environment.GetEnvironmentVariable("LV_P73_SHOT") = "1"
let mutable p73Step = 0
+ // P78 证据钩子:LV_P78_SHOT=1 时在同一机位拍「白天 / 夜晚」两帧,机位同时纳入桥头灯柱、
+ // 河面与一户带炊烟的民居,用于核验夜帧水面光斑/桥头窄光晕/炊烟与白天零回归。
+ let p78ShotMode = Environment.GetEnvironmentVariable("LV_P78_SHOT") = "1"
+ let mutable p78Step = 0
// P52 证据钩子:LV_P52_SHOT=1 时选职业开局 → 走到 NPC 旁用真实 Interact 开对话 →
// 真实 BuyFromTarget / SellToTarget 各一次,拍「对话入口 / 买入后 / 卖出后」三帧;
// 每笔交易打印钱与背包数量前后值,供 analyze 脚本算 delta。
@@ -1482,7 +1486,114 @@ type LivingVillageGame() as this =
let placeCameraOn (tileX: int) (tileY: int) =
world <- { world with Avatar = { world.Avatar with Pos = { X = float32 (tileX * Sim.tilePixels); Y = float32 (tileY * Sim.tilePixels) } } }
this.CenterCamera()
- if p73ShotMode then
+ if p78ShotMode then
+ // 机位:倒影最长的桥头灯柱 → 其最近一户民居的中点;同机位先拍正午(白天对照),
+ // 再拍 22 时(夜帧),两帧差异只来自光照与夜帧加成层。
+ match riverscapeMap with
+ | Some map ->
+ let bridgePick =
+ SceneDetail.bridgeLanternTiles map
+ |> List.sortByDescending (fun (x, y) -> SceneDetail.reflectionLength map x y)
+ |> List.tryHead
+ match bridgePick with
+ | None ->
+ printfn "p78-shot=error no-bridge-lantern"
+ this.Exit()
+ | Some (bx, by) ->
+ let nearestHouse =
+ (map.Buildings @ map.Farmhouses)
+ |> List.sortBy (fun b -> abs (b.DoorX - bx) + abs (b.DoorY - by))
+ |> List.tryHead
+ let camTileX, camTileY =
+ match nearestHouse with
+ | Some b -> (bx + b.DoorX) / 2, (by + b.DoorY) / 2
+ | None -> bx, by
+ let waterBounds () =
+ let vw = this.GraphicsDevice.Viewport.Width
+ let vh = this.GraphicsDevice.Viewport.Height
+ let visible =
+ [ for y in 0 .. map.Height - 1 do
+ for x in 0 .. map.Width - 1 do
+ if SceneDetail.isWaterSurface map x y then
+ let sx = x * Sim.tilePixels - int camera.X
+ let sy = y * Sim.tilePixels - int camera.Y
+ if sx + Sim.tilePixels > 0 && sx < vw && sy + Sim.tilePixels > 0 && sy < vh then
+ yield (sx, sy) ]
+ match visible with
+ | [] -> (0, 0, 0, 0)
+ | _ ->
+ let xs = visible |> List.map fst
+ let ys = visible |> List.map snd
+ let x0, y0 = List.min xs, List.min ys
+ let x1 = List.max xs + Sim.tilePixels
+ let y1 = List.max ys + Sim.tilePixels
+ (x0, y0, x1 - x0, y1 - y0)
+ let smokeScreen =
+ match nearestHouse with
+ | Some b -> (b.DoorX * Sim.tilePixels - int camera.X, (b.DoorY - 3) * Sim.tilePixels - int camera.Y)
+ | None -> (-1, -1)
+ let vw = this.GraphicsDevice.Viewport.Width
+ let vh = this.GraphicsDevice.Viewport.Height
+ let screenX (x: int) = x * Sim.tilePixels - int camera.X
+ let screenY (y: int) = y * Sim.tilePixels - int camera.Y
+ let visibleTile (x: int) (y: int) =
+ screenX x >= 0 && screenX x + Sim.tilePixels <= vw && screenY y >= 0 && screenY y + Sim.tilePixels <= vh
+ let glintTiles = SceneDetail.waterGlintTiles map
+ let glintPick = glintTiles |> List.tryPick (fun (x, y) -> if visibleTile x y then Some(x, y) else None)
+ let glintCtl =
+ glintPick
+ |> Option.bind (fun (gx, gy) ->
+ [ (gx + 1, gy); (gx + 2, gy); (gx - 1, gy) ]
+ |> List.tryFind (fun (cx, cy) -> SceneDetail.isWaterSurface map cx cy && not (List.contains (cx, cy) glintTiles)))
+ let bridgeScreen = SceneDetail.bridgeLanternTiles map |> List.tryFind (fun (x, y) -> visibleTile x y)
+ let regionFmt (x, y) = sprintf "%d,%d" (screenX x) (screenY y)
+ let regionLine (tag: string) =
+ printfn
+ "p78-regions %s glint_src=%s glint_ctl=%s bridge_src=%s"
+ tag
+ (match glintPick with Some p -> regionFmt p | None -> "-1,-1")
+ (match glintCtl with Some p -> regionFmt p | None -> "-1,-1")
+ (match bridgeScreen with Some p -> regionFmt p | None -> "-1,-1")
+ if p78Step = 0 then
+ setHour 12.0
+ placeCameraOn camTileX camTileY
+ p47PendingName <- "p78-day"
+ p47Pending <- true
+ let wx, wy, ww, wh = waterBounds ()
+ printfn
+ "p78-shot=day hour=12.0 tick=%d lantern=%.2f water=%d,%d,%d,%d glints=%d smoke=%d bridge_halo=%d"
+ world.Tick
+ (M6Presentation.effectiveLanternGlow world.Tick)
+ wx wy ww wh
+ (List.length (SceneDetail.waterGlintTiles map))
+ (List.length (SceneDetail.smokeSourceTiles map))
+ (List.length (SceneDetail.bridgeLanternTiles map))
+ regionLine "day"
+ elif p78Step = 1 then
+ // 保持同一机位,只把时刻改到夜晚。
+ setHour 22.0
+ p47PendingName <- "p78-night"
+ p47Pending <- true
+ let wx, wy, ww, wh = waterBounds ()
+ printfn
+ "p78-shot=night hour=22.0 tick=%d lantern=%.2f water=%d,%d,%d,%d smoke_src=%d,%d smoke_alpha=%d smoke_cell=%d glint_alpha=%d"
+ world.Tick
+ (M6Presentation.effectiveLanternGlow world.Tick)
+ wx wy ww wh
+ (fst smokeScreen) (snd smokeScreen)
+ (SceneDetail.smokeAlphaAt (M6Presentation.effectiveLanternGlow world.Tick))
+ (SceneDetail.smokeCellSize (M6Presentation.effectiveLanternGlow world.Tick))
+ (SceneDetail.waterGlintAlpha
+ (M6Presentation.effectiveLanternGlow world.Tick)
+ (SceneDetail.waterGlintPhase world.Tick camTileX camTileY))
+ regionLine "night"
+ else
+ printfn "p78-shot=done"
+ this.Exit()
+ | None ->
+ printfn "p78-shot=error no-riverscape-map"
+ this.Exit()
+ elif p73ShotMode then
// 同一桥头光源机位的四时段巡拍;tick 只经 setHour 改变,保证画面差异只来自光照档。
let hours = [ ("p73-morning", 6.0); ("p73-noon", 12.0); ("p73-dusk", 18.0); ("p73-night", 22.0) ]
if p73Step < hours.Length then
@@ -1626,6 +1737,10 @@ type LivingVillageGame() as this =
printfn "p73-frame=%s tick=%d pos=(%.0f,%.0f) camera=(%.0f,%.0f)"
p47PendingName world.Tick world.Avatar.Pos.X world.Avatar.Pos.Y camera.X camera.Y
p73Step <- p73Step + 1
+ elif p78ShotMode then
+ printfn "p78-frame=%s tick=%d camera=(%.0f,%.0f)"
+ p47PendingName world.Tick camera.X camera.Y
+ p78Step <- p78Step + 1
else
printfn "p47-shot=%s tick=%d pos=(%.0f,%.0f) camera=(%.0f,%.0f)"
p47PendingName world.Tick world.Avatar.Pos.X world.Avatar.Pos.Y camera.X camera.Y
@@ -2431,7 +2546,7 @@ type LivingVillageGame() as this =
elif p57ShotMode then
if menu.Page = MainMenu then
this.PrepareP57Shot()
- elif p47ShotMode || p73ShotMode then
+ elif p47ShotMode || p73ShotMode || p78ShotMode then
if menu.Page = MainMenu then
this.DispatchMenuInput Confirm
elif menu.Page = OccupationSelect then
@@ -2925,7 +3040,7 @@ type LivingVillageGame() as this =
this.CaptureP46Shot()
if p57ShotMode then
this.CaptureP57Shot()
- if (p47ShotMode || p73ShotMode) && menu.Page = Playing then
+ if (p47ShotMode || p73ShotMode || p78ShotMode) && menu.Page = Playing then
this.CaptureP47Shot()
if p52ShotMode && menu.Page = Playing then
this.CaptureP52Shot()
@@ -3259,7 +3374,7 @@ type LivingVillageGame() as this =
VillageArt.drawCc0BoatOverlay spriteBatch artTextures camera map worldTint
| _ -> ()
- // P47 场景细节:河道倒影 + 岸边芦苇加密度 + 漂浮元素 + 近景楼层层错落。
+ // P47 场景细节:河道倒影 + 岸边芦苇加密度 + 漂浮元素 + 近景楼层层错落 + P78 炊烟。
// 画在夜光/船之后,保证倒影不被灯笼呼吸光晕盖掉、仍可辨认。
match riverscapeMap with
| Some map ->
@@ -3272,6 +3387,7 @@ type LivingVillageGame() as this =
viewport.Height
map
world.Tick
+ lanternGlow
| None -> ()
VillageArt.drawGroundShadow spriteBatch pixel camera world.Avatar.Pos
@@ -3304,6 +3420,13 @@ type LivingVillageGame() as this =
if period.VeilAlpha > 0 then
let v = period.Veil
spriteBatch.Draw(pixel, Rectangle(0, 0, viewport.Width, viewport.Height), VillageArt.premultiply (int v.R) (int v.G) (int v.B) period.VeilAlpha)
+
+ // P78:夜帧加成层(水面逐格微光斑 + 桥头灯柱窄光晕)。画在全部 veil 之后,
+ // 故不被夜色乘暗、暗部层次仍可读;白天 lanternGlow=0,本层为空、白天帧零回归。
+ match riverscapeMap with
+ | Some map when lanternGlow > 0.0f ->
+ SceneDetailRender.drawNight spriteBatch pixel camera map world.Tick lanternGlow
+ | _ -> ()
spriteBatch.End()
// Contextual floating prompt over the unique target; same CJK atlas font path as the HUD.