@@ -167,7 +167,9 @@ def stroke(x: int, y: int, dx: int, dy: int) -> None:
167167 # stroke is plainly visible in `stroke-*.png` and the diffs mean something.
168168 run_state .wheel (width // 3 , height // 2 , clicks = ZOOM_CLICKS , up = True , delay = 0.5 )
169169 click_field (run_state , left , MAP ["terrain_size" ], "400" )
170- click_field (run_state , left , MAP ["terrain_strength" ], "8" )
170+ # Shape Modify applies a signed delta, so Strength controls a visible
171+ # relief; Height remains meaningful only when the Level brush follows.
172+ click_field (run_state , left , MAP ["terrain_strength" ], "1000" )
171173 click_field (run_state , left , MAP ["terrain_height" ], "80" )
172174 run_state .screenshot ("brush-settings" )
173175
@@ -616,21 +618,23 @@ def map_export(run_state: E2ERun) -> None:
616618 width , height = window_size (run_state )
617619
618620 # A project first: Export compiles the *saved* project, so it needs a path.
619- # Save As creates it and reloads into it (the one unavoidable wait).
620- run_state .click (* panel_point (left , TOOLBAR ["save_as" ]), delay = 0.6 )
621- run_state .click (* dialog_point (run_state , DIALOG ["file_name" ]), delay = 0.15 )
621+ # Save As creates it and reloads into it. Wait for the second ready line,
622+ # rather than assuming every machine needs the old fixed eight seconds.
623+ reload_log = run_state .log_cursor ()
624+ run_state .click_settled (* panel_point (left , TOOLBAR ["save_as" ]), delay = 0.3 )
625+ run_state .click_settled (* dialog_point (run_state , DIALOG ["file_name" ]), delay = 0.1 )
622626 run_state .type_text ("ExportMap" )
623- run_state .key ( "Return" , delay = 0.15 )
624- # The one unavoidable wait: Save As restarts the engine into the new project.
625- run_state .click ( * dialog_point ( run_state , DIALOG [ "file_ok_name" ]), delay = 8.0 )
627+ run_state .click_settled ( * dialog_point ( run_state , DIALOG [ "file_ok_name" ]) , delay = 0.1 )
628+ run_state . wait_for_command ( "ReloadIntoProjectCommand" )
629+ run_state .wait_for_log ( "finished loading and is now ingame" , after = reload_log )
626630
627631 # Terrain: pick a pattern, arm Add, a fat brush, one sweep across the map.
628632 run_state .click (left + TAB_X ["map" ], TAB_Y , delay = 0.15 )
629633 run_state .click (* editor_point (left , "map" , "terrain" ), delay = 0.3 )
630634 run_state .click (* panel_point (left , MAP ["terrain_pattern" ]), delay = 0.15 )
631635 run_state .click (* panel_point (left , MAP_ACTIONS ["terrain_add" ]), delay = 0.15 )
632636 click_field (run_state , left , MAP ["terrain_size" ], "1200" )
633- click_field (run_state , left , MAP ["terrain_height " ], "80 " )
637+ click_field (run_state , left , MAP ["terrain_strength " ], "1000 " )
634638 _sweep (run_state , left , width , height )
635639 run_state .assert_command_at_least ("TerrainShapeModifyCommand" , 1 )
636640
@@ -642,15 +646,20 @@ def map_export(run_state: E2ERun) -> None:
642646 run_state .click (* panel_point (left , MAP_ACTIONS ["texture_paint" ]), delay = 0.15 )
643647 _sweep (run_state , left , width , height )
644648 run_state .assert_any_command ("TerrainChangeTextureCommand" , paintMode = "paint" )
649+ run_state .key ("Escape" , delay = 0.15 )
650+ run_state .move (* panel_point (left , PARK_PANEL ), delay = 0.15 )
651+ edited = run_state .screenshot ("edited-before-export" )
645652
646653 # Save the edits, then Export -> Spring archive (the default type).
654+ save_log = run_state .log_cursor ()
647655 run_state .key ("ctrl+s" , delay = 0.5 )
648656 run_state .assert_command_at_least ("SaveCommand" , 1 )
649- run_state .click (* panel_point (left , TOOLBAR ["export" ]), delay = 0.3 )
650- run_state .click (* dialog_point (run_state , DIALOG ["file_name" ]), delay = 0.15 )
657+ run_state .wait_for_log ("save editor state:" , after = save_log )
658+ run_state .click_settled (* panel_point (left , TOOLBAR ["export" ]), delay = 0.2 )
659+ run_state .click_settled (* dialog_point (run_state , DIALOG ["file_name" ]), delay = 0.1 )
651660 run_state .type_text ("ExportMap" )
652- run_state .key ( "Return" , delay = 0.15 )
653- run_state .click ( * dialog_point ( run_state , DIALOG [ "file_ok_export" ]), delay = 0.3 )
661+ run_state .click_settled ( * dialog_point ( run_state , DIALOG [ "file_ok_export" ]) , delay = 0.1 )
662+ run_state .wait_for_command ( "ExportSpringArchiveCommand" )
654663 run_state .assert_command ("ExportSpringArchiveCommand" )
655664
656665 archive = _wait_for_archive (run_state , "ExportMap" )
@@ -659,6 +668,49 @@ def map_export(run_state: E2ERun) -> None:
659668 if archive .stat ().st_size < 1024 :
660669 raise AssertionError (f"compiled archive is suspiciously small: { archive } " )
661670
671+ # Prove the deliverable is usable: expose this session's archive to the map
672+ # scanner, create a project on it, and check that the painted material is
673+ # still visibly present. Comparing camera frames pixel-for-pixel here is
674+ # deliberately avoided: reloading rebuilds the terrain draw and its
675+ # sub-pixel shading is not frame-stable, even when the exported texture is.
676+ # `tiles` is orange while the base map is green, so this is a direct visual
677+ # assertion that the diffuse PNG made it through mapcompile and back in.
678+ map_region = (left // 2 - 260 , height // 2 - 220 , 520 , 440 )
679+ painted_before = run_state .count_color (edited , map_region , "#C87830" , fuzz = "18%" )
680+ if painted_before < 5_000 :
681+ raise AssertionError (
682+ f"texture paint was not visibly present before export ({ painted_before } warm pixels)"
683+ )
684+ assert run_state .write_dir is not None
685+ maps_dir = run_state .write_dir / "maps"
686+ maps_dir .mkdir (parents = True , exist_ok = True )
687+ shutil .copyfile (archive , maps_dir / "ExportMap.sdz" )
688+ reload_log = run_state .log_cursor ()
689+ reload_commands = run_state .command_cursor ()
690+ run_state .click_settled (* panel_point (left , TOOLBAR ["new_project" ]), delay = 0.2 )
691+ run_state .click_settled (* dialog_point (run_state , DIALOG ["new_project_name" ]), delay = 0.1 )
692+ run_state .type_text ("FromExport" )
693+ run_state .click_settled (* dialog_point (run_state , DIALOG ["new_project_map" ]), delay = 0.2 )
694+ run_state .click_settled (
695+ * dialog_point (run_state , dropdown_option (DIALOG ["new_project_map" ], 1 )), delay = 0.2
696+ )
697+ run_state .click_settled (
698+ * dialog_point (run_state , DIALOG ["new_project_create_nosize" ]), delay = 0.1
699+ )
700+ run_state .wait_for_command ("ReloadIntoProjectCommand" , after = reload_commands )
701+ run_state .wait_for_log ("finished loading and is now ingame" , after = reload_log )
702+ reopened = run_state .screenshot ("export-reopened" )
703+ painted_after = run_state .count_color (reopened , map_region , "#C87830" , fuzz = "18%" )
704+ if painted_after < 5_000 :
705+ raise AssertionError (
706+ f"exported map lost its painted diffuse texture ({ painted_after } warm pixels)"
707+ )
708+ if abs (painted_after - painted_before ) > painted_before // 5 :
709+ raise AssertionError (
710+ "exported diffuse texture moved within the map: "
711+ f"{ painted_before } warm pixels before export, { painted_after } after reopening"
712+ )
713+
662714
663715@scenario ()
664716def map_roundtrip (run_state : E2ERun ) -> None :
0 commit comments