diff --git a/README.md b/README.md
index 5a3dcbc..2067027 100644
--- a/README.md
+++ b/README.md
@@ -113,8 +113,8 @@ powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\serve.
### 3. Acoustic Camera에서 실행하기
1. 홈으로 돌아가 **Acoustic Camera**를 엽니다.
-2. 권장 순서인 **Scene → Array → Experiment → Assets**로 입력합니다. Experiment를 먼저
- 열어도 모든 분석 대역이 유지됩니다.
+2. 권장 순서인 **Scene → Array → Experiment → Assets**로 입력합니다. Experiment는 어느
+ 시점에 열어도 모든 분석 대역을 유지하고 현재 Scene·Array에 자동으로 연결됩니다.
3. **Scene**에서 Builder가 저장한 `.scene.json`을 선택합니다.
4. 기본 배열을 사용하지 않을 때는 **Array**에서 Array Builder가 저장한 `.array.json`을 선택합니다.
5. 별도 실험 문서를 사용할 때만 **Experiment**에서 `.experiment.json`을 선택합니다.
@@ -123,10 +123,10 @@ powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass -File .\scripts\serve.
7. 입력 검증이 통과하면 위치 추정 알고리즘, backend와 scan grid를 정하고 실행합니다.
8. 완료 후 **Result JSON**을 저장합니다.
-Experiment를 먼저 불러오면 문서와 모든 `frequencyBands`를 즉시 편집할 수 있습니다. 현재
-Scene·Array와 `sceneId`·`arrayId`가 다른 동안에는 검증 카드에 연결 대기를 표시하고 실행만
-비활성화합니다. 참조하는 Scene과 Array를 이어서 불러오면 실행할 수 있습니다. Scene을 다시
-불러오면 이전 asset 연결은 해제됩니다.
+Experiment를 불러오면 Camera가 먼저 문서 자체의 계약을 검사한 뒤 `sceneId`와 `arrayId`를
+현재 열린 Scene·Array에 자동으로 연결합니다. 가져온 `frequencyBands`와 나머지 분석 설정은
+보존됩니다. 재연결한 문서는 현재 입력을 기준으로 관계 검증을 다시 수행하므로 Nyquist·scan 등
+실제 오류는 그대로 거부됩니다. Scene을 다시 불러오면 이전 asset 연결은 해제됩니다.
Camera 안의 **Builder 새 탭** 링크는 편집 중인 Experiment와 분석 대역을 원래 Camera 탭에
유지합니다. Builder에서 JSON을 저장한 뒤 새 Camera를 다시 열지 말고 원래 Camera 탭으로 돌아와
diff --git a/apps/acoustic-camera/README.md b/apps/acoustic-camera/README.md
index b2cc67a..f95b866 100644
--- a/apps/acoustic-camera/README.md
+++ b/apps/acoustic-camera/README.md
@@ -27,7 +27,7 @@ DAS·DAS-PHAT·GCC-PHAT 위치추정으로 주파수 대역별 acoustic map과 g
## 사용자 입력 불러오기
-문서는 다음 순서로 불러오는 것이 안전합니다.
+문서는 다음 순서로 불러오면 asset 연결까지 한 번에 확인하기 쉽습니다.
1. **Scene**: `.scene.json`
2. **Array**: 선택 사항인 `.array.json`; 또는 **비교 배열** preset 사용
@@ -35,8 +35,11 @@ DAS·DAS-PHAT·GCC-PHAT 위치추정으로 주파수 대역별 acoustic map과 g
4. **Assets**: Scene이 참조하는 원본 이미지와 오디오
Scene을 불러오면 현재 Experiment의 `sceneId`와 scan plane을 Scene에 맞추고 기존 asset 연결을
-해제합니다. Array를 불러오면 현재 Experiment의 `arrayId`를 맞춥니다. 별도 Experiment는 현재
-Scene과 Array를 참조해야 하므로 마지막에 불러오세요.
+해제합니다. Array를 불러오면 현재 Experiment의 `arrayId`를 맞춥니다. 별도 Experiment는 어느
+시점에 불러와도 문서 자체를 먼저 검증한 뒤 `sceneId`와 `arrayId`를 현재 열린 Scene·Array에
+자동으로 연결합니다. 이후 현재 입력을 기준으로 관계 검증을 다시 수행하므로 원래 참조 ID가 달라도
+연결 대기로 실행이 막히지 않으며, 모든 분석 대역은 보존됩니다. Nyquist·scan 같은 실제 관계 오류는
+그대로 거부됩니다.
Scene JSON에는 원본 파일 byte가 없습니다. **Assets**에서 Builder에 사용한 파일을 다시 선택하면
Camera가 SHA-256을 계산해 manifest에 연결합니다. 같은 이름이라도 내용이 바뀌면 거절됩니다.
diff --git a/apps/acoustic-camera/index.html b/apps/acoustic-camera/index.html
index dcac2eb..d633748 100644
--- a/apps/acoustic-camera/index.html
+++ b/apps/acoustic-camera/index.html
@@ -40,7 +40,7 @@
-
+
diff --git a/apps/acoustic-camera/main.js b/apps/acoustic-camera/main.js
index 24c20fb..9ae2b44 100644
--- a/apps/acoustic-camera/main.js
+++ b/apps/acoustic-camera/main.js
@@ -1568,16 +1568,30 @@ async function importDocument(kind, input) {
try {
const value = await readJsonFile(file);
if (generation !== state.importGenerations[kind]) return;
+ let importedExperimentReferences = null;
let issues;
if (kind === "array") {
issues = await validateArrayDefinition(value);
} else if (kind === "scene") {
issues = await validateScene(value);
} else {
- // An Experiment is a useful editable document even before its referenced
- // Scene or Array has been loaded. Validate its own contract here, then let
- // validateCurrent() keep Run disabled until the document IDs line up.
+ // Validate the imported document independently before changing any
+ // references. Then bind it to the open workspace and run the relationship
+ // checks as well, so import convenience does not weaken the contract.
issues = await validateExperiment(value);
+ if (generation !== state.importGenerations[kind]) return;
+ if (issues.length === 0) {
+ importedExperimentReferences = {
+ sceneId: value.sceneId,
+ arrayId: value.arrayId,
+ };
+ value.sceneId = state.scene.id;
+ value.arrayId = state.arrayDefinition.id;
+ issues = await validateExperiment(value, {
+ arrayDefinition: state.arrayDefinition,
+ scene: state.scene,
+ });
+ }
}
if (generation !== state.importGenerations[kind]) return;
if (issues.length > 0) throw new Error(issues.slice(0, 4).map(formatIssue).join(" · "));
@@ -1606,14 +1620,14 @@ async function importDocument(kind, input) {
: kind === "experiment"
? ` · 분석 대역 ${state.experiment.frequencyBands.length}개`
: "";
- const pendingReferences = kind === "experiment"
- ? [
- value.sceneId !== state.scene?.id ? `Scene ${value.sceneId}` : null,
- value.arrayId !== state.arrayDefinition?.id ? `Array ${value.arrayId}` : null,
- ].filter(Boolean)
- : [];
- const referenceStatus = pendingReferences.length > 0
- ? ` · ${pendingReferences.join(" · ")} 연결 대기`
+ const referencesChanged = kind === "experiment" && (
+ importedExperimentReferences?.sceneId !== state.scene.id ||
+ importedExperimentReferences?.arrayId !== state.arrayDefinition.id
+ );
+ const referenceStatus = kind === "experiment"
+ ? referencesChanged
+ ? ` · 현재 Scene ${state.scene.id} · Array ${state.arrayDefinition.id}로 재연결`
+ : " · 현재 Scene · Array 연결 확인"
: "";
setRunStatus(`${file.name} ${kind} 문서를 불러왔습니다.${bandStatus}${referenceStatus}`);
} catch (error) {
diff --git a/docs/data-contracts.md b/docs/data-contracts.md
index 80d55bd..88c0501 100644
--- a/docs/data-contracts.md
+++ b/docs/data-contracts.md
@@ -189,6 +189,11 @@ SHA-256을 계산해 scene의 해당 asset `sha256` 필드를 채운 상태로 c
현재 계약 버전은 `1.0.0`이다. 브라우저는 `packages/contracts/schema-validator.js`로 저장소의
실제 schema를 실행한 뒤 `validation.js`에서 다음 관계를 추가 검사한다.
+Acoustic Camera에서 Experiment를 가져올 때는 문서 자체를 먼저 검증한 뒤 `sceneId`와
+`arrayId`를 현재 열린 Scene·Array의 id로 연결하고, 그 context에서 관계 검증을 다시 수행한다.
+이 가져오기 편의 동작은 계약을 완화하지 않는다. 내보내기와 실행에 쓰는 Experiment도 아래 관계를
+모두 만족해야 한다.
+
- 중복 id와 array/scene/experiment/result 참조
- asset 종류와 안전한 상대 경로
- Nyquist 및 시간·scan 범위
diff --git a/docs/index.html b/docs/index.html
index f97b8e6..49b1124 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -109,10 +109,10 @@
Scene 저장 후 Camera 열기를 누릅니다.
Camera에서 파일을 Scene → Array → Experiment → Assets 순서로
- 가져오는 것을 권장합니다. Experiment를 먼저 열어도 분석 대역은 보존되며, 참조
- Scene·Array가 연결될 때까지 실행만 비활성화됩니다. Array Builder에서 저장한 파일이
- 있다면 Array 단계에서 선택하고, 내 Array/Experiment가 없다면 내장 기본값을 그대로
- 둡니다.
+ 가져오는 것을 권장합니다. Experiment는 어느 시점에 열어도 분석 대역을 보존하고
+ sceneId와 arrayId를 현재 열린 입력에 자동으로 연결합니다.
+ 재연결 뒤 관계 검증도 다시 수행됩니다. Array Builder에서 저장한 파일이 있다면 Array
+ 단계에서 선택하고, 내 Array/Experiment가 없다면 내장 기본값을 그대로 둡니다.
Assets에서 Builder에 사용한 원본 이미지와 오디오를 한 번에 다시 선택합니다.
@@ -268,8 +268,8 @@
start.cmd -Port 4174처럼 다른 포트를 지정하고 http://127.0.0.1:4174/을 여세요.
- Experiment를 가져온 뒤 실행할 수 없습니다.
-
Experiment부터 가져와도 frequencyBands는 모두 표시됩니다. 검증 카드에 연결 대기가 보이면 참조하는 Scene과 Array를 이어서 가져와 sceneId와 arrayId를 맞추세요.
+ Experiment를 가져왔는데 검증 오류가 남습니다.
+
참조 ID는 현재 Scene·Array에 자동으로 연결됩니다. 실행할 수 없다면 검증 카드에서 Nyquist, scan 범위, asset처럼 재연결 뒤 확인된 실제 입력 오류를 확인하세요.