static void RunStudioEditorChecks() { EditorApp app; app.LoadLevel("../assets/map.dat"); auto original=app.CaptureSnapshot(); auto before=original; app.RecordLevelEdit(before,71); Check(app.undoStack.empty(),"selection-only gesture creates no undo entry"); app.playerStartX=7; app.RecordLevelEdit(before,71); before=app.CaptureSnapshot(); app.playerStartX=8; app.RecordLevelEdit(before,71); Check(app.undoStack.size()==1,"continuous inspector gesture creates exactly one snapshot"); app.RecordLevelEdit(app.CaptureSnapshot(),0); before=app.CaptureSnapshot(); app.floorColor=0; app.RecordLevelEdit(before,72); Check(app.undoStack.size()==2,"next property edit gets an independent snapshot"); app.Undo(); Check(app.playerStartX==8 && app.floorColor==original.floorColor,"undo palette preserves preceding transform"); app.Undo(); Check(EditorApp::SameLevel(app.CaptureSnapshot(),original),"undo continuous transform restores initial state"); app.Redo(); app.Redo(); Check(app.playerStartX==8 && app.floorColor==0,"redo restores both authored edits"); app.Undo(); before=app.CaptureSnapshot(); app.ceilingColor=123; app.RecordLevelEdit(before,0); Check(app.redoStack.empty(),"new edit after undo invalidates redo branch"); before=app.CaptureSnapshot(); app.sprites[0].x+=10; app.sprites[0].scale=131072; app.sprites[0].textureId=13; SetSpriteEntityData(app.sprites[0],ENTITY_ARCHETYPE_GUARD,55,3,8); app.transitions[0].targetSpawnX=9; app.transitions[0].targetAngle=128; strcpy_s(app.transitions[0].targetMap,"other.dat"); app.cutscene.flags=CUTSCENE_AUTOPLAY; app.RecordLevelEdit(before,0); app.draggingLightRadiusIdx=2; app.strokeSnapshotTaken=true; app.Undo(); Check(EditorApp::SameLevel(app.CaptureSnapshot(),before),"snapshot restores sprite parameters portal names and cutscene flags together"); Check(app.draggingLightRadiusIdx==-1 && !app.strokeSnapshotTaken,"undo cancels light-radius drag and paint stroke"); for(int i=0;i<80;++i) {before=app.CaptureSnapshot(); app.floorColor=i+1; app.RecordLevelEdit(before,0);} Check(app.undoStack.size()==64,"history remains bounded to sixty-four snapshots"); BinaryLevel level; std::string error; ReadLevelFile("../assets/map.dat",level,error); level.cells[10*32+10]=CELL_DOOR; SetSwitchDoor(level.sprites[0],10,10,true); Check(ValidateLevel(level,error) && HasSwitchDoor(level.sprites[0]),"switch binding uses existing v5 sprite bytes"); level.cells[10*32+10]=CELL_EMPTY; Check(!ValidateLevel(level,error),"switch binding cannot target a non-door cell"); level.cells[10*32+10]=CELL_DOOR; level.sprites.push_back(level.sprites[0]); Check(!ValidateLevel(level,error),"duplicate door bindings are rejected"); level.sprites.pop_back(); level.sprites[0].flags|=0x20000000; Check(!ValidateLevel(level,error),"out-of-bounds switch target is rejected"); // Drive actual ImGui inspector controls, not a duplicate model of UI edits. ImGui::CreateContext(); auto& io=ImGui::GetIO(); io.IniFilename=nullptr; io.DisplaySize=ImVec2(1400,1000); io.DeltaTime=1.0f/60; unsigned char* pixels; int w,h; io.Fonts->GetTexDataAsRGBA32(&pixels,&w,&h); app.LoadLevel("../assets/map.dat"); app.selectedSpriteIdx=-1; app.selectedTransitionIdx=-1; auto frame=[&](const char* target, const char* text) { if(text) io.AddInputCharactersUTF8(text); ImGui::NewFrame(); ImGui::SetNextWindowPos(ImVec2(0,0)); ImGui::SetNextWindowSize(ImVec2(600,950)); ImGui::Begin("InspectorTest",nullptr,ImGuiWindowFlags_NoSavedSettings); if(target) { auto& g=*ImGui::GetCurrentContext(); g.NavActivateId=g.NavActivateDownId=ImGui::GetID(target); g.NavActivateFlags=ImGuiActivateFlags_PreferInput; } app.RenderInspector(); ImGui::End(); ImGui::Render(); }; frame(nullptr,nullptr); frame("Spawn X",nullptr); frame(nullptr,"7"); io.AddKeyEvent(ImGuiKey_Enter,true); frame(nullptr,nullptr); io.AddKeyEvent(ImGuiKey_Enter,false); frame(nullptr,nullptr); Check(app.playerStartX==7 && app.undoStack.size()==1,"real inspector spawn input records original authored state"); app.Undo(); Check(app.playerStartX==2,"real inspector spawn input can be undone"); app.Redo(); Check(app.playerStartX==7,"real inspector spawn input can be redone"); app.selectedSpriteIdx=0; frame(nullptr,nullptr); auto oldX=app.sprites[0].x; auto count=app.undoStack.size(); frame("Позиция X",nullptr); frame(nullptr,"6"); io.AddKeyEvent(ImGuiKey_Enter,true); frame(nullptr,nullptr); io.AddKeyEvent(ImGuiKey_Enter,false); frame(nullptr,nullptr); Check(app.sprites[0].x==384 && app.undoStack.size()==count+1,"real sprite inspector transform participates in history"); app.Undo(); Check(app.sprites[0].x==oldX,"real sprite inspector undo restores raw world coordinates"); app.selectedSpriteIdx=-1; app.selectedTransitionIdx=0; auto oldSpawn=app.transitions[0].targetSpawnX; count=app.undoStack.size(); frame(nullptr,nullptr); frame("Целевой Spawn X",nullptr); frame(nullptr,"11"); io.AddKeyEvent(ImGuiKey_Enter,true); frame(nullptr,nullptr); io.AddKeyEvent(ImGuiKey_Enter,false); frame(nullptr,nullptr); Check(app.transitions[0].targetSpawnX==11 && app.undoStack.size()==count+1,"real portal destination input records a transaction"); app.Undo(); Check(app.transitions[0].targetSpawnX==oldSpawn,"real portal destination undo restores original spawn"); app.selectedTransitionIdx=-1; auto oldPalette=app.CaptureSnapshot(); count=app.undoStack.size(); frame(nullptr,nullptr); frame("Хоррор",nullptr); frame(nullptr,nullptr); Check(app.ceilingColor==0x003B0710 && app.undoStack.size()==count+1,"real environment preset records both colors once"); app.Undo(); Check(app.ceilingColor==oldPalette.ceilingColor && app.floorColor==oldPalette.floorColor, "real environment preset undo restores ceiling and floor"); app.viewportMode=2; app.showCutsceneTimeline=false; auto uiBefore=app.CaptureSnapshot(); count=app.undoStack.size(); for (float width : {1000.0f,1360.0f,1920.0f}) { io.DisplaySize=ImVec2(width,840); app.contentDrawerOpen=true; ImGui::NewFrame(); app.RenderUI(); ImGui::Render(); ImGui::NewFrame(); app.RenderUI(); ImGui::Render(); } Check(ImGui::GetDrawData()->TotalVtxCount>0 && app.editDepth==0, "full workspace and content drawer render at supported window widths"); Check(EditorApp::SameLevel(uiBefore,app.CaptureSnapshot()) && app.undoStack.size()==count, "workspace layout and content browsing do not dirty authored history"); ImGui::DestroyContext(); }