구조
패키지
| 패키지 | 하는 일 | 기대는 것 |
|---|---|---|
@isora/core | 씬 모델·검증, 투영·깊이 정렬, 배치·쌓기, 앉기, 길 찾기, 행동 계획, 움직임, 그릴 파츠 계산 | 없음 (순수 TS, DOM 없음) |
@isora/react | <IsoScene>, 그리기 레이어, 재생 훅(시계·행동·타임라인·씬 변화) | core, React 18·19 |
@isora/assets | 공식 에셋 팩 10종 (공용·오피스·공장·도시·의료·군·학교·리테일·공항·사람, CC0) | core |
@isora/pack | 팩 매니페스트·검증·빌드, SVG 정제, 정적 SVG 내보내기 | core |
@isora/cli | isora init·studio(로컬 서버)·pack·export | core, pack |
apps/studio | GUI 편집기 (MUI) | react, assets |
example/* | 프레임워크를 앱처럼 쓰는 예제 (claude-office·nullus-login·showcase: 코드로 지은 대형 씬 열) | react, assets |
core 모듈과 흐름
types · asset(defineAsset) · scene(검증·parse)
│
projection(iso·회전·projectPoint) ── geometry(볼록 껍질·SAT) ── shape(파츠 상자)
│ │
placement(쌓기·바닥 높이·floorPointAt) ── seating(자리·SITTING·앉는 길 검사·describeRig)
│ │
navigation(격자·A*·시간 예약) │
│ │
activities(활동·기본 루틴) ── behavior(계획·명령·frameAt) ── motion(걸음·자세 섞기·transitionClear)
│ │
depth(앞뒤 그래프) ── render(prepareRender·composeRender·buildRenderList)
timeline(경로·키프레임) ──────────────────────────────────────────┘
builder(코드로 씬 쌓기) · lint(배치 검사) · motionLint(움직임 물리 검사) ─ 위 층을 읽기만 하는 도구- 데이터 → 계획 → 모습 → 그림 순서로만 기댄다. 아래 층은 위 층을 모른다.
- 계획(
behavior)은 시각의 함수다. 사람 id·seed 로 난수를 정하고, 결정 시각 순서로만 계획을 늘려 간다. - 모습(
motion)은 계획이 준 한 시각의 값(자리·자세·전환 진행도·걸은 거리)을 파츠 상자로 바꾼다. 상태가 없다. - 그림(
render)은 상자를 화면 면으로 바꾸고 앞뒤를 정한다. 정지한 것과 움직이는 것을 나눠 매 프레임 비용을 줄인다.
react
| 층 | 무엇 |
|---|---|
시계 createTimelineClock | 틱(80ms) 알림 + 프레임 시각 timeAt. 행동·타임라인이 같이 쓸 수 있다 |
| 재생 훅 | useBehaviorPlayback(루틴·명령) · useTimelinePlayback(화물·키프레임) · useObjectMotion(씬 데이터 변화) |
| 매 프레임 source | LiveSource { ids, sample(wall), until? } — 훅들이 내놓고, 앱이 직접 만들 수도 있다 |
| 레이어 | LiveSceneLayer(prepared + sources → 매 프레임) · SceneLayer(파츠 → SVG) · GroundGrid · HullOutline · ActivityBubbles |
| 뷰어 | <IsoScene> — 위를 조립한 것. behavior·clock·renderOverlay·onSceneClick 으로 앱이 끼어든다 |
틱과 프레임
| 틱 (80ms) | 프레임 (rAF) | |
|---|---|---|
| 무엇이 바뀌나 | 활동·단계(people), 기기·문 상태, 하루 시각, 타임라인 화물 | 걷고 앉는 사람의 파츠 |
| 어디서 | clock.subscribe → 훅 → React 리렌더 | LiveSceneLayer 의 저장소 → 그 레이어만 리렌더 |
| 왜 나눴나 | 전체 리렌더·정지 그림 다시 만들기는 비싸다 | 걷는 모습은 틱으로 끊으면 떨린다 |
확장 지점
| 하고 싶은 것 | 쓰는 것 |
|---|---|
| 사람을 앱 이벤트로 움직이기 | useBehaviorPlayback().command / BehaviorWorld.command |
| 계속 바뀌는 데이터를 사람 움직임으로 | useActorCommands |
| 씬을 코드로 짓기 | createSceneBuilder |
| 루틴·시간표 정의 | 씬 routines, clock |
새 활동 (예: charge) | 가구 seats[].activity·spots[].activity 에 이름을 적고 루틴에서 그 이름을 쓴다 |
| 사람이 아닌 것을 매 프레임 움직이기 (지게차·AGV 실시간 위치) | LiveSource 를 만들어 LiveSceneLayer 에 넣는다 |
| 머리 위 표시·핀·경로선 | <IsoScene renderOverlay> 또는 LiveSceneLayer children |
| 클릭으로 위치 고르기 | <IsoScene onSceneClick>, floorPointAt |
| 새 사람 에셋 | 사람 에셋 만들기, describeRig |
| 다른 렌더러 (Canvas·WebGL·서버 이미지) | prepareRender·composeRender 의 면, actorPartBoxes·objectPartBoxes 의 상자 |
| 설비 상태 바인딩 | <IsoScene states> — 에셋 states 이름 |
| 이름·메시지를 다른 언어로 | 영어 글 옆의 names·labels, assetName·stateLabel·localize, 검사의 { locale } (언어) |
점검: 프레임워크로 쓸 만한가
| 영역 | 상태 | 비고 |
|---|---|---|
| 씬·에셋 데이터 모델 | ✅ | 스키마 검증(parseScene·validateScene·defineAsset), 팩 id 규칙, 결정적 JSON |
| 좌표·투영·클릭 좌표 | ✅ | projectPoint·unprojectPoint·floorPointAt, onSceneClick |
| 배치·쌓기·충돌 | ✅ | placeAt·surfaceHeightAt·findCollisions |
| 사람 자동 행동 | ✅ | 루틴·시간표·비켜 가기·출입, 결정적 |
| 사람 명령 | ✅ | go·do·idle·leave·routine, 대신 할 명령(orElse), 서서 하는 일, 둘러서기, 끊기·자리 내놓기·결과 보고, 기록 재적용 |
| 데이터 → 움직임 | ✅ | useActorCommands 로 사람마다 할 일을 선언 (example/claude-office) |
| 코드로 씬 쌓기 | ✅ | createSceneBuilder (add·sit·floor) |
| 위치·활동 읽기 | ✅ | people·overrides·frameAt(t), 매 프레임 renderOverlay |
| 재생 제어 | ✅ | 공유 시계 clock, seek·setSpeed, 동작 줄이기 |
| 움직임 품질 | ✅ | 매 프레임 월드 공간, 앉는 길 충돌 검사 |
| 사람 에셋 확장 | ✅ | 파츠 이름 약속 + describeRig |
| 렌더러 교체 | ✅ | core 가 DOM 없이 면·상자를 준다 |
| 언어 | ✅ | 기본 영어, 한국어 내장. 번역은 영어 글 옆에 둔다 |
| 명령 완료 이벤트 | △ | 콜백 대신 people 을 틱마다 비교한다 (사람 움직이기) |
| 사람이 아닌 이동체의 길 찾기 명령 | △ | 지게차·AGV 는 LiveSource 로 앱이 위치를 댄다. 길 찾기·비켜 가기는 사람만 한다 |
| 층·계단 | ✗ | 한 층만. 층 이동은 아직 없다 |
| 연속 회전 | ✗ | 방향은 90° 단위로 바로 돈다 |
| 리그 이름 바꾸기 | ✗ | 파츠 이름 규칙(leg-l 등)이 고정이다. 다른 이름을 쓰려면 이름을 맞춘다 |
| 긴 건너뛰기 | △ | 1시간 앞으로 seek 하면 계획에 수백 ms 걸린다 |
테스트가 지키는 것
packages/core/src/behavior.test.ts— 계획·비켜 가기·시간표·명령(끊기·자리 내놓기·출입·실패), 결정성, 명령과 루틴이 섞여도 겹치지 않음packages/assets/src/motion.test.ts— 네 방향 책상 의자·소파에서 모든 사람의 앉는 길이 가구를 뚫지 않음, 전환 연속성, 걸음, 깊이 순서, 리그 약속, 문서 예제 에셋apps/studio/src/editor/samples.test.ts— 샘플 씬의 모든 책상·소파·벤치 자리가 앉는 길 검사를 통과함packages/react/src/IsoScene.test.tsx— 앱이 쥔 행동 재생·오버레이example/claude-office/src/office.test.ts— 세션 → 책상 배정, 모습별 명령(코드·커피·Zz)이 자리가 모자라도 겹치지 않고 해내짐