# 데이터 계약 micarrsim의 핵심 Array·Scene·Experiment·Result 형식은 JSON Schema Draft 2020-12와 추가 관계 검증으로 정의한다. Activity와 비교 기록 같은 편의 감사 export는 이 문서에서 별도 범위를 명시한다. 모든 길이는 m, 시간은 s, 주파수는 Hz다. 배열 중심이 원점이고 `+x`는 오른쪽, `+y`는 위쪽, `+z`는 배열에서 장면을 향하는 우수 좌표계를 사용한다. ## 파일 종류 - `micarrsim.array`: aperture와 마이크별 `[x, y, z]` 좌표 - `micarrsim.scene`: 음원·ambient recipe, 위치 ground truth와 로컬 asset manifest - `micarrsim.experiment`: scan plane/grid, 주파수 대역, STFT와 위치 추정 설정 - `micarrsim.result`: raw/display acoustic map, 복수 peak와 source 매칭, 평가·성능 정보 - `micarrsim.activity-report`: 단일 물리 마이크의 raw CFAR 또는 spectral-flux 감사 export. core schema 검증 대상은 아니며 위치추정 Result를 대신하지 않는다. - `micarrsim.comparison-history`: 세션의 배열별 Result 요약을 모은 편의 export. core schema 검증 대상은 아니며 각 원본 Result JSON을 대신하지 않는다. scene JSON에는 192채널 원시 sample이나 원본 asset byte를 넣지 않는다. 합성 tone/noise는 seed가 포함된 recipe로 재생성하고, 녹음 파일과 이미지는 `id`, `kind`, `fileName`, `mimeType`, `sha256` manifest로 참조한다. 사용자 컴퓨터의 절대 경로, URL, `..` 경로는 저장하지 않는다. array의 `sensitivityDb`와 `phaseOffsetDeg`는 각 마이크 수신 channel에 적용되는 편차다. 합성기는 이를 신호에 주입하고, 위치 추정기는 선택한 알고리즘의 규칙에 따라 알려진 calibration을 보정한다. 선택적 `frequencyResponse`는 2–64개의 `{ frequencyHz, gainDb, phaseDeg? }` 측정점이다. 주파수는 0–1,000,000 Hz에서 중복 없이 증가해야 하며, dB와 wrapped phase를 구간 선형 보간하고 측정 범위 밖에서는 가까운 끝점을 사용한다. Array Builder의 사각형/타원 생성 경계, 프리셋 종류, 행·열, random seed·최소 간격·경계 여백, snap과 zoom은 편집기 상태이며 `micarrsim.array` 계약에 포함되지 않는다. 저장 파일에는 `aperture.width/height`와 최종 `microphones` 좌표·순서만 들어간다. 따라서 다른 도구는 형상 이름을 추론하지 않고 실제 좌표를 배열 geometry의 기준으로 사용해야 한다. Experiment의 필수 `beamforming.algorithm` enum은 `das`, `das-phat`, `gcc-phat`, `music`이다. backend와 raw map 계약은 다음과 같다. | algorithm | `rawMap.scale` | 허용 backend | | --- | --- | --- | | `das` | `power` | `cpu`, `webgpu`; `auto` 가능 | | `das-phat` | `score` | `cpu`, `webgpu`; `auto` 가능 | | `gcc-phat` | `score` | `cpu`; `auto`는 CPU 선택 | | `music` | `noise-projection` | `cpu`; `auto`는 CPU 선택 | `gcc-phat` 또는 `music`에 명시적 `webgpu`를 조합하면 검증 오류이며, 마이크가 두 개 이상이어야 한다. 필드 그룹 이름 `beamforming`은 기존 계약과의 호환을 위해 유지하지만 네 알고리즘을 모두 선택하는 위치 추정 설정이다. MUSIC은 `normalization: "none"`만 허용한다. band마다 analog FFT-grid 가설을 정확히 하나 선택하고, 접힌 양의 ADC bin 하나에서 snapshot covariance를 만든다. Fixed K는 `1..M-1`이다. Wax–Kailath `mdl`/`aic`는 complete non-overlapping frame, `T > M`, 엄격히 양수인 noise eigenvalue와 IID complex Gaussian snapshot·spatially white noise·고정 source 수를 가정한다. non-overlap은 독립성의 증명이 아니다. 조건 불충족은 fixed fallback 없이 typed `MUSIC_UNAVAILABLE`이다. 선택적 `beamforming.compensateFrequencyResponse`는 기본 `true`다. 합성 channel에는 curve가 항상 적용된다. DAS는 sensitivity와 정적 phase를 항상 역보정하고, 값이 `true`이면 선택 bin에서 curve의 gain과 phase도 역보정한다. DAS-PHAT와 GCC-PHAT는 입력과 calibration의 amplitude gain을 사용하지 않지만 정적 phase를 보정하며, 값이 `true`이면 알려진 curve phase도 보정한다. `false`는 모든 알고리즘에서 curve 보정만 끄고 정적 phase 보정은 유지한다. 선택적 `beamforming.maxCompensationDb`는 DAS가 응답 notch를 역보정할 때 양의 gain boost만 제한하는 0–120 dB 값이며 생략 시 24 dB다. 응답 peak를 줄이는 attenuation과 phase 보상은 이 boost 상한의 대상이 아니다. amplitude를 사용하지 않는 DAS-PHAT와 GCC-PHAT에는 이 값이 영향을 주지 않는다. MUSIC은 analog 가설에서 residual complex response를 일관되게 manifold에 넣는다. 네 알고리즘의 계산식과 한계는 [음향 환경과 마이크 응답 모델](acoustic-model.md)에 설명한다. ## 로컬 asset 재연결 브라우저 보안상 Scene JSON을 새 페이지에서 열었을 때 이전 로컬 파일 권한은 복원되지 않는다. Camera는 사용자가 다시 선택한 파일을 SHA-256으로 계산해 같은 descriptor에만 연결한다. 이름이 같아도 byte가 다르면 거절하고, 원본은 브라우저 메모리에서만 decode하며 서버에 업로드하지 않는다. - 파일 하나: 최대 128 MiB - 한 Scene에 연결된 원본 파일 합계: 최대 512 MiB - 동시에 유지하는 decode 오디오 channel 합계: 최대 192 MiB Builder가 내보낸 Scene JSON과 사용한 원본 이미지·오디오는 함께 보관해야 한다. Preview WAV는 scene recipe를 mono로 합친 청취 확인용 파일이며 마이크 배열의 192채널 수신 신호가 아니다. ## 이동 음원 trajectory Scene source의 선택적 `trajectory`는 2–4,096개의 `{ time, position }` keyframe 배열이다. `time`은 Scene 시작 기준 절대 초이며 0 이상 `scene.duration` 이하에서 엄격히 증가해야 한다. `position`은 정적 source와 같은 `[x, y, z]` m 좌표다. keyframe 사이 위치는 선형 보간하며, 첫 keyframe은 호환용 `source.position`과 같은 시작 위치로 Builder가 유지한다. Camera 라이브 미리보기는 sample 경계에 맞춘 시간창 중앙에서 trajectory 위치를 평가한다. 짧은 창으로 Scene 시간을 다시 기준화해도 tone 위상, noise sample index와 audio asset 재생 위치는 원래 절대 시간에 이어진다. 이는 준정적 근사이며 Doppler를 계약하지 않는다. ## 음향 환경 Scene의 선택적 `acoustics.model`은 `free-field` 또는 `shoebox`다. 필드를 생략한 문서는 free-field로 해석해 이전 직접음 결과를 유지한다. free-field에는 사용되지 않는 `room`, `reflections`, `diffuseReverb` 또는 `occluders`를 함께 둘 수 없다. shoebox에는 `room.min`과 `room.max`가 필요하다. 각 축에서 min은 max보다 작아야 하며 정적 source와 모든 trajectory keyframe은 room 안에 있어야 한다. `reflections.order`는 0 또는 1이고, `wallGainDb`와 선택적 면별 `perWallDb`는 -60–0 dB, `scattering`은 0–1이다. `diffuseReverb`는 `enabled`, 0.05–5 s의 `rt60Seconds`, -60–0 dB의 `gainDb`와 1–32의 `tapCount`를 갖는다. 축 정렬 `occluders`는 최대 8개이며 room 안의 min/max, 고유 id, -120–0 dB의 `transmissionDb`와 `diffractionGainDb`를 갖는다. 이 계약은 직접음, 여섯 면의 1차 image-source, 결정적 scattering, 막힌 occluder별 최단 corner 우회와 제한된 RT60 tail을 위한 것이다. 재질 mesh나 고차 room impulse response 계약은 아니다. 세부 계산과 preset은 [음향 환경과 마이크 응답 모델](acoustic-model.md)에 설명한다. ## 오디오와 map 정규화 `scene.mix.normalization`은 마이크 신호 합성 전에 적용할 오디오 mix 정책이다. `none`은 원래 gain을 보존하고, `peak`는 합성 전 peak를 `targetPeak`에 맞춘다. result의 `signalStats`에는 정규화 전 peak, 실제 scale, scale 적용 후에도 범위를 넘은 sample 수를 기록한다. 이는 acoustic map 표시 정규화와 별개다. result의 `rawMap.scale`은 알고리즘의 원래 비음수 map을 뜻하는 `power` 또는 `score`다. DAS의 `power`는 절대 SPL로 calibration한 값이 아니며, DAS-PHAT와 GCC-PHAT의 `score`도 확률, likelihood 또는 confidence가 아니다. 합이 1이 되도록 정규화되지 않으며 서로 다른 scale이나 알고리즘의 절대값을 직접 비교해서는 안 된다. GCC-PHAT raw score는 pair 수·band의 Hermitian coefficient 수·IFFT 크기로 나눈 0–1 양의 상관 응답이므로 scan 범위를 바꿔도 같은 위치 가설의 척도가 유지된다. 그래도 확률 calibration은 아니며 DAS-PHAT score와 절대값을 비교하는 용도가 아니다. `displayMap`은 원래 scale, peak-normalized `normalized` 또는 `db`일 수 있다. peak 정규화는 최댓값을 1로 만드는 표시 변환일 뿐 확률 정규화가 아니다. `processedMaps`는 `damas`, `clean-sc` 또는 `custom` 후처리 결과를 별도로 교환하기 위한 예약 슬롯이다. 현재 위치 추정 코어는 DAMAS나 CLEAN-SC를 실행하거나 이 필드를 자동 생성하지 않으며, raw map을 후처리 map으로 덮어쓰지 않는다. map은 row-major 배열이다. 첫 행은 `yMax`, 마지막 행은 `yMin`, 각 행은 `xMin`에서 `xMax` 방향이다. `values.length`는 반드시 `rows × columns`와 같아야 한다. Experiment의 선택적 `evaluation.detectionThresholdRatio`는 최강 local peak의 같은-scale raw 값에 대한 검출 임계 비율이며 `0 < ratio <= 1`이다. 생략된 이전 문서를 Camera에서 열면 기본값 `0.25`를 적용하며, Experiment JSON을 다시 저장하면 이 값이 명시된다. 값을 낮추면 약한 peak 후보를 포함하지만 sidelobe false positive도 늘 수 있다. ## Result 평가 정보 Result 1.1은 최상위 필수 `algorithm`에 실제 실행한 `das`, `das-phat`, `gcc-phat` 또는 `music`을 기록하며 Experiment의 선택값과 같아야 한다. `backend`는 요청값 `auto`가 아니라 실제 사용한 `cpu` 또는 `webgpu`다. 관계 검증은 `das → power`, `das-phat/gcc-phat → score`를 강제한다. Peak에 `beamSpread`가 있으면 power map은 power-weighted method, score map은 score-weighted method여야 한다. 따라서 구조상 유효하지만 의미가 모순된 외부 Result도 import 시 거절된다. `timingMs.localization`은 선택한 위치 추정 알고리즘의 wall time이다. 현재 export는 기존 DAS 전용 소비자를 위해 같은 값을 `timingMs.beamforming`에도 호환 alias로 기록한다. `1.1.0` schema는 `beamforming`을 여전히 필수로 두므로, 새 writer는 `localization`과 `beamforming`을 함께 저장해야 한다. `timingMs.total`은 전체 시간이고 선택적 `signalGeneration`은 합성 시간이다. 각 band의 `peaks`는 위치와 해당 `rawMap.scale`의 값, 선택적 `sourceId`, `localizationErrorM`, `localizationErrorPixels`, `beamSpread`를 저장한다. `beamSpread`는 선택 peak를 포함하는 8-connected 성분의 가중 centroid·공분산과 major/minor RMS 반경·각도다. - `power` map은 `connected-half-power-power-weighted-covariance`를 사용해 peak의 `halfPowerRatio` 이상인 연결 성분을 power로 가중한다. - `score` map은 `connected-half-maximum-score-weighted-covariance`를 사용해 peak의 같은 비율 이상인 연결 성분을 score로 가중한다. 호환 필드명 `halfPowerRatio`는 이 경우 실제로 half-maximum score 비율을 뜻한다. 두 방식 모두 `calibrated`는 현재 항상 `false`다. covariance ellipse는 map의 공간적 퍼짐을 결정적으로 요약할 뿐 확률분포, Gaussian 신뢰구간 또는 통계적 위치 불확실성이 아니다. `metrics`에는 다음 유한값만 기록한다. - `peakToSidelobeDb` - `energyInsideTruthRatio` - `precision`, `recall` - `mainLobeWidthM` 호환 필드명 `energyInsideTruthRatio`는 `power` map에서는 truth 주변 power 비율, `score` map에서는 truth 주변 score 비율이다. 후자의 값은 물리적 energy 비율이나 위치 확률이 아니다. `evaluation`은 지표를 다시 해석할 수 있도록 truth 반경, source 매칭 tolerance, peak 검출 threshold와 최소 거리, sidelobe 제외 반경, half-power 비율, dynamic range, 최대 검출 수를 버전과 함께 저장한다. `environment`에는 실제 backend, microphone/grid 수, scan bounds와 z, 가능한 경우 GPU adapter 및 fallback 정보를 기록한다. `signalStats`는 정규화 전 peak, scale과 clipping 수 외에 필수 `sampling` provenance와 선택적 `acousticModel`, 실제 전체 `acousticPathCount`와 `acousticPathCounts`를 기록한다. 종류별 key는 `direct`, `reflection`, `scattering`, `diffraction`과 `diffuse`다. 각 band의 필수 `frequencyObservation`은 연속 analog fold와 이산 FFT 계산 선택을 구분한다. `foldSegments`는 1-based Nyquist zone·반전·alias order를, `adcBins`/`adcBinCenterRanges`는 `selectionModel: fft-bin-center-membership`으로 선택된 bin center를 뜻한다. 요청 `minHz`와 `maxHz`에 정확히 놓인 FFT bin center도 선택한다. 정확한 Nyquist 경계의 `+Fs/2`와 `-Fs/2`는 같은 실수 sample이며, point provenance는 `+Fs/2`로 canonicalize하고 fold segment endpoint는 zone 진행 방향에 따라 둘 중 하나를 기록한다. `localizationInputModel`은 one-sided real FFT bin만 사용함을 밝히며, `localizationModel`은 DAS 계열의 구면 steering, GCC-PHAT의 기하 lag와 MUSIC의 configured-analog-frequency near-field manifold를 구분한다. DAS/DAS-PHAT/GCC-PHAT의 `originalCarrierUsed`는 `false`이고 Nyquist 위 band의 `aliasOrderResolution`은 `not-attempted`다. MUSIC의 `originalCarrierUsed: true`는 Scene truth가 아니라 사용자가 지정한 analog 가설을 뜻한다. `equivalentBandIds`가 있으면 그 band들은 같은 ADC bin 집합을 공유하므로 독립 관측으로 세면 안 된다. 대역별 ground truth의 실제 정책은 evaluation의 `truthSelection: tone-analog-interval-or-nearest-adc-bin-and-all-nontone-sources`에 기록한다. Tone은 원 주파수가 요청 analog interval 안에 있거나, 접힌 주파수의 nearest ADC bin이 선택됐을 때 포함한다. noise와 asset source는 모든 band에 포함한다. 녹음 asset의 spectrum을 임의 threshold로 분류하는 미검증 multiband 알고리즘은 사용하지 않는다. ## Activity JSON Acoustic Camera의 Activity export는 schemaVersion: "1.0.0", kind: "micarrsim.activity-report"인 편의 감사 문서다. 최상위에는 실제 실행 Array·Scene·Experiment ID, 생성 시각과 64자리 SHA-256 inputHash가 있고, report에는 다음 값이 있다. - 사용자가 선택한 원래 microphone index·id·position과 microphone fusion 없음 - sample rate/count, FFT/hop, ADC 0…Fs/2와 alias order 미결정 상태 - simulator가 Scene truth로 물리 sample을 만들었다는 사실과 detector에는 truth를 전달하지 않았다는 분리 표기 - 선택 마이크 한 채널의 normalization·clipping·음향 경로 집계와 causal 전처리 여부 - CFAR의 모든 complete frame×모든 ADC bin 또는 spectral flux의 raw ODF와 모든 frame×full-DFT-bin 양의 차이 - 알고리즘 설정·가정·보정 상태, workload와 signal-generation/detector timing Source identity와 source별 arrival 목록은 포함하지 않지만 path 수·최대 지연 같은 source-derived 집계 physical audit는 포함하며 이를 명시한다. CFAR의 untested bin과 zero-threshold ratio 상태도 삭제하지 않는다. Typed array는 JSON 숫자 배열로 변환하고 비유한 수가 생기면 문자열 Infinity, -Infinity 또는 NaN으로 명시한다. Activity inputHash는 검출에 실제로 사용한 선택 마이크, Scene의 음향 입력 (배경 image와 비audio asset 제외), STFT, 합성 최소 거리와 detector 설정의 canonical UTF-8 SHA-256이다. 분석 band·scan·위치추정 algorithm/backend는 detector 입력이 아니므로 hash에 넣지 않는다. Array·Scene·Experiment ID는 provenance label로 별도 보존한다. ## 재현성 hash `result.inputHash`는 실행에 사용한 `{ array, scene, experiment }`를 객체 key 사전순으로 재귀 정렬하고 공백 없이 JSON 직렬화한 UTF-8 byte의 SHA-256 소문자 hex다. 사용한 asset은 실행 전에 SHA-256을 계산해 scene의 해당 asset `sha256` 필드를 채운 상태로 canonical input을 만든다. 따라서 파일 이름이 같아도 내용이 바뀌면 hash가 달라진다. ## 버전과 검증 Array·Scene·Experiment 계약 버전은 `1.0.0`, Result 계약 버전은 `1.1.0`이다. Result `1.1.0`은 sampling provenance와 band별 fold/localization metadata를 필수화하므로 이전 `1.0.0` Result와 구조적으로 호환되지 않는다. 브라우저는 `packages/contracts/schema-validator.js`로 저장소의 실제 schema를 실행한 뒤 `validation.js`에서 다음 관계를 추가 검사한다. Acoustic Camera에서 Experiment를 가져올 때는 문서 자체를 먼저 검증한다. `sceneId`와 `arrayId`는 provenance label이므로 현재 열린 Scene·Array ID와 비교해 거부하거나 값을 덮어쓰지 않는다. 실행에는 현재 열린 geometry와 sample data를 사용하고, Result의 `sceneId`/`arrayId`와 `inputHash`에는 실제 사용한 입력을 기록한다. ## MUSIC Result 의미 MUSIC의 `rawMap.scale`은 `noise-projection`이고 `q = ||E_n^H a||²/(a^H a)`의 최솟값이 optimum이다. 표시용 `P/Pmax = q_min/q`도 q와 마찬가지로 확률, likelihood, posterior 또는 신뢰구간이 아니다. `q_min = 0`이면 0인 cell만 1로 표시한다. Result의 `musicModel`과 band별 `musicDiagnostics`는 source-count 방법·가정, covariance/eigensolver 감사, analog/ADC alias와 CPU workload를 보존한다. MUSIC의 `originalCarrierUsed: true`는 Scene truth가 아니라 Experiment에 사용자가 지정한 analog FFT-grid 가설을 썼다는 뜻이다. analog carrier `f_a`와 관측 alias orientation `η`로 `exp[-j η 2π f_a (r_m-r_ref)/c]`를 만들며, inversion이면 calibration과 steering을 포함한 전체 complex manifold를 켤레화한다. Scene source carrier/위치 truth는 읽지 않는다. 정확한 DC/Nyquist bin은 quadrature-degenerate라 거절한다. diagonal loading, epsilon/floor, spatial smoothing, multibin averaging/association과 wideband fusion은 없다. grating lobe, 동률과 coherent-source rank deficiency는 숨기지 않고 map 또는 typed unavailable로 나타낸다. 이 가져오기 편의 동작은 계약을 완화하지 않는다. 내보내기와 실행에 쓰는 Experiment도 아래 관계를 모두 만족해야 한다. - 중복 id와 array/scene/experiment/result 참조 - asset 종류와 안전한 상대 경로 - ideal sampler의 Nyquist alias mapping 및 시간·scan 범위 - 마이크 주파수 응답점의 범위·정렬과 shoebox room/occluder 포함 관계 - map shape와 source peak 매칭 - band별 evaluation 파라미터와 허용 metric - crop 크기 microphone channel과 한 sampled source의 자체 최대 sparse-delay samples·spectrum·workspace를 합친 typed working set의 512 MiB 예산. validator와 runtime은 reflection·scattering·diffraction·diffuse를 포함한 동일 estimator를 사용한다. 지원하지 않는 schemaVersion은 묵시적으로 변환하지 않고 오류로 알린다. Scene tone과 Experiment band는 0–1 MHz 검증 범위 안에서 Nyquist를 넘어도 유효하다. 1 MHz는 Nyquist 제한이 아니라 수치 위상 정밀도·마이크 응답·희소 음향 모델의 현재 검증 상한이다. 이는 원 신호를 복원한다는 뜻이 아니라 anti-alias filter가 없는 이상적 real ADC에서 접힌 bin을 관찰한다는 뜻이다. Result는 `signalStats.sampling`과 band별 `frequencyObservation`에 원 주파수, signed ADC 주파수, spectral inversion, fold segment와 선택 bin center를 기록한다. 동일 ADC bin에 접힌 analog 후보는 단일 channel의 시간 sample만으로 식별할 수 없다. 다채널 공간 위상에는 원 파장 정보가 남을 수 있지만 현재 conventional localizer는 carrier hypothesis를 시도하지 않으며, validation이나 평가도 임의의 후보를 정답으로 선택하지 않는다. 실측 channel을 받는 portable API는 저장 JSON 계약과 별도로 입력 Array를 같은 규칙으로 검사한다. CPU 분석 계획은 기본 512 MiB, 반복 steering cache는 64 MiB, streaming ring과 분석창은 기본 256 MiB hard cap을 할당 전에 적용한다. 채널 순서와 streaming 상태 계약은 [실측 다채널 위치추정 코어 포팅](realtime-porting.md)을 참고한다. portable block 결과의 `processingTimeMs`는 선택한 위치 추정 알고리즘과 local-peak 검출 wall time이다. Streaming 결과는 `deadlineMs`와 `deadlineMet`을 추가하고 상태에는 최근·최대 처리 시간, `deadlineMisses`와 `droppedSamples`를 누적한다. 이 runtime telemetry는 `micarrsim.result` 저장 스키마의 필수 필드가 아니다.