paavohtl Claude Opus 4.6 commited on
Commit
d91b3ec
·
1 Parent(s): 6adcfaf

feat: add runtime-validated PCLandscape struct, landscape dump via F10 hotkey

Browse files

Rewrote PCLandscape struct with fields verified against live game memory:
crater sprites, collision bitmap, dirty rect queue, 6 terrain layers,
level/theme paths, visible bounds, GfxHandlers. Added DirtyRect struct
and vtable_slots module. Added 5 new landscape function addresses.
Validator now dumps full PCLandscape state on F10 or after 15s delay.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

crates/openwa-types/src/address.rs CHANGED
@@ -171,9 +171,18 @@ pub mod va {
171
  // === Landscape ===
172
 
173
  pub const CONSTRUCT_PC_LANDSCAPE: u32 = 0x0057_ACB0;
 
174
  pub const CONSTRUCT_SPRITE_REGION: u32 = 0x0057_DB20;
175
  pub const REDRAW_LAND_REGION: u32 = 0x0057_CC10;
176
  pub const WRITE_LAND_RAW: u32 = 0x0057_C300;
 
 
 
 
 
 
 
 
177
 
178
  // === Sound ===
179
 
 
171
  // === Landscape ===
172
 
173
  pub const CONSTRUCT_PC_LANDSCAPE: u32 = 0x0057_ACB0;
174
+ pub const DESTRUCT_PC_LANDSCAPE: u32 = 0x0057_B540;
175
  pub const CONSTRUCT_SPRITE_REGION: u32 = 0x0057_DB20;
176
  pub const REDRAW_LAND_REGION: u32 = 0x0057_CC10;
177
  pub const WRITE_LAND_RAW: u32 = 0x0057_C300;
178
+ /// Applies explosion crater to terrain — destroys pixels + collision (vtable slot 2).
179
+ pub const PC_LANDSCAPE_APPLY_EXPLOSION: u32 = 0x0057_C820;
180
+ /// Draws 8px checkered borders at landscape edges (vtable slot 6).
181
+ pub const PC_LANDSCAPE_DRAW_BORDERS: u32 = 0x0057_D7F0;
182
+ /// Redraws a single terrain row (vtable slot 8).
183
+ pub const PC_LANDSCAPE_REDRAW_ROW: u32 = 0x0057_CF60;
184
+ /// Clips and merges dirty rectangles for terrain redraw.
185
+ pub const PC_LANDSCAPE_CLIP_AND_MERGE: u32 = 0x0057_D2B0;
186
 
187
  // === Sound ===
188
 
crates/openwa-types/src/landscape.rs CHANGED
@@ -1,61 +1,121 @@
1
  use crate::task::Ptr32;
2
 
3
- /// PCLandscape — terrain/landscape subsystem.
4
  ///
5
- /// Created by PCLandscape__Constructor (0x57ACB0).
6
- /// Vtable: 0x66B208
7
  ///
8
- /// Manages terrain data, water effects, and level graphics.
9
- /// Loads Water.dir and Level.dir from `data\Gfx\`.
10
- /// Allocates a 384KB (0x60000) terrain buffer.
11
  ///
12
- /// PARTIAL: Only constructor-confirmed fields are defined.
13
- /// Ghidra uses DWORD-indexed offsets (param_1[0x240] etc.).
14
- /// Byte offset = dword_index * 4.
 
 
 
 
 
 
 
 
 
 
 
15
  #[repr(C)]
16
  pub struct PCLandscape {
17
  /// 0x000: Vtable pointer (0x66B208)
18
  pub vtable: Ptr32,
19
- /// 0x004: Parent DDGame pointer
20
  pub ddgame: Ptr32,
21
- /// 0x008-0x0C7: Unknown
22
- pub _unknown_008: [u8; 0xC0],
23
- /// 0x0C8: Water effect object pointer (0xBC bytes, vtable 0x66B268)
24
- pub water_effect: Ptr32,
25
- /// 0x0CC-0x8E7: Unknown fields (terrain metadata, dimensions, etc.)
26
- pub _unknown_0cc: [u8; 0x81C],
27
- /// 0x8E8: Terrain buffer pointer (0x60000 bytes allocated)
28
- pub terrain_buffer: Ptr32,
29
- /// 0x8EC-0x8EF: Buffer size (0x60000)
30
- pub _terrain_size: [u8; 4],
31
- /// 0x8F0-0x90F: Unknown
32
- pub _unknown_8f0: [u8; 0x20],
33
- /// 0x910-0x913: Terrain handler pointer
34
- pub terrain_handler: Ptr32,
35
- /// 0x914: Initialized flag (set to 1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  pub initialized: u32,
37
- /// 0x918-0x92F: Terrain directory path pointers
38
- pub _dir_paths: [u8; 0x18],
39
- /// 0x930: Splash buffer pointer (0x4C bytes)
40
- pub splash_buffer: Ptr32,
41
- /// 0x934: Terrain data pointer
42
- pub terrain_data: Ptr32,
43
- /// 0x938: Palette pointer
44
- pub palette: Ptr32,
45
- /// 0x93C: Shader object pointer (vtable 0x66B1DC)
46
- pub shader: Ptr32,
47
- /// 0x940-0x947: Surface texture buffer pointers
48
- pub surface_textures: [Ptr32; 2],
49
- /// 0x948-0x94B: Unknown
50
- pub _unknown_948: [u8; 4],
51
- /// 0x94C-0xB33: Unknown
52
- pub _unknown_94c: [u8; 0x1E8],
53
- /// 0xB34: Water .dir file handle
54
- pub water_dir_handle: Ptr32,
55
- /// 0xB38: Level .dir file handle
56
- pub level_dir_handle: Ptr32,
57
- /// 0xB3C-0xB3F: Trailing padding
58
- pub _unknown_b3c: [u8; 4],
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
  const _: () = assert!(core::mem::size_of::<PCLandscape>() == 0xB40);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  use crate::task::Ptr32;
2
 
3
+ /// PCLandscape — terrain/landscape subsystem (0xB40 bytes).
4
  ///
5
+ /// Created by `PCLandscape__Constructor` (0x57ACB0). Vtable: 0x66B208 (32 slots).
 
6
  ///
7
+ /// Manages terrain pixel data (8-bit indexed/paletted), collision bitmap,
8
+ /// water effects, and level graphics. Loads `Water.dir` and `Level.dir`.
 
9
  ///
10
+ /// ## Rendering pipeline
11
+ /// - Terrain is stored as multiple pixel layers (DisplayGfx objects)
12
+ /// - Collision uses a 1-bit-per-pixel packed bitmap at +0x0D0
13
+ /// - Dirty rects queued via `RedrawLandRegion` (up to 256, 8 bytes each)
14
+ /// - `DrawLandscape` (0x5A2790) blits landscape pixels to framebuffer
15
+ /// (memcpy for opaque, per-pixel color-key for transparent)
16
+ /// - `WriteLandRaw` (0x57C300) modifies terrain across all layers + collision
17
+ ///
18
+ /// ## Key dimensions (stored in DDGame, set by constructor)
19
+ /// - DDGame+0x77C0: level width
20
+ /// - DDGame+0x77C4: level height
21
+ /// - DDGame+0x77C8: width × height (total pixels)
22
+ ///
23
+ /// Ghidra uses DWORD-indexed offsets (param_1[N] = byte offset N*4).
24
  #[repr(C)]
25
  pub struct PCLandscape {
26
  /// 0x000: Vtable pointer (0x66B208)
27
  pub vtable: Ptr32,
28
+ /// 0x004: Parent DDGame pointer (param_1[1])
29
  pub ddgame: Ptr32,
30
+ /// 0x008-0x0CB: Pre-rendered crater sprites for 15 explosion sizes.
31
+ /// param_1[2..17]: crater image ptrs, param_1[18..33]: secondary ptrs.
32
+ /// Indexed by `explosion_size * 15 / 100`.
33
+ pub crater_sprites: [Ptr32; 16],
34
+ pub crater_sprites_secondary: [Ptr32; 16],
35
+ /// 0x088-0x0CB: Unknown
36
+ pub _unknown_088: [u8; 0x44],
37
+ /// 0x0CC: Resource handle (param_1[0x33])
38
+ pub resource_handle: Ptr32,
39
+ /// 0x0D0: Collision bitmap pointer — 1 bit per pixel, packed into DWORDs.
40
+ /// Width = (level_width + 7) / 8 rounded to 4-byte alignment.
41
+ pub collision_bitmap: Ptr32,
42
+ /// 0x0D4: Dirty rect array — 256 entries, each 8 bytes (x1,y1,x2,y2 as u16).
43
+ /// Queued by `RedrawLandRegion`, flushed during frame render.
44
+ pub dirty_rects: [DirtyRect; 256],
45
+ /// 0x8D4: Number of dirty rects queued (max 256, overflows call flush)
46
+ pub dirty_rect_count: u32,
47
+ /// 0x8D8: Dirty flag — set to 1 when any rect is queued
48
+ pub dirty_flag: u8,
49
+ /// 0x8D9-0x8EB: Unknown
50
+ pub _unknown_8d9: [u8; 0x13],
51
+ /// 0x8EC: Unknown (zero at runtime)
52
+ pub _unknown_8ec: u32,
53
+ /// 0x8F0: Unknown (0x9B at runtime — small integer, not pointer)
54
+ pub _unknown_8f0: u32,
55
+ /// 0x8F4: Unknown (zero at runtime)
56
+ pub _unknown_8f4: u32,
57
+ /// 0x8F8-0x8FF: Unknown
58
+ pub _unknown_8f8: [u8; 8],
59
+ /// 0x900: Unknown (NOT DDGame — runtime value 0x13300048 doesn't match DDGame ptr)
60
+ pub _unknown_900: Ptr32,
61
+ /// 0x904: Initialized flag (param_1[0x241], set to 1)
62
  pub initialized: u32,
63
+ /// 0x908: Terrain layer 0 — DisplayGfx* (collision visual / background)
64
+ pub layer_0: Ptr32,
65
+ /// 0x90C: Terrain layer 1 — DisplayGfx*
66
+ pub layer_1: Ptr32,
67
+ /// 0x910: Terrain layer 2 — DisplayGfx* (main terrain image).
68
+ /// Pixel data at `*(layer_2 + 8)`, width at `*(layer_2 + 0x14)`,
69
+ /// height at `*(layer_2 + 0x18)`, stride at `*(layer_2 + 0x10)`.
70
+ pub layer_terrain: Ptr32,
71
+ /// 0x914: Terrain layer 3 — DisplayGfx* (edge/shading layer)
72
+ pub layer_edges: Ptr32,
73
+ /// 0x918-0x91B: Unknown
74
+ pub _unknown_918: [u8; 4],
75
+ /// 0x91C: Terrain layer 4 — DisplayGfx* (shadow/overlay)
76
+ pub layer_shadow: Ptr32,
77
+ /// 0x920: Terrain layer 5 — DisplayGfx*
78
+ pub layer_5: Ptr32,
79
+ /// 0x924: Level directory path (char buffer, ~0x100 bytes)
80
+ pub level_dir_path: [u8; 0x100],
81
+ /// 0xA24: Theme/data directory path (char buffer, 0x100 bytes)
82
+ pub theme_dir_path: [u8; 0x100],
83
+ /// 0xB24-0xB2F: Visible bounds (left, top, right, bottom)
84
+ pub visible_left: u32,
85
+ pub visible_top: u32,
86
+ pub visible_right: u32,
87
+ pub visible_bottom: u32,
88
+ /// 0xB34: GfxHandler for Level.dir (param_1[0x2CD])
89
+ pub level_gfx_handler: Ptr32,
90
+ /// 0xB38: GfxHandler for Water.dir (param_1[0x2CE])
91
+ pub water_gfx_handler: Ptr32,
92
+ /// 0xB3C: Remaining control flag
93
+ pub _unknown_b3c: u32,
94
  }
95
 
96
  const _: () = assert!(core::mem::size_of::<PCLandscape>() == 0xB40);
97
+
98
+ /// A dirty rectangle entry in the PCLandscape dirty rect queue.
99
+ /// Coordinates are in landscape pixel space.
100
+ #[repr(C)]
101
+ #[derive(Clone, Copy)]
102
+ pub struct DirtyRect {
103
+ pub x1: u16,
104
+ pub y1: u16,
105
+ pub x2: u16,
106
+ pub y2: u16,
107
+ }
108
+
109
+ const _: () = assert!(core::mem::size_of::<DirtyRect>() == 8);
110
+
111
+ /// PCLandscape vtable slot indices (32 entries at 0x66B208).
112
+ pub mod vtable_slots {
113
+ /// Slot 0: Destructor (0x57B540)
114
+ pub const DESTRUCTOR: usize = 0;
115
+ /// Slot 2: Apply explosion crater (0x57C820) — terrain destruction
116
+ pub const APPLY_EXPLOSION: usize = 2;
117
+ /// Slot 6: Draw borders — 8px checkered borders at landscape edges (0x57D7F0)
118
+ pub const DRAW_BORDERS: usize = 6;
119
+ /// Slot 8: Redraw single row (0x57CF60)
120
+ pub const REDRAW_ROW: usize = 8;
121
+ }
crates/openwa-validator/src/lib.rs CHANGED
@@ -617,6 +617,158 @@ fn dump_team_blocks() {
617
  }
618
  }
619
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  // ---------------------------------------------------------------------------
621
  // Main validation entry point
622
  // ---------------------------------------------------------------------------
@@ -678,16 +830,27 @@ fn run_validation() -> Result<(), Box<dyn std::error::Error>> {
678
  });
679
  let _ = log_line(" Team block dump thread started (30s delay).");
680
 
681
- // 9. Hotkey listener — F9 triggers a team block dump on demand
 
 
 
 
 
 
 
682
  std::thread::spawn(|| {
683
  const VK_F9: i32 = 0x78;
684
- let _ = log_line(" Hotkey listener started (press F9 to dump team blocks).");
 
685
  loop {
686
  std::thread::sleep(std::time::Duration::from_millis(100));
687
- let state = unsafe { GetAsyncKeyState(VK_F9) };
688
- // Bit 0 = key was pressed since last call
689
- if state & 1 != 0 {
690
- dump_team_blocks();
 
 
 
691
  }
692
  }
693
  });
 
617
  }
618
  }
619
 
620
+ // ---------------------------------------------------------------------------
621
+ // PCLandscape dump (for landscape struct verification)
622
+ // ---------------------------------------------------------------------------
623
+
624
+ fn dump_landscape() {
625
+ use openwa_types::landscape::PCLandscape;
626
+
627
+ let dump_num = DUMP_COUNTER.fetch_add(1, Ordering::Relaxed);
628
+ let _ = log_line("");
629
+ let _ = log_line(&format!("--- PCLandscape Dump #{} ---", dump_num));
630
+
631
+ unsafe {
632
+ let session_ptr = read_u32(rb(va::G_GAME_SESSION));
633
+ if session_ptr == 0 {
634
+ let _ = log_line(" No game session — skipping.");
635
+ return;
636
+ }
637
+
638
+ let wrapper_addr = read_u32(session_ptr + 0xA0);
639
+ if wrapper_addr == 0 {
640
+ let _ = log_line(" No DDGameWrapper — need to start a game first.");
641
+ return;
642
+ }
643
+
644
+ let landscape_ptr = read_u32(wrapper_addr + 0x4CC);
645
+ if landscape_ptr == 0 {
646
+ let _ = log_line(" PCLandscape is NULL — no level loaded.");
647
+ return;
648
+ }
649
+
650
+ let land = &*(landscape_ptr as *const PCLandscape);
651
+ let _ = log_line(&format!(" PCLandscape @ 0x{:08X}", landscape_ptr));
652
+
653
+ // Vtable validation
654
+ let expected_vt = rb(va::PC_LANDSCAPE_VTABLE);
655
+ let vt_ok = land.vtable == expected_vt;
656
+ let _ = log_line(&format!(" vtable: 0x{:08X} (expected 0x{:08X}) {}",
657
+ land.vtable, expected_vt, if vt_ok { "OK" } else { "MISMATCH" }));
658
+
659
+ // DDGame pointer
660
+ let _ = log_line(&format!(" ddgame: 0x{:08X}", land.ddgame));
661
+ let _ = log_line(&format!(" _unknown_900: 0x{:08X}", land._unknown_900));
662
+
663
+ // Collision bitmap
664
+ let _ = log_line(&format!(" collision_bitmap: 0x{:08X}", land.collision_bitmap));
665
+
666
+ // Initialized flag
667
+ let _ = log_line(&format!(" initialized: {}", land.initialized));
668
+
669
+ // Crater sprites — count non-null
670
+ let primary_count = land.crater_sprites.iter().filter(|&&p| p != 0).count();
671
+ let secondary_count = land.crater_sprites_secondary.iter().filter(|&&p| p != 0).count();
672
+ let _ = log_line(&format!(" crater_sprites: {}/16 non-null, secondary: {}/16 non-null",
673
+ primary_count, secondary_count));
674
+
675
+ // Terrain layers
676
+ let _ = log_line(&format!(" layer_0: 0x{:08X}", land.layer_0));
677
+ let _ = log_line(&format!(" layer_1: 0x{:08X}", land.layer_1));
678
+ let _ = log_line(&format!(" layer_terrain: 0x{:08X}", land.layer_terrain));
679
+ let _ = log_line(&format!(" layer_edges: 0x{:08X}", land.layer_edges));
680
+ let _ = log_line(&format!(" layer_shadow: 0x{:08X}", land.layer_shadow));
681
+ let _ = log_line(&format!(" layer_5: 0x{:08X}", land.layer_5));
682
+
683
+ // If layer_terrain is valid, read DisplayGfx fields
684
+ if land.layer_terrain != 0 {
685
+ let dgfx = land.layer_terrain;
686
+ let pixel_data = read_u32(dgfx + 0x08);
687
+ let stride = read_u32(dgfx + 0x10);
688
+ let width = read_u32(dgfx + 0x14);
689
+ let height = read_u32(dgfx + 0x18);
690
+ let _ = log_line(&format!(" layer_terrain DisplayGfx: pixels=0x{:08X} stride={} width={} height={}",
691
+ pixel_data, stride, width, height));
692
+ }
693
+
694
+ // Dirty rects
695
+ let _ = log_line(&format!(" dirty_rect_count: {}", land.dirty_rect_count));
696
+ let _ = log_line(&format!(" dirty_flag: {}", land.dirty_flag));
697
+ if land.dirty_rect_count > 0 && land.dirty_rect_count <= 256 {
698
+ for i in 0..land.dirty_rect_count.min(5) as usize {
699
+ let r = &land.dirty_rects[i];
700
+ let _ = log_line(&format!(" rect[{}]: ({},{})..({},{})",
701
+ i, r.x1, r.y1, r.x2, r.y2));
702
+ }
703
+ if land.dirty_rect_count > 5 {
704
+ let _ = log_line(&format!(" ... and {} more", land.dirty_rect_count - 5));
705
+ }
706
+ }
707
+
708
+ // Unknown fields around palette area
709
+ let _ = log_line(&format!(" _unknown_8ec: 0x{:08X}", land._unknown_8ec));
710
+ let _ = log_line(&format!(" _unknown_8f0: 0x{:08X}", land._unknown_8f0));
711
+ let _ = log_line(&format!(" _unknown_8f4: 0x{:08X}", land._unknown_8f4));
712
+
713
+ // Resource handle
714
+ let _ = log_line(&format!(" resource_handle: 0x{:08X}", land.resource_handle));
715
+
716
+ // GfxHandlers
717
+ let _ = log_line(&format!(" level_gfx_handler: 0x{:08X}", land.level_gfx_handler));
718
+ let _ = log_line(&format!(" water_gfx_handler: 0x{:08X}", land.water_gfx_handler));
719
+
720
+ // Visible bounds
721
+ let _ = log_line(&format!(" visible_bounds: left={} top={} right={} bottom={}",
722
+ land.visible_left, land.visible_top, land.visible_right, land.visible_bottom));
723
+
724
+ // Path buffers — read as C strings
725
+ let level_path = std::ffi::CStr::from_ptr(land.level_dir_path.as_ptr() as *const i8);
726
+ let theme_path = std::ffi::CStr::from_ptr(land.theme_dir_path.as_ptr() as *const i8);
727
+ let _ = log_line(&format!(" level_dir_path: {:?}", level_path));
728
+ let _ = log_line(&format!(" theme_dir_path: {:?}", theme_path));
729
+
730
+ // DDGame level dimensions (from DDGame, not PCLandscape)
731
+ let ddgame_ptr = land.ddgame;
732
+ if ddgame_ptr != 0 {
733
+ let level_w = read_u32(ddgame_ptr + 0x77C0);
734
+ let level_h = read_u32(ddgame_ptr + 0x77C4);
735
+ let level_total = read_u32(ddgame_ptr + 0x77C8);
736
+ let _ = log_line(&format!(" DDGame level dims: {}x{} (total={})",
737
+ level_w, level_h, level_total));
738
+ }
739
+
740
+ // Vtable slot dump (first 10 entries)
741
+ let _ = log_line(" Vtable slots:");
742
+ let vt_addr = land.vtable;
743
+ for slot in 0..10u32 {
744
+ let entry = read_u32(vt_addr + slot * 4);
745
+ let in_text = is_in_text(entry);
746
+ let _ = log_line(&format!(" [{}]: 0x{:08X} {}",
747
+ slot, entry, if in_text { "" } else { "(NOT .text)" }));
748
+ }
749
+
750
+ let _ = log_line(&format!(" _unknown_b3c: 0x{:08X}", land._unknown_b3c));
751
+
752
+ // Hex dump of unknown regions for discovery
753
+ let _ = log_line(" _unknown_088 (0x44 bytes):");
754
+ let _ = log_hex_dump(&land._unknown_088);
755
+ let _ = log_line(" _unknown_8d9 (0x13 bytes):");
756
+ let _ = log_hex_dump(&land._unknown_8d9);
757
+ let _ = log_line(" _unknown_8f8 (8 bytes):");
758
+ let _ = log_hex_dump(&land._unknown_8f8);
759
+ let _ = log_line(" _unknown_918 (4 bytes):");
760
+ let _ = log_hex_dump(&land._unknown_918);
761
+ }
762
+ }
763
+
764
+ fn log_hex_dump(data: &[u8]) {
765
+ for chunk in data.chunks(16) {
766
+ let hex: String = chunk.iter().map(|b| format!("{:02X}", b)).collect::<Vec<_>>().join(" ");
767
+ let ascii: String = chunk.iter().map(|&b| if (0x20..0x7F).contains(&b) { b as char } else { '.' }).collect();
768
+ let _ = log_line(&format!(" {} | {}", hex, ascii));
769
+ }
770
+ }
771
+
772
  // ---------------------------------------------------------------------------
773
  // Main validation entry point
774
  // ---------------------------------------------------------------------------
 
830
  });
831
  let _ = log_line(" Team block dump thread started (30s delay).");
832
 
833
+ // 8b. Landscape dump — wait 15s (landscape exists at menu too)
834
+ std::thread::spawn(move || {
835
+ std::thread::sleep(std::time::Duration::from_secs(15));
836
+ dump_landscape();
837
+ });
838
+ let _ = log_line(" Landscape dump thread started (15s delay).");
839
+
840
+ // 9. Hotkey listener — F9 = team blocks, F10 = landscape
841
  std::thread::spawn(|| {
842
  const VK_F9: i32 = 0x78;
843
+ const VK_F10: i32 = 0x79;
844
+ let _ = log_line(" Hotkey listener started (F9=team blocks, F10=landscape).");
845
  loop {
846
  std::thread::sleep(std::time::Duration::from_millis(100));
847
+ unsafe {
848
+ if GetAsyncKeyState(VK_F9) & 1 != 0 {
849
+ dump_team_blocks();
850
+ }
851
+ if GetAsyncKeyState(VK_F10) & 1 != 0 {
852
+ dump_landscape();
853
+ }
854
  }
855
  }
856
  });