Worlds Service
상태: 1차 MVP 구현 완료
기준일: 2026-08-09
1. 목적과 도메인 경계
Worlds는 Cinema와 독립적으로 고정 환경을 생성하고 보관하는 서비스다. 생성된 월드는 여러 Cinema Project와 Scene에서 재사용할 수 있다.
- World Model은 벽, 바닥, 천장, 통로, 고정 구조물과 환경 외형을 생성한다.
- 인물, 이동 가능한 소품, 카메라 배치는 생성된 월드와 분리된 Composition 계층이 관리한다.
- Cinema는 mutable World가 아니라 불변
WorldRevision을 참조한다. - Provider와 모델 선택은 서버 설정이며 사용자 UI에 노출하지 않는다.
2. 입력 계약
| 입력 종류 | 필수 입력 | 의미 |
|---|---|---|
TEXT | textPrompt | 자연어 기반 생성형 환경 |
IMAGE | 이미지 1개 | 단일 시점 기반 환경 |
MULTI_IMAGE | 동일 장면 이미지와 선택적 azimuthDegrees | 동일 촬영 위치에서 방향을 달리한 입력을 기본 계약으로 사용 |
VIDEO | 비디오 1개 | 이동하는 시점이 포함된 환경 입력 |
소스 이미지를 서로 다른 카메라 위치에서 촬영했음을 나타내는 위치 좌표는 현재 Marble 공개 API에 없다. 따라서 MULTI_IMAGE는 위치 이동 기반 정밀 재구성을 보장하지 않는다.
원본 미디어는 기존 Presigned Upload API로 SurfAI S3에 먼저 저장한다. 생성 요청은 외부 URL 대신 안정적인 s3Path, 파일명, MIME, 이미지 순서와 선택적인 방위각·파노라마 여부를 기록한다.
3. Provider 추상화
첫 Adapter는 World Labs Marble이다. 기준 요청은 다음과 같다.
POST https://api.worldlabs.ai/marble/v1/worlds:generate
Content-Type: application/json
WLT-Api-Key: <server-secret>
{
"display_name": "Mystical Forest",
"model": "marble-1.1",
"world_prompt": {
"type": "text",
"text_prompt": "A mystical forest with glowing mushrooms"
}
}
Provider Adapter의 공통 책임은 다음과 같다.
WorldModelProvider
├── submit(input) -> providerOperationId
├── getOperation(providerOperationId)
├── getWorld(providerWorldId)
└── parseCompletedOperation(operation)
Provider의 원본 응답은 진단과 재현을 위해 providerSnapshot에 저장하되, 공개 API 응답은 Provider 독립 계약으로 변환한다.
4. 데이터 모델
World
사용자가 관리하는 논리적 컨테이너다.
World
├── id / ownerId
├── name / description
├── currentRevisionId?
├── archivedAt?
└── createdAt / updatedAt
WorldGenerationAttempt
외부 비동기 실행과 입력 스냅샷을 보존한다.
WorldGenerationAttempt
├── id / worldId
├── version
├── status
├── providerKey / providerModel
├── providerOperationId? / providerWorldId?
├── inputSnapshot
├── errorCode? / errorMessage?
├── submittedAt? / completedAt?
└── createdBy / createdAt / updatedAt
상태 흐름은 다음과 같다.
QUEUED -> SUBMITTED -> PROCESSING -> INGESTING -> READY
\-> PARTIAL
\------------------------------------> FAILED
WorldRevision
하나의 완료된 환경 월드다. 생성 후 수정하지 않는다.
WorldRevision
├── id / worldId / version
├── sourceAttemptId
├── status: READY | PARTIAL | ARCHIVED
├── providerWorldId
├── spatialManifest
├── providerSnapshot
├── contentHash
└── createdAt
WorldArtifact
| 종류 | 기본 활용 |
|---|---|
THUMBNAIL | 목록 미리보기 |
PANO | 월드 상세 미리보기 |
SPZ_100K | 저사양 Preview |
SPZ_500K | 기본 3D Viewer |
SPZ_FULL | 고품질 확인 |
COLLIDER_MESH_GLB | 충돌과 배치 계산 |
HQ_MESH_GLB | 선택적 외부 정밀 작업 |
FULL_RES_MESH_GLB | 선택적 전체 해상도 Mesh |
SPLAT_PLY | 선택적 분석·외부 편집 |
모든 Artifact는 providerUrl과 별도로 SurfAI s3Path, MIME, byte size, checksum, ingestion status를 가진다. 외부 URL은 장기 표시 URL로 사용하지 않는다.
spatialManifest는 최소한 다음 값을 가진다.
{
"coordinateConvention": "MARBLE_RAW_OPENCV",
"metricScaleFactor": 1.0,
"groundPlaneOffset": 0.0,
"viewerTransform": {
"rotationXDegrees": 180
}
}
5. 비동기 처리와 수집
브라우저는 World Labs API를 직접 호출하지 않는다. Backend Worker가 Provider Operation을 지수 backoff로 확인한다. 완료 후 외부 Artifact를 스트리밍 방식으로 SurfAI 영구 버킷에 복사한다.
- DB lease를 사용해 다중 Backend 인스턴스의 중복 처리를 방지한다.
- Provider Operation 완료와 SurfAI World 준비 완료를 구분한다.
- 필수 Artifact 수집이 끝나야
READY다. - 일부 Artifact만 수집되면
PARTIAL로 표시해 누락 결과를 사용자에게 숨기지 않는다. - 외부 CDN 응답은 임의 크기 청크를 그대로 S3에 전달하지 않고 AWS multipart uploader로 결합해 저장한다.
- Artifact 수집 실패는 같은 Provider World ID의 최신 결과 URL을 다시 조회한 뒤 실패 파일만 재수집한다. 이 동작은 World 생성 요청을 새로 만들지 않는다.
- 완료·실패 이벤트는 기존 사용자 WebSocket 채널을 통해 전달한다.
POST /v1/worlds/:worldId/artifacts/retry는 최근 FAILED 또는 PARTIAL 시도의 실패 Artifact를 PENDING으로 되돌린다. Worker는 저장된 providerWorldId로 World Labs 결과를 다시 조회하고, 이미 READY인 Artifact는 건너뛴다.
6. 화면 구조
| Route | 책임 |
|---|---|
/worlds | 내 월드 목록, 상태, Pano/Thumbnail |
/worlds/new | 입력 방식 선택, 원본 업로드, 생성 요청 |
/worlds/:worldId | Revision 이력, Pano, SPZ Viewer, Artifact 상태 |
/worlds/:worldId/compose | 향후 인물·오브젝트·카메라 배치 |
SPZ Viewer는 Three.js와 Spark를 사용한다. semantics_metadata.metric_scale_factor를 위치와 Gaussian 크기에 적용하고, center의 Y 좌표에서 ground_plane_offset을 뺀다. Three.js View에서는 Marble 안내에 따라 X축 180도 변환을 적용한다.
7. Cinema 연결
1차 Worlds 구현은 Cinema Entity에 직접 의존하지 않는다. 후속 단계에서 별도 Binding을 추가한다.
CinemaSceneWorldBinding
├── projectId / sceneId
├── worldRevisionId
├── worldTransform
├── status
└── createdBy / createdAt
시작·종료 Frame 생성 시에는 다음 불변 Snapshot을 사용한다.
WorldRevision
+ WorldCompositionRevision
+ CameraRevision
= FrameRecipe
8. 1차 구현 범위
- 독립 Backend
worlds모듈과 Migration - World 생성·목록·상세·보관 API
- Marble text/image/multi-image/video Adapter
- Operation 추적과 Artifact S3 수집
- 독립 Sidebar 메뉴와
/worlds화면 - Pano Preview와 SPZ 3D Viewer
Composition, 캐릭터 배치, Cinema Binding, PLY/HQ Mesh 주문형 Export는 후속 범위다.
9. 실행 설정
World Labs 인증 정보와 모델은 Backend 환경변수로만 관리한다. Frontend와 사용자 요청에는 API Key 또는 모델 선택지를 노출하지 않는다.
WORLDLABS_API_KEY=<required>
WORLDLABS_BASE_URL=https://api.worldlabs.ai
WORLDLABS_MODEL=marble-1.1
WORLDLABS_TIMEOUT_MS=60000
WORLDLABS_API_KEY를 제외한 값에는 위 기본값이 적용된다. Key를 변경한 뒤에는 Backend 프로세스를 재시작해야 한다.