Youngki Jeong | Game Dev Lab
HomeProjectsEvidenceJourneyAbout
SearchGitHub글쓰기

Youngki Jeong | Game Dev Lab

Unreal C++ 게임개발 포트폴리오 & 개발 기록

Projects·Evidence·Journey·About·GitHub Portfolio·GitHub

빠른 검색

프로젝트, Evidence, 디버깅 노트, 학습 기록을 검색합니다.

← 목록으로
← 목록으로
DX11-B04

맵이 바뀌었다고 해서 정상적인 Map Transition은 아니었습니다

2026년 9월 7일
·
JEONGYOUNGKI
Description

잘못된 Spawn Source를 Last Good / First Bad Boundary까지 추적하고 기존 Canonical Restore Path로 복구한 Map Transition Debug Story입니다.

Project

DX11-SpongeBob-BFBB

ArticleType

Debugging

SourceBoundary

Historical Reconstruction

EvidenceLevel

Debugger

Priority

S++

Tags

Debugging

ProofType

Debugger

Audience

Technical Interviewer

CareerTrack

Game Client

목차

DX11-B04 | DX11-SpongeBob-BFBB — 맵이 바뀌었다고 해서 정상적인 Map Transition은 아니었습니다

한 줄 결론

Destination Map이 화면에 정상적으로 보인다고 Map Transition이 끝난 것은 아닙니다. Player / Navigation / Camera / Transient Gameplay State까지 목적지 Context로 다시 연결되어야 Restore가 끝난 것으로 보고, 마지막 증상부터 고치지 않고 Last Good → First Boundary to Recheck → Restore Result 순서로 문제 범위를 좁혔습니다.

Restore Debug Snapshot

질문답
Map이 바뀌면 Restore도 끝난 것인가?아니오. Player / Navigation / Camera / Gameplay Resume까지 이어져야 합니다.
Camera가 이상하면 Camera부터 고치는가?아니오. 앞 단계의 Request / Spawn Resolution부터 정상 범위를 닫습니다.
핵심 Debug Boundary는 어디였는가?ResolvePlayerSpawnForPending이 해석하는 Request / Spawn Policy 경계입니다.
Persistent Player는 Map마다 새로 Clone되는가?아니오. 기존 Instance를 유지하고 목적지 Transform으로 Relocate합니다.
Session State는 Disk SaveGame인가?아니오. 실행 중 Map 이동을 위한 Process-memory Restore Context입니다.
무엇으로 증명하는가?Runtime Video + Runtime State Screenshot + Request/Resolver Source + Relocate Source

먼저 아래 흐름만 잡으면 Debug Story 전체가 쉽게 읽힙니다.

Text
Map Changed
≠
Map State Restored
 
Expected
→ Actual
→ Last Good
→ First Boundary to Recheck
→ Existing Restore Flow
→ Runtime Result

이 글의 핵심은 특정 문자열 하나를 바꾼 이야기가 아니라, 화면에 보이는 Downstream 증상보다 앞쪽 State 의미를 먼저 확인해 최초로 다시 봐야 할 경계를 찾은 과정입니다.

DX11-SpongeBob-BFBB에서 가장 강하게 설명할 수 있는 Debug Story 중 하나는 목적지 Map은 정상적으로 바뀌었는데 Player / Camera / Navigation State가 기대한 상태로 복원되지 않았던 문제입니다.

처음에는 Camera나 Animation 문제처럼 보였습니다. 하지만 Map Transition을 다시 따라가 보니 화면의 Map Mesh가 바뀌는 것만으로는 전환이 끝난 것이 아니었습니다.

정상적인 Restore라고 보려면 다음 State가 목적지 기준으로 다시 연결되어야 했습니다.

  • Destination Map Content
  • Player Spawn / Relocation
  • Navigation Context
  • Camera State
  • Transient Gameplay State
  • Gameplay Resume

즉:

Map Changed ≠ Map State Restored

이 문제를 잡을 때는 마지막에 보이는 증상부터 수정하지 않고, 어디까지 정상이고 어디서부터 의미를 다시 확인해야 하는지를 앞에서부터 닫아갔습니다.

Text
Expected
→ Actual
→ Last Good
→ First Boundary to Recheck
→ Existing Restore Flow
→ Runtime Result

🎥 Video 1. Map Transition 이후 Arrival → Move → Jump → Landing까지 이어지는 Canonical Restore 결과

파일: DX11-B04_Video_01_CanonicalRestore_Regression.mkv

다른 Map 선택 → Transition → 목적지 도착 → Camera 확인 → 이동 → 점프 → 착지까지 이어서 보여줍니다.

이 영상에서 중요한 것은 Map이 바뀌는 장면 자체가 아닙니다.

Text
Destination Arrival
→ Player가 기대한 위치에 있음
→ Camera가 목적지 Player 기준으로 맞음
→ 이동 가능
→ 점프 가능
→ 착지 후 Gameplay 계속

이 영상은 Player-visible Restore가 끝난 뒤 Gameplay가 다시 이어지는지를 닫는 Runtime Evidence입니다.

Navigation Context의 내부 값 자체는 영상 한 장면으로 단정하지 않고, Source / Debugger에서 확인한 Restore 흐름과 함께 설명합니다.


Contribution Boundary

기존 Client에는 Map Runtime / Checkpoint / Persistent Player / Camera Restore 구조가 이미 있었습니다.

따라서 Map Transition Architecture 전체를 제가 처음부터 설계했다고 설명하지 않습니다.

이 글에서 보여주려는 것은 기존 Request / Resolver / Session / Relocate 경계를 Source에서 따라가고, Player-facing Travel이 기존 Canonical Restore 경로를 올바른 의미로 사용하도록 연결하면서 문제 범위를 좁힌 경험입니다.

구체적으로 다음 질문에 답할 수 있도록 구조를 따라갔습니다.

  • Destination은 어디서 결정되는가?
  • Transition Request에는 어떤 값이 들어가는가?
  • Spawn Source는 누가 결정하는가?
  • Session State는 누가 보관하는가?
  • Persistent Player는 Map 전환에서 어떻게 유지되는가?
  • Camera Restore는 어느 시점에 적용되는가?
  • 화면에 보인 증상과 실제 원인 후보는 어디에서 갈리는가?

그리고 Player-facing Travel이 기존 Canonical Restore 경로를 제대로 사용하도록 연결했습니다.


문제 상황 — Map은 정상인데 Gameplay State가 맞지 않았습니다

화면상으로는 Destination Map이 정상적으로 표시됐습니다.

하지만 실제 Gameplay State는 달랐습니다.

  • Player 위치가 기대와 다름
  • Camera Framing이 이상함
  • Player / Navigation 관계가 어긋남
  • 도착 직후 Movement / Pose / Animation이 자연스럽지 않음

이때 Camera를 바로 수정하거나 Animation을 강제로 Idle로 바꾸면 마지막 증상만 덮을 가능성이 있습니다.

그래서 Map Transition을 단계별로 나눠 확인했습니다.

Text
MapCard
→ Destination ID
→ Map-scoped Content
→ Transition Request
→ Spawn Resolution
→ Persistent Player Relocation
→ Navigation Context
→ Camera Restore
→ Gameplay Resume

확인 결과 MapCard, Destination ID, Map-scoped Content까지는 정상 범위였습니다.

즉 MapCard Click이나 Map Loader가 최초 원인 후보는 아니었습니다.

그 다음으로 Spawn Resolution Input / Policy의 의미를 다시 확인해야 했습니다.


DX11-B04_Screenshot_01_FirstBadBoundary_Flow.png

📸 Screenshot 1. First Bad Boundary를 좁힌 뒤 확인한 Player / Camera / Navigation Runtime State

파일: DX11-B04_Screenshot_01_FirstBadBoundary_Flow.png

이 Screenshot은 단순한 Flow Chart가 아니라 Gameplay 화면과 Runtime Debug Overlay를 함께 보여주는 상태 확인 Evidence입니다.

화면에서는 다음 두 묶음을 중심으로 읽습니다.

Text
[Player / Camera / Navigation]
Player Detected
Player Position
Camera Detected
Navigation Available
Navigation Cell
 
[Height / Transient State]
SpongeBob Y
Nav ComputeHeight
Ground Y
Is Jumping
Vertical Velocity

이 화면의 역할은 MapCard / Destination / Map Content가 PASS였다는 사실을 한 장으로 모두 증명하는 것이 아닙니다.

앞 단계 Debug Sequence에서 범위를:

Text
MapCard
→ Destination
→ Map Content
→ Spawn Resolution

까지 좁힌 뒤, Downstream에서 Player / Camera / Navigation State가 실제 Runtime에서 어떤 상태인지 함께 확인하는 Anchor로 사용합니다.

따라서 Evidence 역할을 분리합니다.

Text
Debugging Sequence / Source 확인
→ Last Good와 First Boundary to Recheck를 결정
 
Screenshot 1
→ Player / Camera / Navigation의 실제 Runtime 상태를 한 화면에서 확인

이 구분을 두는 이유는 Screenshot 한 장이 보여주지 않는 내용을 과하게 주장하지 않기 위해서입니다.


Spawn Resolution 경계 — ResolvePlayerSpawnForPending

Current Source에서 가장 중요하게 본 함수는:

CMapTransitionManager::ResolvePlayerSpawnForPending

입니다.

이 함수는 좌표 하나를 고르는 함수라기보다, Transition Context를 보고 어떤 Spawn Source와 Transform을 사용할지 결정하는 Policy Boundary에 가깝습니다.

입력과 결과를 정리하면 다음과 같습니다.

구분확인한 의미
Target Map어느 Map으로 이동하는가
Pending Transition StateCaller가 어떤 의미를 넘겼는가
Session / Authored Data기존 Session 또는 작성된 Spawn Data가 있는가
Transition Path / Mode어떤 Policy Branch를 타는가
Resolved Spawn Source최종적으로 어떤 Source가 선택됐는가
Resolved TransformPlayer를 어디로 Relocate할 것인가

그래서 이 문제를 볼 때 자연스럽게 다음 값들이 한 지점에 모였습니다.

  • Requested Target Map
  • Pending EntryPointTag
  • Transition Path / Mode
  • Session Validity
  • Resolved Spawn Source
  • Resolved Position

전체 흐름을 줄이면 다음과 같습니다.

단계기대당시 확인 / 해석
MapCard올바른 목적지 선택정상
Destination IDTarget Map 일치정상
Map-scoped Content목적지 Content 적용정상
Spawn Resolver Input의도한 Canonical Policy 사용의미를 다시 확인한 경계
Spawn Source목적에 맞는 Source 선택Downstream
Player Relocation목적지 Spawn TransformDownstream
Navigation목적지 Nav Context 일치Downstream
Camera복원된 Player 기준Downstream
Gameplay정상 ResumeDownstream

즉 Camera가 마지막에 이상하게 보였다고 해서 Camera가 최초 원인이라고 볼 수는 없었습니다.

Camera는 앞 단계에서 결정된 Player / Map State를 사용하는 downstream 단계였습니다.


L"Default"라는 이름보다 실제 Resolver Semantic을 봤습니다

이 문제에서 중요했던 부분 중 하나는 문자열을 사람이 읽는 의미와 코드가 실제로 해석하는 의미가 다를 수 있다는 점이었습니다.

과거 문제 경로는 현재 Source와 당시 증상을 기준으로 다음 형태로 복기했습니다.

Historical Reconstruction

C++
RequestTransition(
    Target,
    L"Default",
    L"",
    true
);

처음 보면 L"Default"를 “적당한 기본 위치를 사용한다” 정도로 읽기 쉽습니다.

하지만 Resolver 입장에서는 Default가 실제 Profile에 존재하는 Entry Tag가 될 수 있습니다.

Text
non-empty EntryPointTag
→ ResolveEntryPoint("Default")
→ exact Entry resolve
→ EMapSpawnSource::ExplicitEntry

즉 L"Default"라는 이름 자체가 generic fallback marker라고 보장해 주는 것은 아닙니다.

상황에 따라 **“Default라는 Entry를 명시적으로 요청한다”**에 가까운 의미가 될 수 있었습니다.

그리고 ExplicitEntry는 bool이 아니라 어떤 Spawn Source가 선택됐는지를 나타내는 결과 의미로 봤습니다.


Historical Reconstruction은 Current Source와 구분했습니다

위 L"Default" 호출은 과거 Git Commit에서 직접 찾아낸 코드라고 설명하지 않습니다.

현재 확보된 근거는 다음 세 가지입니다.

  • Current Resolver Source
  • Presenter Comment
  • Previous Runtime Symptom

이 자료를 바탕으로 당시 경로를 복기한 것입니다.

그래서 공개 글에서도 이 부분은 Historical Reconstruction으로 표시합니다.

“Git History에서 버그가 있던 Commit을 직접 찾았습니다.”

처럼 쓰지 않습니다.

현재 직접 확인할 수 있는 것은 Current Correct Path이고, 과거 Wrong Caller의 Commit Hash는 확인되지 않았습니다.

Debug Story를 더 강하게 보이게 만들기보다, 현재 확인 가능한 범위를 정확히 구분하는 쪽을 선택했습니다.


Current Player-facing Request는 Explicit Entry를 강제하지 않습니다

현재 Player-facing Bypass 경로는 EntryPointTag를 비운 상태로 기존 Resolver에 넘깁니다.

Text
RequestCanonicalBypassTravel
→ RequestTransition(
    Target,
    L"",
    L"",
    true
)

핵심은:

EntryPointTag = EMPTY

입니다.

이 의미는 “Default Entry를 직접 선택한다”가 아니라, Explicit Entry를 강제로 지정하지 않고 Resolver가 현재 Policy에 따라 Spawn Source를 결정하도록 둔다는 쪽에 가깝습니다.


Session이 없을 때의 Fallback은 모든 Transition에서 같지 않습니다

전체 Transition을 무조건 다음처럼 설명하면 Current Source와 맞지 않습니다.

Session → Authored Checkpoint → Default Entry

먼저 Valid Session이 있으면 Session Spawn이 우선할 수 있습니다.

하지만 Session miss 이후의 Fallback은 Transition Path / Mode에 따라 달라집니다.

Bypass Path

Text
Session miss
→ Explicit Entry
→ Authored Default Checkpoint
→ Authored Default Entry

Production Path

Text
Session miss
→ ResolveEntryPoint
→ Explicit Entry
or
→ Authored Default Entry

제가 최종적으로 잡은 Mental Model은 단순합니다.

상태의미
Session valid먼저 사용
Session missPath / Mode에 따라 Fallback Policy가 달라짐

이 정도가 Current Source를 과도하게 단순화하지 않는 설명입니다.


🔡 Source 1A~1D. Current Request → Pending Context → Session Priority → Bypass Fallback

Request / Resolver 흐름은 Canonical Request → Pending Context → Session Priority → Bypass Fallback의 네 단계로 나눠 읽습니다.

현재 공개 Evidence에는 Request / Resolver 전용 Watch Screenshot이 없으므로, 이 네 장은 정적 Source Evidence로 설명합니다. 실제 Runtime 결과는 Video 1과 Runtime State Screenshot이 담당합니다.

전체 관계부터 보면:

Text
Current Canonical Request
→ Pending Transition Context
→ Resolver Policy
→ Session 우선
→ Session miss 시 Mode별 Fallback
→ Resolved Spawn
DX11-B04_Screenshot_02_Source_Request_Resolver0_CanonicalRequest.png

Source 1A — Canonical Request

파일: DX11-B04_Screenshot_02_Source_Request_Resolver0_CanonicalRequest.png

현재 Player-facing Bypass Request가 Explicit Entry를 강제로 지정하지 않고 기존 Transition Manager로 책임을 넘기는 경계를 보여줍니다.

Text
RequestCanonicalBypassTravel
→ RequestTransition
→ EntryPointTag = EMPTY

이 장의 핵심은 문자열 자체가 아니라 Caller가 Resolver에게 어떤 의미를 넘기는가입니다.

DX11-B04_Screenshot_02_Source_Request_Resolver1_PendingContext.png

Source 1B — Pending Context

파일: DX11-B04_Screenshot_02_Source_Request_Resolver1_PendingContext.png

Transition Request가 Resolver에서 사용할 Pending Context로 이어지는 경계를 보여줍니다.

Text
Request
→ Pending Transition State
→ Target / Entry / Mode Context
→ ResolvePlayerSpawnForPending

즉 최종 Spawn 결과만 보는 것이 아니라 Resolver가 어떤 Input Context를 받았는지 먼저 확인합니다.

DX11-B04_Screenshot_02_Source_Request_Resolver2A_SessionPriority.png

Source 1C — Session Priority

파일: DX11-B04_Screenshot_02_Source_Request_Resolver2A_SessionPriority.png

Resolver가 Valid Session State를 사용할 수 있는 경우 Session Spawn을 우선할 수 있는 Policy Branch를 보여줍니다.

Text
Session valid
→ Session Spawn Source 우선

이 Source는 Session이 항상 존재한다는 뜻이 아니라, Session validity가 Spawn Source 선택의 한 조건이라는 점을 보여줍니다.

DX11-B04_Screenshot_02_Source_Request_Resolver2B_BypassFallback.png

Source 1D — Bypass Fallback

파일: DX11-B04_Screenshot_02_Source_Request_Resolver2B_BypassFallback.png

Session을 사용할 수 없을 때 Bypass Path가 다음 Spawn Source를 결정하는 Fallback 경계를 보여줍니다.

Text
Session miss
→ Bypass Policy
→ Explicit Entry / Authored Checkpoint / Default Entry 계열의 다음 후보
→ Resolved Spawn

세부 Fallback 순서는 Current Source의 Mode / 조건을 기준으로 읽고, 모든 Transition이 동일한 한 줄 Policy를 가진다고 단순화하지 않습니다.

Historical Reconstruction과 Current Source는 계속 분리합니다

과거 L"Default" 경로는 이 네 Screenshot의 Current Evidence와 섞지 않습니다.

Text
Historical Reconstruction
→ 본문 코드 블록
 
Current Correct Path
→ Source 1A~1D

따라서 “과거 버그 Commit을 직접 캡처했다”고 주장하지 않습니다.


Session State의 Owner는 CCheckpointManager입니다

Map별 Session Player / Camera State는 현재 Source 기준 CCheckpointManager가 보관합니다.

대표 Storage는:

  • m_SessionCheckpoints[EMapId]
  • m_SessionCameras[EMapId]

입니다.

역할을 나누면 다음과 같습니다.

역할Owner / 경로
Session State Owner / StorageCCheckpointManager
WriterCaptureLeavingMapState chain
ReaderResolvePlayerSpawnForPending / Camera Restore

예전 자료에서 CEditor_Manager를 authoritative Session Owner처럼 설명한 내용은 현재 Source 기준으로 사용하지 않습니다.


🔎 Deep Dive Source 1A~1B. Session Checkpoint Owner — Write / Read

Session State의 Owner를 더 깊게 확인할 때 사용하는 Source입니다.

DX11-B04_Backup_Source_SessionCheckpoint_Owner0_Write.png

Deep Dive Source 1A — Session Write

파일: DX11-B04_Backup_Source_SessionCheckpoint_Owner0_Write.png

Map을 떠날 때 Capture된 Player State가 CCheckpointManager의 Session Checkpoint 쪽으로 기록되는 Writer 경계를 보여주는 Deep Dive Source입니다.

Text
Leaving Map State
→ Session Checkpoint Write
→ CCheckpointManager Storage
DX11-B04_Backup_Source_SessionCheckpoint_Owner1_Read.png

Deep Dive Source 1B — Session Read

파일: DX11-B04_Backup_Source_SessionCheckpoint_Owner1_Read.png

다음 Transition에서 Resolver가 Session State를 읽을 수 있는 Reader 경계를 보여주는 Deep Dive Source입니다.

Text
CCheckpointManager Session State
→ Resolver / Restore Reader

이 두 자료는 핵심 Flow를 반복하기 위한 것이 아니라 다음 질문에 답하는 Deep Dive입니다.

“Session Player State를 실제로 누가 저장하고, 누가 다시 읽나요?”

B03의 Disk Persistence와 혼동하지 않도록 아래 Lifetime 구분을 그대로 유지합니다.


Session Checkpoint는 SaveGame이 아닙니다

현재 Session State는 process-memory state입니다.

즉 실행 중 Map을 오갈 때 Player / Camera State를 기억하기 위한 Runtime State이지, 프로그램을 종료한 뒤까지 남는 Disk SaveGame이라고 설명하지 않습니다.

B03의 ObjectsSaved.txt, UiLayout.txt 같은 Disk Persistence와도 역할이 다릅니다.

구분B03B04
LifetimeFile에 저장하고 다시 Load실행 중 Map 이동을 위한 Session State
성격Disk PersistenceProcess-memory Restore Context

같은 “복원”이라는 표현을 쓰더라도 Lifetime이 다릅니다.


Player Session은 Position만 기억하는 것이 아닙니다

대표적으로 Player Session에는 다음 정보가 함께 들어갑니다.

  • MapId
  • Position
  • Yaw
  • Navigation Profile
  • Navigation Cell

즉 Player Transform뿐 아니라 Navigation Context도 함께 본다는 점이 중요합니다.

Player 위치가 얼추 맞더라도 Navigation Context가 목적지와 어긋나면 정상 Restore라고 보기 어렵습니다.


Cross-map과 Same-map Relocate는 Contract가 다릅니다

Cross-map Transition에서는 Leaving State를 기록하고, 다음에 돌아왔을 때 Session Restore에 사용할 수 있습니다.

Text
Map Leave
→ CaptureLeavingMapState
→ SetSessionCheckpointForMap
→ Camera CaptureMapSessionPose
→ SetSessionCameraForMap

반면 Same-map Relocate는 다른 경로입니다.

Text
RelocateWithinMap
→ Leaving-state Capture 없음
→ Session Restore 사용하지 않음

따라서 Player 위치가 바뀐다고 항상 Session Checkpoint를 저장한다고 설명하지 않습니다.

이 차이는 B05에서 같은 Map을 다시 선택했을 때 불필요한 Travel을 하지 않는 Negative Flow와도 이어집니다.


Player는 Map마다 다시 Clone하지 않습니다

SpongeBob은 Map Transition 때마다 새로 만드는 Object가 아닙니다.

기존 Persistent Player Instance를 유지한 채 목적지 Spawn Transform으로 Relocate합니다.

Text
Existing Persistent SpongeBob
→ Resolved Spawn Transform
→ Hard Relocate
→ Transient State Cleanup
→ Camera Restore
→ Gameplay Resume

핵심은 Player Identity를 유지하면서 Destination Context를 바꾸는 것입니다.

Map-specific Object는 Load 과정에서 다시 구성될 수 있지만 Persistent Player의 Lifetime은 별도로 유지됩니다.


DX11-B04_Screenshot_03_Debug_PersistentPlayer_Relocate.png

📸 Screenshot 2. Persistent Player Relocate Runtime State

파일: DX11-B04_Screenshot_03_Debug_PersistentPlayer_Relocate.png

이 Screenshot은 Persistent Player의 Relocate 결과와 목적지 Runtime State를 확인하기 위한 Evidence입니다.

화면에서는 너무 많은 Debug 값을 늘어놓기보다 다음 두 질문에 집중합니다.

Text
Persistent Player가 목적지 Context로 이어지는가?
+
Destination 기준 Transform / Navigation State가 적용되는가?

Player Identity 유지 자체는 Screenshot 한 장의 해석에만 맡기지 않고, 아래 Relocate Source와 Persistent Player 구조 설명을 함께 봅니다.

이 Runtime Screenshot과 아래 Source Evidence는 역할을 나눕니다.

Text
Runtime Debug Screenshot
→ 실제 실행 상태
 
Source 2A~2C
→ Relocate 이후 어떤 책임이 순서대로 이어지는가
DX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate0_ApplyPosition.png

🔡 Source 2A — Destination Position Apply

파일: DX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate0_ApplyPosition.png

Resolved Spawn 결과를 Persistent Player에 적용하는 Relocate 경계를 보여줍니다.

Text
Existing Persistent Player
→ Resolved Destination Transform
→ Position / Relocate Apply
DX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate1A_ResetTransient.png

🔡 Source 2B — Transient State Reset

파일: DX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate1A_ResetTransient.png

Persistent Object를 유지하기 때문에 이전 Map에서 남을 수 있는 일시적인 Gameplay State를 정리하는 경계를 보여줍니다.

Text
Hard Relocate
→ ResetTransientAfterHardRelocate
→ Movement / Jump / Control / Presentation 계열 transient state 정리

이 단계의 핵심은 “Animation을 강제로 Idle로 바꾼다”가 아니라:

Persistent Lifetime을 유지하는 대신 이전 Context의 transient state를 명시적으로 정리한다

는 것입니다.

DX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate1B_NavigationReacquire.png

🔡 Source 2C — Navigation Reacquire

파일: DX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate1B_NavigationReacquire.png

목적지 Transform 적용 이후 Player가 목적지 Navigation Context를 다시 획득하는 경계를 보여줍니다.

Text
Destination Relocate
→ Navigation Context Reacquire
→ 목적지 Gameplay 조건으로 복귀

세 장을 연결하면 다음 흐름이 됩니다.

Text
Persistent Player 유지
→ Destination Position Apply
→ Transient State Reset
→ Navigation Reacquire
→ 이후 Camera Restore / Gameplay Resume

Camera Restore는 이 세 Screenshot 안에 억지로 포함하지 않습니다. 현재 글의 별도 Source 설명과 Runtime Regression Video에서 Player-visible 결과를 연결합니다.


Hard Relocate 뒤에는 Transient State를 따로 정리합니다

Persistent Player는 Object 자체를 유지하기 때문에 이전 Map의 일시적인 State가 남을 수 있습니다.

범주로 보면:

  • Movement
  • Jump / Landing
  • Input / Control
  • Presentation

같은 State입니다.

그래서 Hard Relocate 이후 ResetTransientAfterHardRelocate 단계가 존재합니다.

이걸 “Animation을 Idle로 강제로 바꿔 해결했다”고 설명하지 않습니다.

중요한 것은 Persistent Lifetime을 유지하는 대신 이전 Map의 transient gameplay state를 정리하는 단계가 필요했다는 점입니다.

Animation은 그 뒤 정상 Gameplay Update가 다시 이어지면서 자연스럽게 Resume돼야 합니다.


Camera Restore도 Player Restore와 별도 단계입니다

Player가 목적지 위치에 도착했다고 Camera까지 자동으로 끝나는 것은 아닙니다.

대표적인 흐름은 다음과 같습니다.

Text
FinalizeDestinationCameraFraming
→ SynchronizePersistentCameraToPlayer
→ ResolveTargetCameraProfile
→ ApplyMapSessionPose

큰 순서로 보면:

Text
Spawn Resolution
→ Player Relocation
→ Camera Restore

입니다.

Camera가 이상했을 때 Camera부터 고치지 않은 이유도 이 때문입니다.

Camera는 앞에서 정해진 Player / Map State를 읽는 downstream 단계였습니다.


Debugger를 많이 붙이기보다 Claim별로 역할을 나눴습니다

B04는 Debug Story라서 Watch 화면을 많이 붙이고 싶어질 수 있습니다.

확인 후보는 많습니다.

  • RequestedTargetMapId
  • PendingEntryPointTag
  • Session Validity
  • ResolvedSpawn.Source
  • ResolvedSpawn.MapId
  • ResolvedCamera.MapId
  • Player Pointer
  • Before / After Transform

하지만 전부 공개하면 글이 Debug Log처럼 보이기 쉽습니다.

그래서 공개 Evidence의 역할을 나눴습니다.

Text
Runtime Video
→ 최종 Restore 결과
 
Runtime State Screenshot
→ Player / Camera / Navigation 상태
 
Request / Resolver Source
→ Policy와 Responsibility
 
Persistent Player Debug + Relocate Source
→ Destination Context 적용 흐름

현재 Request / Resolver Source에는 별도 Watch Screenshot이 없으므로 “이번 실행에서 Watch 값까지 확인한 Evidence”라고 과장하지 않습니다.

더 깊은 질문이 들어오면 Pending Entry / Session Validity / Resolved Spawn Source 같은 값은 Debugger로 추가 확인할 수 있지만, 공개 글의 핵심 Claim은 현재 Evidence만으로 설명합니다.


현재 Source와 최신 Runtime Evidence도 구분합니다

Current Source에서는 다음 구조를 확인할 수 있습니다.

구조상태
Current Request PathSource Confirmed
ResolvePlayerSpawnForPendingSource Confirmed
Mode-specific Spawn PolicySource Confirmed
CCheckpointManager Session StorageSource Confirmed
Same-map Session SkipSource Confirmed
Persistent Player RelocateSource Confirmed
Transient ResetSource Confirmed
Camera RestoreSource Confirmed

Current Source가 존재하는 것과 Runtime Result가 확인되는 것은 같은 증거가 아닙니다.

B04에서는 DX11-B04_Video_01_CanonicalRestore_Regression.mkv를 별도 Runtime Evidence로 두어 Arrival 이후 Move / Jump / Landing까지 Gameplay가 이어지는 결과를 Source Claim과 분리해서 보여줍니다.


B04 Evidence Map — 각 자료가 맡는 질문

B04의 Evidence는 역할 기준으로 네 묶음입니다.

Text
Runtime Restore Result
Request / Resolver Policy
Persistent Player Relocate
Session Owner Deep Dive

실제 파일은 Source를 단계별로 나눠 총 12개입니다.

모든 자료를 같은 비중으로 읽는 대신 Core Evidence와 Deep Dive의 역할을 분리합니다.

Core Runtime / Debug Evidence — 3개

역할실제 파일이 자료가 닫는 질문
Runtime RegressionDX11-B04_Video_01_CanonicalRestore_Regression.mkvDestination Arrival 이후 Move / Jump / Landing까지 Gameplay가 이어지는가?
Runtime State AnchorDX11-B04_Screenshot_01_FirstBadBoundary_Flow.pngPlayer / Camera / Navigation State를 실제 Runtime에서 어떻게 확인했는가?
Persistent Player DebugDX11-B04_Screenshot_03_Debug_PersistentPlayer_Relocate.pngMap Transition에서 Persistent Player의 Runtime State가 어떻게 이어지는가?

Request / Resolver Source — 4개

역할실제 파일이 자료가 닫는 질문
Canonical RequestDX11-B04_Screenshot_02_Source_Request_Resolver0_CanonicalRequest.pngCaller가 Resolver에 어떤 의미의 Request를 넘기는가?
Pending ContextDX11-B04_Screenshot_02_Source_Request_Resolver1_PendingContext.pngResolver가 읽는 Pending Transition Context는 어떻게 구성되는가?
Session PriorityDX11-B04_Screenshot_02_Source_Request_Resolver2A_SessionPriority.pngValid Session은 Spawn Policy에서 어떤 우선순위를 갖는가?
Bypass FallbackDX11-B04_Screenshot_02_Source_Request_Resolver2B_BypassFallback.pngSession miss 이후 Bypass Path는 어디로 Fallback하는가?

Persistent Player Relocate Source — 3개

역할실제 파일이 자료가 닫는 질문
Apply PositionDX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate0_ApplyPosition.png목적지 Spawn Transform을 Persistent Player에 어디서 적용하는가?
Reset TransientDX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate1A_ResetTransient.png이전 Map의 transient gameplay state를 어디서 정리하는가?
Navigation ReacquireDX11-B04_Screenshot_03_Source_PersistentPlayer_Relocate1B_NavigationReacquire.pngRelocate 이후 목적지 Navigation Context를 어떻게 다시 연결하는가?

Session Owner Deep Dive — 2개

역할실제 파일이 자료가 닫는 질문
Session WriteDX11-B04_Backup_Source_SessionCheckpoint_Owner0_Write.pngSession State는 어디에서 저장되는가?
Session ReadDX11-B04_Backup_Source_SessionCheckpoint_Owner1_Read.png저장된 Session State를 Restore 쪽에서 어디서 다시 읽는가?

가장 쉬운 읽기 순서

처음 읽는 면접관에게는 아래 순서가 가장 부담이 적습니다.

Text
① Runtime Video
최종 Restore 결과를 먼저 본다
 
→ ② FirstBoundary Runtime Screenshot
Player / Camera / Navigation 상태를 본다
 
→ ③~⑥ Request / Resolver Source
왜 Spawn Source가 그렇게 결정되는지 본다
 
→ ⑦ Persistent Player Debug
Player Runtime 결과를 본다
 
→ ⑧~⑩ Relocate Source
Position → Transient Reset → Navigation Reacquire를 본다
 
→ ⑪~⑫ Session Owner Deep Dive
Session Lifetime 질문이 있을 때 깊게 본다

즉 모든 Evidence를 보관하고 글에 연결하지만, 모든 Screenshot을 같은 중요도로 연속 배치하지 않습니다.


게임 클라이언트 관점에서 이 Debug Story가 중요한 이유

Map Transition 문제는 화면에 보이는 마지막 증상만 따라가면 Camera / Animation / Navigation을 각각 건드리기 쉽습니다.

B04에서는 State가 결정되는 순서를 기준으로 확인 범위를 줄였습니다.

Text
Map 자체가 틀림
→ Destination / Content
 
Map은 맞지만 Player 위치가 틀림
→ Request / Spawn Resolution
 
Player 위치는 맞지만 이동이 이상함
→ Navigation / Transient State
 
Player는 맞지만 화면 구도가 이상함
→ Camera Restore

즉 중요한 것은 “어떤 Fix를 했는가?”보다 Downstream 증상을 보고 Upstream의 첫 의미 경계까지 되짚어 올라가는 방식입니다.


면접에서 30초로 설명한다면

“문제는 Map 자체는 정상적으로 바뀌는데 Player / Camera / Navigation State가 목적지 기준으로 자연스럽게 이어지지 않는 것이었습니다. Camera를 바로 수정하지 않고 MapCard, Destination, Map Content까지 정상 범위를 닫은 뒤 Spawn Resolution의 Request Semantic과 Resolver Policy를 다시 봤습니다. 현재 Player-facing 경로는 Explicit Entry를 강제로 지정하지 않고 기존 Resolver에 맡기고, Resolver가 Session과 Mode별 Fallback을 기준으로 Spawn Source를 결정합니다. 이후 기존 Persistent Player를 목적지로 Relocate하고 transient state를 정리한 뒤 Navigation과 Camera를 다시 연결해 Gameplay를 Resume합니다.”

이 답변의 핵심은:

Text
마지막 증상 수정
X
 
Last Good
→ First Boundary to Recheck
→ State Semantic
→ Existing Restore Flow
→ Runtime Result
O

입니다.


정리

이 문제를 가장 짧게 줄이면 다음과 같습니다.

Text
Map은 바뀌었다.
 
하지만
Player / Camera / Navigation State는
목적지 기준으로 완전히 복원되지 않았다.
 
→ MapCard 정상
→ Destination 정상
→ Map Content 정상
 
→ Spawn Resolution에서
   의미를 다시 확인
 
→ EntryPointTag Semantic
→ Transition Mode별 Spawn Policy
 
→ Existing Canonical Restore 재사용
→ Persistent Player Relocate
→ Transient State Cleanup
→ Camera / Navigation Restore
→ Gameplay Resume

제가 이 문제에서 가져간 핵심은 특정 문자열 하나를 바꿨다는 것이 아닙니다.

눈에 보이는 마지막 증상부터 고치는 대신, 정상인 단계를 하나씩 닫아가면서 State의 의미를 다시 확인해야 할 경계를 찾고, Source와 Runtime State Evidence를 분리해 Player / Camera / Navigation 흐름을 다시 연결한 경험입니다.

다음 B05에서는 이 Canonical Map Transition을 새로 만드는 것이 아니라, BusStop → Map Selection → Bus Presentation → Canonical Handoff로 연결해서 실제 Player가 경험하는 Travel Flow로 마무리한 과정을 정리합니다.

빠른 검색

프로젝트, Evidence, 디버깅 노트, 학습 기록을 검색합니다.

목차