물 영상 정식 3D 기준·끝점·31개 컷 제작

This commit is contained in:
NINI
2026-10-10 03:25:24 +09:00
parent 5ee2b2e972
commit f8f12f963d
40 changed files with 18669 additions and 21 deletions
@@ -0,0 +1 @@
/__pycache__/
@@ -1,6 +1,6 @@
# 물 소재 3D 시제품 작성 경계 # 물 소재 3D 제작 경계
- 이 폴더는 물 영상의 세 대표 장면을 재현하는 bpy 코드다. 범용 실행기는 `tools/blender/run.py`를 사용하고 수정하지 않는다. - 이 폴더는 물 영상의 대표 장면과 승인된 9개 정식 장면을 재현하는 bpy 코드다. 범용 실행기는 `tools/blender/run.py`를 사용하고 수정하지 않는다.
- 원본·렌더는 자기 `08-scenes/r3` 아래에 둔다. 이전 결과·WAV·사용자 스타일 원본·project.json은 변경하지 않는다. - 대표 원본·렌더는 해당 새 08 버전, 정식 원본·렌더는 자기 `10–12/r2` 아래에 둔다. 이전 결과·WAV·사용자 스타일 원본·project.json은 변경하지 않는다.
- 전체 Interface 범위의 평가 mesh 정점·물 경계·화면 투영 포함과 실제 O1H2·V자·두 종류 결합·동적 이웃·닫힌 mesh 부피와 `--disable-autoexec` 평가를 검사한다. 물리 시뮬레이션으로 주장하지 않는다. - 전체 Interface 범위의 평가 mesh 정점·물 경계·화면 투영 포함과 실제 O1H2·V자·두 종류 결합·동적 이웃·닫힌 mesh 부피와 `--disable-autoexec` 평가를 검사한다. 물리 시뮬레이션으로 주장하지 않는다.
- draft 품질 조정은 별도 렌더 시도를 보존한다. 제출 후 결과 변경은 새 revision으로 진행하며 새 scenes 승인 전 정식 10–12를 실행하지 않는다. - draft 품질 조정은 별도 렌더 시도를 보존한다. 제출 후 결과 변경은 새 revision으로 진행하며 새 scenes 승인 전 정식 10–12를 실행하지 않는다. 승인 후에도 총괄의 실제 10/11 완료 기록에 맞춰 다음 렌더를 진행한다.
@@ -1,8 +1,10 @@
# 물 영상의 제한된 3D 시제품 # 물 영상의 3D 제작
## 제한된 시제품 기록
`build_preview.py`는 BondPair·Interface·DropletStretch 세 Scene만 만든다. 불규칙한 물 분자, 실제 연결 쌍 변화, 내부/표면 강조, 같은 부피의 3D 변형을 구현한 설명 모형이다. MD·유체 시뮬레이션은 아니다. 31컷 전체 모델·최종 영상은 scenes 승인 뒤 별도 제작한다. `build_preview.py`는 BondPair·Interface·DropletStretch 세 Scene만 만든다. 불규칙한 물 분자, 실제 연결 쌍 변화, 내부/표면 강조, 같은 부피의 3D 변형을 구현한 설명 모형이다. MD·유체 시뮬레이션은 아니다. 31컷 전체 모델·최종 영상은 scenes 승인 뒤 별도 제작한다.
저장소 루트에서 기존 설치를 사용한다. Blender 논리 F는 실제 F+1, MP4는 `[start,end)`다. 대표 영상은 c07 4040ms/202프레임, c16 4880ms/244프레임, c18 4000ms/200프레임의 무음 영상이다. 끝 PNG는 exclusive 경계이며 MP4 마지막 프레임보다 20ms 뒤다. 승인·제출된 08 r3에서 아래 시제품 작성 명령을 다시 실행하지 않는다. `build_preview.py`의 top-level 실행은 원본을 덮어쓰므로 정식 제작에서 import하지 않는다. 저장소 루트에서 기존 설치를 사용한다. Blender 프레임은 논리 프레임+1이며 MP4는 `[start,end)`다. 대표 영상은 c07 4040ms/202프레임, c16 4880ms/244프레임, c18 4000ms/200프레임의 무음 영상이다. 끝 PNG는 exclusive 경계이며 MP4 마지막 프레임보다 20ms 뒤다.
```powershell ```powershell
& 'C:/Program Files/Blender Foundation/Blender 5.2/blender.exe' --background --disable-autoexec --python-exit-code 1 --python projects/water-hydrogen-bond-surface-tension/blender/build_preview.py & 'C:/Program Files/Blender Foundation/Blender 5.2/blender.exe' --background --disable-autoexec --python-exit-code 1 --python projects/water-hydrogen-bond-surface-tension/blender/build_preview.py
@@ -16,3 +18,14 @@
작성 순서는 새 draft 재현용이다. `prepare_artifacts.py`는 대표 미디어가 빈 08/09 초안을 작성하므로 제출한 r3에서 재실행하지 않는다. `render_preview.py`는 현재 초안에 실제 증거만 추가한다. 완료된 결과는 새 버전에서 작업한다. 작성 순서는 새 draft 재현용이다. `prepare_artifacts.py`는 대표 미디어가 빈 08/09 초안을 작성하므로 제출한 r3에서 재실행하지 않는다. `render_preview.py`는 현재 초안에 실제 증거만 추가한다. 완료된 결과는 새 버전에서 작업한다.
`test_model.py`는 없는 파일·Scene 누락·H 원자 누락·부피 driver 오류·원자 중심은 유지하고 mesh 정점만 물 밖에 둔 fixture·기존 r2 원본의 실제 RED와 수정 원본 GREEN을 확인한다. 포함 관계 실패 문구도 검사·기록한다. Interface 전체 논리 F2468–3172의 실제 원자·내부 실선·활성 점선 mesh 정점을 실제 물 mesh 경계와 camera 투영 윤곽에 대조한다. 최소 축 여백 0.03을 요구한다. 물 모형은 앞면·윗면이 실제 열린 절개이며 벽·고체 껍질이 아니다. 부피는 시작·중간 세 곳·전체 컷 끝을 실제 mesh로 평가한다. 실제 렌더는 기존 실행기의 source hash·Scene·profile·frame 검사, PNG/MP4 probe 및 전체 디코드를 거친다. 큰 원본·렌더·실패 시도는 해당 r3의 좁은 `.gitignore`로 보존하고 작은 검증 JSON에 복사할 파일·해시를 기록한다. `test_model.py`는 없는 파일·Scene 누락·H 원자 누락·부피 driver 오류·원자 중심은 유지하고 mesh 정점만 물 밖에 둔 fixture·기존 r2 원본의 실제 RED와 수정 원본 GREEN을 확인한다. 포함 관계 실패 문구도 검사·기록한다. Interface 전체 논리 F2468–3172의 실제 원자·내부 실선·활성 점선 mesh 정점을 실제 물 mesh 경계와 camera 투영 윤곽에 대조한다. 최소 축 여백 0.03을 요구한다. 물 모형은 앞면·윗면이 실제 열린 절개이며 벽·고체 껍질이 아니다. 부피는 시작·중간 세 곳·전체 컷 끝을 실제 mesh로 평가한다. 실제 렌더는 기존 실행기의 source hash·Scene·profile·frame 검사, PNG/MP4 probe 및 전체 디코드를 거친다. 큰 원본·렌더·실패 시도는 해당 r3의 좁은 `.gitignore`로 보존하고 작은 검증 JSON에 복사할 파일·해시를 기록한다.
## 승인 후 정식 10–12 제작
`build_production.py --stage references`는 승인된 08 원본을 읽기만 하여 10의 실제 기준 모델을 만든다. 실제 7개 기준 PNG를 검증하고 총괄이 10 완료를 기록한 뒤 `--stage images`로 10 원본을 열어 11의 9개 Scene 애니메이션 원본을 만든다. 11은 62개 frame ID와 continuous 경계의 공유 PNG를 검증하고 총괄이 11 완료를 기록한 다음 12의 31개 전체 컷을 렌더한다. 정식 원본은 이미 존재하면 덮어쓰지 않는다.
`production_requests.py --stage <단계>` → `production_render.py --stage <단계>` → `production_check.py --stage <단계>`가 실제 요청·GPU 직렬 렌더·계약/해시 대조 경로다. `production_test.py --stage references|images`는 실제 누락/중심 내부·정점 외부 fixture의 RED와 원본 GREEN을 기록한다. `production_views.py --stage 11-images|12-motion`은 실제 끝점과 MP4 디코드의 시작·중간·마지막을 모바일 크기로 비교한다. 정지 유지 구간은 설명 맥락으로 판단한다.
`export_anchors.py`는 11 실제 원본의 1080×1920 camera 좌표·전체 31컷 target/bbox·필요한 매 프레임 라벨 키를 내보낸다. label_pixel은 배치 시작점이며 합성 단계에서 실제 글자·자막·충돌을 확인한다. 기존 2D 색 bbox나 945×1680 좌표를 재사용하지 않는다. 모든 최종 파일·SHA는 각 단계 manifest.json으로 전달한다.
`production_preserve.py`는 승인 실물 목록의 SHA와 고정 커밋의 작업 사본 체크아웃 바이트를 대조한다. 기존 WAV·스타일·01–09 및 이전 r1 결과를 수정하지 않았는지 확인한다.
@@ -0,0 +1,253 @@
"""승인된 물 소재 자산을 복사해 정식 기준 원본과 애니메이션 원본을 만든다."""
import argparse
import hashlib
import json
import math
from pathlib import Path
import sys
import bpy
from mathutils import Vector
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
APPROVED=PROJECT/'stages/08-scenes/r3/generated-source/preview.blend'
APPROVED_HASH='ba0850a2148824cc8416704c03f14e5714ca01a16620a4eb26981e238af78c73'
parser=argparse.ArgumentParser()
parser.add_argument('--stage',choices=['references','images'],required=True)
args=parser.parse_args(sys.argv[sys.argv.index('--')+1:])
def clone(obj,name,scene,parent=None):
result=obj.copy()
if obj.data:
result.data=obj.data.copy()
result.name=name
result.animation_data_clear()
result.parent=parent
scene.collection.objects.link(result)
return result
def studio(name,base,bounds,scale):
scene=bpy.data.scenes.new(name)
scene.world=base.world
scene.render.engine='BLENDER_EEVEE'
scene.render.fps=50
scene.render.resolution_x,scene.render.resolution_y=1080,1920
scene.eevee.taa_render_samples=64
scene.view_settings.view_transform='Standard'
for obj in base.objects:
if obj.type in ('CAMERA','LIGHT') or obj.get('role')=='background':
item=clone(obj,name+obj.type+obj.name,scene)
if item.type=='CAMERA':
scene.camera=item
item.data.ortho_scale=scale
scene.frame_start,scene.frame_end=bounds
return scene
def molecule(scene,name,location,size=1,rotation=0):
template=bpy.data.objects['PairA']
root=clone(template,name,scene)
root.location=location
root.rotation_euler=(0,0,rotation)
root.scale=(size,)*3
for child in template.children:
clone(child,name+child.name[len('PairA'):],scene,root)
return root
def droplet(scene,name,location,scale):
obj=clone(bpy.data.objects['Droplet'],name,scene)
obj.location=location
obj.scale=scale
return obj
def anchor(scene,name,location,label):
obj=bpy.data.objects.new(name,None)
scene.collection.objects.link(obj)
obj.location=location
obj['anchor']=label
return obj
def mark(obj,label):
obj['anchor']=label
def collection(name,objects):
group=bpy.data.collections.new(name)
group.use_fake_user=True
for obj in objects:
group.objects.link(obj)
return group
def animate_network(scene,prefix,start,turn,end):
# 온전한 분자를 움직이고 H···O 원통을 실제 원자 위치로 갱신한다.
roots=[scene.objects[prefix+str(i)] for i in range(10)]
for i,root in enumerate(roots):
position=root.location.copy()
for frame,delta in [(start,0),(turn,.09*math.sin(i+1)),(end,.06*math.cos(i))]:
root.location=position+Vector((delta,0,0))
root.keyframe_insert('location',frame=frame)
template=bpy.data.objects['LiquidDash0_1_0']
pairs=[(0,1),(1,2),(3,4),(3,7),(4,5),(4,6),(4,7),(5,6),(5,8),(8,9)]
for i,j in pairs:
hydrogen,oxygen=scene.objects[prefix+str(i)+'_H1'],scene.objects[prefix+str(j)+'_O']
for segment in range(6):
obj=clone(template,prefix+'Dash'+str(i)+'_'+str(j)+'_'+str(segment),scene)
obj['hydrogen'],obj['oxygen']=hydrogen.name,oxygen.name
for frame in [start,1779,turn,2257,end]:
scene.frame_set(frame)
bpy.context.view_layer.update()
a,b=hydrogen.matrix_world.translation,oxygen.matrix_world.translation
direction=b-a
obj.location=a+direction*(.18+segment*.11+.031)
obj.rotation_euler=direction.to_track_quat('Z','Y').to_euler()
active=not ((i,j)==(3,4) and frame>=turn) and not ((i,j)==(3,7) and frame<turn) and not ((i,j)==(4,5) and frame>=2257) and not ((i,j)==(4,6) and frame<2257)
obj.scale=(1 if active else .001,1 if active else .001,direction.length*.062/.1)
for path in ['location','rotation_euler','scale']:
obj.keyframe_insert(path,frame=frame)
if args.stage=='references':
assert hashlib.sha256(APPROVED.read_bytes()).hexdigest()==APPROVED_HASH, '승인 원본 해시 불일치'
bpy.ops.wm.open_mainfile(filepath=str(APPROVED))
pair=bpy.data.scenes['BondPair']
drop=bpy.data.scenes['DropletStretch']
interface=bpy.data.scenes['Interface']
opening=studio('OpeningDroplet',drop,(1,397),10.5)
obj=droplet(opening,'OpeningWater',(0,0,0),(1,1,1))
mark(obj,'opening_drop')
anchor(opening,'OpeningMoleculeInset',(1.65,0,1.7),'molecule_inset')
single=studio('SingleMolecule',pair,(397,905),9)
molecule(single,'SingleWater',(0,0,-.4),1.8)
for suffix,label in [('O','single_O_delta_minus'),('H1','single_H_delta_plus'),('H2','single_H2_delta_plus')]:
mark(single.objects['SingleWater_'+suffix],label)
network=studio('LiquidNetwork',interface,(1637,2469),13)
for i in range(10):
old=interface.objects['Liquid'+str(i)]
molecule(network,'Network'+str(i),(old.location.x,old.location.y,old.location.z+1.3),.58,old.rotation_euler.z)
mark(network.objects['Network4'],'network_interior')
comparison=studio('EqualVolumeComparison',drop,(4077,4370),12.5)
size=.72
droplet(comparison,'ComparisonLong',(-1.65,0,0),(size*1.65,size/math.sqrt(1.65),size/math.sqrt(1.65)))
droplet(comparison,'ComparisonRound',(1.65,0,0),(size,)*3)
mark(comparison.objects['ComparisonLong'],'equal_volume_left')
mark(comparison.objects['ComparisonRound'],'equal_volume_right')
conditions=studio('ShapeConditions',drop,(4370,4889),12.5)
droplet(conditions,'SmallFreeDrop',(-1.65,0,.2),(.58,)*3)
# 큰 접촉 방울은 같은 부피 비교가 아닌 별도 조건 모형이다.
contact=droplet(conditions,'LargeContactDrop',(1.55,0,-.275),(1.02,1.02,.75/1.3))
mark(conditions.objects['SmallFreeDrop'],'small_free_condition')
mark(contact,'large_contact_condition')
bpy.context.window.scene=conditions
bpy.ops.mesh.primitive_cube_add(size=1,location=(1.55,0,-1.15))
floor=bpy.context.object
floor.name='ContactFloor'
floor.scale=(3.1,2.5,.25)
floor_material=bpy.data.materials.new('ContactFloorCream')
floor_material.diffuse_color=(.78,.73,.62,1)
floor_material.use_nodes=True
floor_material.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value=(.78,.73,.62,1)
floor_material.node_tree.nodes['Principled BSDF'].inputs['Roughness'].default_value=.8
floor.data.materials.append(floor_material)
floor['role']='condition_floor'
mark(floor,'contact_floor')
answer=studio('AnswerDroplet',drop,(4889,5529),12.5)
droplet(answer,'AnswerWater',(0,0,-.7),(1,)*3)
mark(answer.objects['AnswerWater'],'answer_macro_drop')
for i,position in enumerate([(-1.1,0,1.8),(0,.1,2.05),(1.1,0,1.8)]):
molecule(answer,'AnswerMolecule'+str(i),position,.55,i*.2)
mark(answer.objects['AnswerMolecule0_H1'],'answer_inset_H')
mark(answer.objects['AnswerMolecule1_O'],'answer_inset_O')
mark(answer.objects['AnswerMolecule2'],'answer_inset_extent')
mark(pair.objects['PairA_H1'],'pair_H_delta_plus')
mark(pair.objects['PairB_O'],'pair_O_delta_minus')
mark(interface.objects['Liquid4'],'water_interior')
mark(interface.objects['Liquid1'],'water_surface')
anchor(interface,'InterfaceAirAnchor',(0,0,1.45),'air_region')
anchor(interface,'InterfaceWaterAnchor',(0,0,-2.95),'cutaway_water_region')
mark(drop.objects['Droplet'],'same_volume_drop')
collection('StudioAssets',[o for s in bpy.data.scenes for o in s.objects if o.get('role')=='background'])
collection('StyleAssets',[o for s in bpy.data.scenes for o in s.objects if o.get('role')=='background'])
collection('DropletStudioAssets',[o for s in bpy.data.scenes if s.name in ['OpeningDroplet','DropletStretch','AnswerDroplet'] for o in s.objects if o.get('role')=='background'])
collection('MoleculeStudioAssets',[o for s in bpy.data.scenes if s.name in ['SingleMolecule','BondPair','LiquidNetwork'] for o in s.objects if o.get('role')=='background'])
collection('ComparisonStudioAssets',[o for s in bpy.data.scenes if s.name in ['EqualVolumeComparison','ShapeConditions'] for o in s.objects if o.get('role')=='background'])
collection('MoleculeAssets',[o for o in bpy.data.objects if o.get('role') in ('molecule','atom','covalent','hydrogen_bond')])
collection('DropletAssets',[o for o in bpy.data.objects if o.get('role')=='same_volume_water'])
collection('InterfaceAssets',[interface.objects['WaterCutawayBody']])
# 실제 기준 원본에도 액체 연결 자산을 구성하여 후속 원본이 새 경로만 참조하지 않게 한다.
bpy.context.window.scene=network
animate_network(network,'Network',1637,2019,2469)
bpy.context.window.scene=answer
# 답 회수에는 거시 방울과 공간적으로 분리된 분자 개념도가 공존한다.
for i,j in [(0,1),(1,2)]:
template=bpy.data.objects['LiquidDash0_1_0']
h,o=answer.objects['AnswerMolecule'+str(i)+'_H1'],answer.objects['AnswerMolecule'+str(j)+'_O']
bpy.context.view_layer.update()
a,b=h.matrix_world.translation,o.matrix_world.translation
for n in range(6):
obj=clone(template,'AnswerDash'+str(i)+'_'+str(j)+'_'+str(n),answer)
obj['hydrogen'],obj['oxygen']=h.name,o.name
direction=b-a
obj.location=a+direction*(.18+n*.11+.031)
obj.rotation_euler=direction.to_track_quat('Z','Y').to_euler()
obj.scale=(1,1,direction.length*.062/.1)
for scene in bpy.data.scenes:
scene['approved_source_sha256']=APPROVED_HASH
destination=PROJECT/'stages/10-references/r2/generated-source/references.blend'
else:
artifact=json.loads((PROJECT/'stages/10-references/r2/artifact.json').read_text(encoding='utf-8'))
result=artifact['payload']['result']
source=ROOT/result['scene_file']['path']
assert result['scene_file']['verified'] and hashlib.sha256(source.read_bytes()).hexdigest()==result['scene_file']['sha256']
assert len(result['items'])==7 and all((ROOT/item['media']['path']).is_file() and item['media']['verified'] for item in result['items'])
bpy.ops.wm.open_mainfile(filepath=str(source))
for scene in bpy.data.scenes:
scene['reference_source_sha256']=result['scene_file']['sha256']
drop=bpy.data.objects['Droplet']
expression='1+0.85*min(max((frame-3173)/200,0),1)*(1-min(max((frame-3657)/196,0),1))'
drop.animation_data.drivers[0].driver.expression=expression
for axis in [1,2]:
drop.animation_data.drivers[axis].driver.expression='('+expression+')**(-0.5)'
# 액체 점선의 위치·방향을 프레임마다 실제 H/O 평가 위치에 맞춰 굽는다.
network=bpy.data.scenes['LiquidNetwork']
bpy.context.window.scene=network
segments=[obj for obj in network.objects if obj.get('role')=='hydrogen_bond']
for obj in segments:
obj.animation_data_clear()
for frame in range(1637,2470):
network.frame_set(frame)
bpy.context.view_layer.update()
for obj in segments:
h,o=network.objects[obj['hydrogen']],network.objects[obj['oxygen']]
a,b=h.matrix_world.translation,o.matrix_world.translation
direction=b-a
segment=int(obj.name.rsplit('_',1)[1])
obj.location=a+direction*(.18+segment*.11+.031)
obj.rotation_euler=direction.to_track_quat('Z','Y').to_euler()
pair=(h.parent.name,o.parent.name)
first=min(max((frame-1779)/240,0),1)
second=min(max((frame-2019)/238,0),1)
weight={('Network3','Network4'):1-first,('Network3','Network7'):first,('Network4','Network5'):1-second,('Network4','Network6'):second}.get(pair,1)
obj.scale=(max(weight,.001),max(weight,.001),direction.length*.062/.1)
for path in ['location','rotation_euler','scale']:
obj.keyframe_insert(path,frame=frame)
# 답의 수소결합은 영구 끈이 아니며 연결이 약해졌다가 다시 나타난다.
answer=bpy.data.scenes['AnswerDroplet']
for obj in answer.objects:
if obj.get('role')=='hydrogen_bond':
original=obj.scale.z
for frame,active in [(4889,1),(5100,1),(5160,.001),(5229,1),(5529,1)]:
obj.scale=(active,active,original)
obj.keyframe_insert('scale',frame=frame)
destination=PROJECT/'stages/11-images/r2/generated-source/animation.blend'
for scene in bpy.data.scenes:
bpy.context.window.scene=scene
scene.frame_set(scene.frame_start)
destination.parent.mkdir(parents=True,exist_ok=True)
assert not destination.exists(), '제출 원본 덮어쓰기 금지'
bpy.ops.wm.save_as_mainfile(filepath=str(destination))
print('정식 원본 저장',destination)
@@ -0,0 +1,85 @@
"""1080×1920 실제 카메라 투영으로 T5 주석 위치와 필요한 매 프레임 키를 내보낸다."""
import argparse
import hashlib
import json
from pathlib import Path
import sys
import bpy
import numpy as np
from mathutils import Vector
from bpy_extras.object_utils import world_to_camera_view
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
parser=argparse.ArgumentParser()
parser.add_argument('--source',required=True)
parser.add_argument('--out',required=True)
args=parser.parse_args(sys.argv[sys.argv.index('--')+1:])
source=Path(args.source).resolve()
bpy.ops.wm.open_mainfile(filepath=str(source))
cuts=json.loads((PROJECT/'stages/07-cuts/r1/artifact.json').read_text(encoding='utf-8'))['payload']['cuts']
plans=json.loads((PROJECT/'stages/08-scenes/r3/artifact.json').read_text(encoding='utf-8'))['payload']['scenes']
cache={}
def bbox_objects(obj):
return [obj] if obj.type=='MESH' else [child for child in obj.children if child.type=='MESH']
def coordinates(obj,deps):
if obj.name not in cache:
assert not obj.modifiers and not obj.data.shape_keys, '정점 캐시가 불가능한 변형 mesh'
evaluated=obj.evaluated_get(deps)
mesh=evaluated.to_mesh()
cache[obj.name]=np.array([tuple(v.co) for v in mesh.vertices])
evaluated.to_mesh_clear()
matrix=np.array(obj.evaluated_get(deps).matrix_world)
return cache[obj.name] @ matrix[:3,:3].T + matrix[:3,3]
result=[]
for cut,plan in zip(cuts,plans):
scene=bpy.data.scenes[plan['start_state'].split(';')[0].split('=')[1]]
bpy.context.window.scene=scene
assert scene.camera.data.type=='ORTHO'
origin=world_to_camera_view(scene,scene.camera,Vector((0,0,0)))
projection=np.array([tuple(world_to_camera_view(scene,scene.camera,Vector(b))-origin)[:2] for b in [(1,0,0),(0,1,0),(0,0,1)]])
offset=np.array(tuple(origin)[:2])
objects=[obj for obj in scene.objects if obj.get('anchor')]
start,end=cut['start_ms']//20,cut['end_ms']//20
tracks={obj['anchor']:[] for obj in objects}
states=[]
for frame in range(start,end+1):
scene.frame_set(frame+1)
deps=bpy.context.evaluated_depsgraph_get()
labels={}
for obj in objects:
center=np.array(tuple(obj.matrix_world.translation)) @ projection + offset
target=[round(float(center[0]*1080),2),round(float((1-center[1])*1920),2)]
parts=bbox_objects(obj)
box=None
if parts:
vertices=np.vstack([coordinates(part,deps) for part in parts])
projected=vertices @ projection + offset
px=projected[:,0]*1080
py=(1-projected[:,1])*1920
box=[round(float(px.min()),2),round(float(py.min()),2),round(float(px.max()),2),round(float(py.max()),2)]
label=[round((box[0]+box[2])/2,2),round(box[1]-32,2)]
else:
label=target
assert 0<label[0]<1080 and 0<label[1]<1920, '주석 제안점 화면 밖'
labels[obj['anchor']]=dict(object_name=obj.name,target_pixel=target,label_pixel=label,bbox_px=box)
tracks[obj['anchor']].append([frame,*label,*target])
if frame in [start,(start+end)//2,end]:
states.append(dict(logical_frame=frame,blender_frame=frame+1,anchors=labels))
motion=[]
for label,values in tracks.items():
changing=any(v[1:]!=values[0][1:] for v in values[1:])
keys=[]
for index,value in enumerate(values):
if index in [0,len(values)-1] or value[1:]!=values[index-1][1:]:
keys.append(value)
motion.append(dict(anchor_id=label,animated=changing,columns=['logical_frame','label_x','label_y','target_x','target_y'],keys=keys if changing else [values[0],values[-1]]))
result.append(dict(cut_id=cut['id'],scene_name=scene.name,camera_name=scene.camera.name,start_frame=start,end_frame=end,states=states,motion=motion))
value=dict(source_path=source.relative_to(ROOT).as_posix(),source_sha256=hashlib.sha256(source.read_bytes()).hexdigest(),width=1080,height=1920,coordinate_system='top_left_pixel',logical_to_blender='logical_frame + 1',label_note='label_pixel은 실제 mesh 위 32px의 배치 시작점이다. 합성 단계에서 글자 크기·충돌·자막 여백을 실제 합성 화면으로 검수한다. bbox_px와 target_pixel은 실제 카메라 투영이다.',cuts=result)
Path(args.out).write_text(json.dumps(value,ensure_ascii=False,separators=(',',':'))+'\n',encoding='utf-8')
print('실제 카메라 주석 앵커 GREEN',len(result))
@@ -0,0 +1,104 @@
"""정식 결과의 계약·참조·실측·원본 및 복사 파일 해시를 대조한다."""
import argparse
import hashlib
import json
from pathlib import Path
import sys
import jsonschema
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
sys.path.insert(0,str(ROOT/'contracts'))
from check import semantic_errors
parser=argparse.ArgumentParser()
parser.add_argument('--stage',choices=['10-references','11-images','12-motion'],required=True)
args=parser.parse_args()
BASE=PROJECT/f'stages/{args.stage}/r2'
def read(path):
return json.loads(path.read_text(encoding='utf-8'))
artifact=read(BASE/'artifact.json')
context={s['stage_id']:read(PROJECT.parent.parent/s['artifact_path']) for s in read(PROJECT/'project.json')['stages'] if (ROOT/s['artifact_path']).is_file()}
jsonschema.validate(artifact,read(ROOT/'contracts/artifact.schema.json'))
assert not semantic_errors(artifact,context,complete=True),semantic_errors(artifact,context,complete=True)
request,result=artifact['payload']['request'],artifact['payload']['result']
source=result['scene_file'] if args.stage!='12-motion' else context['11-images']['payload']['result']['scene_file']
assert source['verified'] and hashlib.sha256((ROOT/source['path']).read_bytes()).hexdigest()==source['sha256']
if args.stage!='12-motion':
assert (ROOT/source['path']).is_relative_to(BASE)
model=read(BASE/'model-check.json')
assert model['source_sha256']==source['sha256'] and model['water_containment']['frames']==705
assets={source['path']:source['sha256']}
key,id_key={'10-references':('items','reference_id'),'11-images':('frames','frame_id'),'12-motion':('clips','cut_id')}[args.stage]
if args.stage=='10-references':
assert len(result['items'])==7 and len({i['object_name'] for i in result['items']})==7
assert {i['entity_id'] for i in request['items'] if i['kind']=='background'}=={b['id'] for b in context['09-entities']['payload']['backgrounds']}
if args.stage=='11-images':
assert len(result['frames'])==62
by_id={f['frame_id']:f['media'] for f in result['frames']}
frames={f['id']:f for f in request['frames']}
assert len(by_id)==len(frames)==62
anchors=read(BASE/'anchors.json')
assert anchors['source_sha256']==source['sha256'] and (anchors['width'],anchors['height'])==(1080,1920)
assert anchors['coordinate_system']=='top_left_pixel' and len(anchors['cuts'])==31
for cut,anchor in zip(context['07-cuts']['payload']['cuts'],anchors['cuts']):
start,end=cut['start_ms']//20,cut['end_ms']//20
assert (anchor['cut_id'],anchor['start_frame'],anchor['end_frame'])==(cut['id'],start,end)
assert frames[cut['id']+'-start']['frame_index']==start and frames[cut['id']+'-end']['frame_index']==end
assert frames[cut['id']+'-start']['scene_name']==anchor['scene_name']==frames[cut['id']+'-end']['scene_name']
assert anchor['motion'] and all(track['keys'][0][0]==start and track['keys'][-1][0]==end for track in anchor['motion'])
for connection in context['08-scenes']['payload']['connections']:
left,right=connection['from_cut_id']+'-end',connection['to_cut_id']+'-start'
if connection['mode']=='continuous':
assert by_id[left]['path']==by_id[right]['path']
assert (frames[left]['scene_name'],frames[left]['frame_index'])==(frames[right]['scene_name'],frames[right]['frame_index'])
else:
assert frames[left]['scene_name']!=frames[right]['scene_name']
if args.stage=='12-motion':
assert len(result['clips'])==31 and sum(c['end_frame']-c['start_frame'] for c in request['clips'])==5528
assert sum(c['duration_ms'] for c in request['clips'])==110560
if args.stage!='10-references':
visual=read(BASE/'visual-check.json')
assert visual['source_sha256']==source['sha256']
assert all(v['visual_review']=='checked' for v in visual['views']), '실제 화면 직접 관찰 미완료'
assert len(visual['views'])==(9 if args.stage=='11-images' else 31)
for view in visual['views']:
for asset in [view['sheet']]+view.get('decoded_frames',[])+view.get('render_frames',[]):
path=ROOT/asset['path']
assert path.is_relative_to(BASE) and hashlib.sha256(path.read_bytes()).hexdigest()==asset['sha256']
assets[asset['path']]=asset['sha256']
for item in result[key]:
media=item['media']
path=ROOT/media['path']
assert path.is_file() and path.is_relative_to(BASE) and media['verified']
assert (media['width'],media['height'])==(1080,1920)
evidence=read(path.parent/'evidence.json')
assert evidence['scene_file']==source and evidence['render_profile']==request['render_profile'] and evidence['media']==media
if args.stage=='10-references':
assert evidence['logical_frames'] and len(evidence['logical_frames'])==1
elif args.stage=='11-images':
frame=next(f for f in request['frames'] if f['id']==item['frame_id'])
assert evidence['scene_name']==frame['scene_name'] and evidence['logical_frames']==[frame['frame_index']]
else:
clip=next(c for c in request['clips'] if c['cut_id']==item['cut_id'])
assert evidence['scene_name']==clip['scene_name'] and evidence['logical_frames']==list(range(clip['start_frame'],clip['end_frame']))
assert media['duration_ms']==clip['duration_ms'] and 0<media['duration_ms']<=5000
endpoints=context['11-images']['payload']
end=next(f for f in endpoints['request']['frames'] if f['id']==clip['end_frame_id'])
assert end['frame_index']-evidence['logical_frames'][-1]==1
for logical in [clip['start_frame'],(clip['start_frame']+clip['end_frame'])//2,clip['end_frame']-1]:
frame=path.parent/f'frame-{logical:08}.png'
assets[frame.relative_to(ROOT).as_posix()]=hashlib.sha256(frame.read_bytes()).hexdigest()
for file in [path,path.parent/'evidence.json']:
assets[file.relative_to(ROOT).as_posix()]=hashlib.sha256(file.read_bytes()).hexdigest()
for file in BASE.glob('*.json'):
if file.name!='manifest.json':
assets[file.relative_to(ROOT).as_posix()]=hashlib.sha256(file.read_bytes()).hexdigest()
if (BASE/'review.md').is_file():
file=BASE/'review.md'
assets[file.relative_to(ROOT).as_posix()]=hashlib.sha256(file.read_bytes()).hexdigest()
value=dict(result='GREEN',stage_id=args.stage,source_sha256=source['sha256'],requested=len(request[key]),actual=len(result[key]),render_profile=request['render_profile'],actual_assets=[dict(path=path,sha256=sha) for path,sha in sorted(assets.items())])
(BASE/'manifest.json').write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('정식 결과 검사 GREEN',args.stage,len(value['actual_assets']))
@@ -0,0 +1,35 @@
"""승인 원본·선행 단계·기존 WAV·스타일의 바이트 보존을 실제 대조한다."""
import hashlib
import json
from pathlib import Path
import subprocess
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
BASE=PROJECT/'stages/12-motion/r2'
PINNED='6b00a8f'
records=[]
def check(path,expected,kind):
actual=hashlib.sha256((ROOT/path).read_bytes()).hexdigest()
assert actual==expected, '기존 결과 바이트 변경: '+path
records.append(dict(path=path,sha256=actual,baseline=kind))
approved=json.loads((PROJECT/'stages/08-scenes/r3/design-check.json').read_text(encoding='utf-8'))
for item in approved['actual_assets']:
check(item['path'],item['sha256'],'08 r3 승인 실물 목록')
original=json.loads((PROJECT/'stages/08-scenes/r3/preservation-check.json').read_text(encoding='utf-8'))['baseline_sha256']
for path,sha in original.items():
if path.endswith('.wav') or path.endswith('stages/08-scenes/r2/generated-source/preview.blend'):
check(path,sha,'08 r3 보존 기준')
paths=subprocess.check_output(['git','ls-tree','-r','--name-only',PINNED,'--','references','projects/water-hydrogen-bond-surface-tension/stages'],cwd=ROOT,text=True,encoding='utf-8').splitlines()
for path in paths:
parts=Path(path).parts
if path.startswith('references/') or (len(parts)>4 and (int(parts[3][:2])<=9 or parts[4]=='r1')):
data=subprocess.check_output(['git','cat-file','--filters','--path='+path,PINNED+':'+path],cwd=ROOT)
check(path,hashlib.sha256(data).hexdigest(),PINNED+' 작업 사본 체크아웃 바이트')
value=dict(result='GREEN',pinned_commit=PINNED,unchanged_files=len(records),files=records)
(BASE/'preservation-check.json').write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('기존 승인 입력·WAV·스타일 보존 GREEN',len(records))
@@ -0,0 +1,83 @@
"""기존 실행기의 정식 승인·해시·디코드 검사로 요청을 실제 렌더한다."""
import argparse
import json
from pathlib import Path
import subprocess
import sys
import time
sys.path.insert(0,str(Path(__file__).resolve().parent))
from production_requests import REFERENCE_PLAN
ROOT=Path(__file__).resolve().parents[3]
PROJECT=Path('projects/water-hydrogen-bond-surface-tension')
PYTHON='C:/Users/baram/anaconda3/python.exe'
BLENDER='C:/Program Files/Blender Foundation/Blender 5.2/blender.exe'
COMPOSITOR=Path('C:/git/ShortVideoGenerator/projects/water-hydrogen-bond-surface-tension/renderer/node_modules/@remotion/compositor-win32-x64-msvc')
parser=argparse.ArgumentParser()
parser.add_argument('--stage',choices=['10-references','11-images','12-motion'],required=True)
args=parser.parse_args()
BASE=PROJECT/'stages'/args.stage/'r2'
artifact_path=BASE/'artifact.json'
artifact=json.loads((ROOT/artifact_path).read_text(encoding='utf-8'))
records_path=ROOT/BASE/'cli-commands.json'
records=json.loads(records_path.read_text(encoding='utf-8')) if records_path.exists() else []
def execute(command):
started=time.monotonic()
value=subprocess.run(command,cwd=ROOT,capture_output=True,text=True,encoding='utf-8')
records.append(dict(command=command,exit_code=value.returncode,elapsed_seconds=round(time.monotonic()-started,3)))
if value.returncode:
records[-1]['error']=value.stderr[-2500:]+value.stdout[-2500:]
records_path.write_text(json.dumps(records,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
assert value.returncode==0,(value.stdout,value.stderr)
result=json.loads(value.stdout)
if result.get('media'):
records[-1]['evidence_path']=(Path(result['media']['path']).parent/'evidence.json').as_posix()
records[-1]['source_sha256']=result['scene_file']['sha256']
records_path.write_text(json.dumps(records,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
return result
def save():
(ROOT/artifact_path).write_text(json.dumps(artifact,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
common=[PYTHON,'-B','-X','utf8','tools/blender/run.py']
source=(BASE/'generated-source/references.blend').as_posix() if args.stage=='10-references' else artifact['payload']['request']['scene_path']
if args.stage!='12-motion' and not artifact['payload']['result']:
first_scene='BondPair'
inspected=execute(common+['inspect','--source',source,'--scene',first_scene,'--require-object','PairA','--blender',BLENDER])
artifact['payload']['result']=dict(scene_file=inspected['scene_file'],**({'items':[]} if args.stage=='10-references' else {'frames':[]}))
save()
if args.stage=='12-motion' and not artifact['payload']['result']:
artifact['payload']['result']=dict(clips=[])
save()
if args.stage=='10-references':
jobs=[dict(id=id,scene=scene,obj=obj,options=['--reference-id',id,'--frame',str(frame)]) for id,kind,entity,scene,obj,frame in REFERENCE_PLAN]
key,id_key='items','reference_id'
elif args.stage=='11-images':
jobs=[dict(id=f['id'],scene=f['scene_name'],obj='StudioAssets',frame=f['frame_index'],options=['--frame-id',f['id'],'--frame',str(f['frame_index'])]) for f in artifact['payload']['request']['frames']]
key,id_key='frames','frame_id'
else:
jobs=[dict(id=c['cut_id'],scene=c['scene_name'],obj='StudioAssets',options=['--cut-id',c['cut_id'],'--start',str(c['start_frame']),'--end',str(c['end_frame'])]) for c in artifact['payload']['request']['clips']]
key,id_key='clips','cut_id'
for job in jobs:
if any(item[id_key]==job['id'] for item in artifact['payload']['result'][key]):
continue
shared=None
if args.stage=='11-images':
request={f['id']:f for f in artifact['payload']['request']['frames']}
shared=next((item['media'] for item in artifact['payload']['result']['frames'] if (request[item['frame_id']]['scene_name'],request[item['frame_id']]['frame_index'])==(job['scene'],job['frame'])),None)
if shared:
media=shared
else:
command=common+['render','--mode','production','--project',(PROJECT/'project.json').as_posix(),'--artifact',artifact_path.as_posix(),'--output-revision','2','--source',source,'--scene',job['scene'],'--require-object',job['obj'],'--blender',BLENDER,'--ffmpeg',str(COMPOSITOR/'ffmpeg.exe'),'--ffprobe',str(COMPOSITOR/'ffprobe.exe')]+job['options']
evidence=execute(command)
media=evidence['media']
item={id_key:job['id'],'media':media}
if args.stage=='10-references':
item['object_name']=job['obj']
artifact['payload']['result'][key].append(item)
save()
print('실제 결과',args.stage,job['id'],media['path'],flush=True)
print('전체 요청 렌더 완료',args.stage,flush=True)
@@ -0,0 +1,77 @@
"""승인된 31컷에서 정식 요청만 작성한다. 실제 결과는 실행 후 채운다."""
import argparse
import json
from pathlib import Path
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
PROFILE=dict(engine='BLENDER_EEVEE',device='GPU',fps=50,width=1080,height=1920,samples=64,seed=0)
REFERENCE_PLAN=[
('style-3d','style',None,'BondPair','StyleAssets',1017),
('background-droplet','background','bg-droplet','OpeningDroplet','DropletStudioAssets',0),
('background-molecules','background','bg-molecules','BondPair','MoleculeStudioAssets',1017),
('background-interface','background','bg-interface','Interface','InterfaceAssets',3050),
('background-comparison','background','bg-comparison','EqualVolumeComparison','ComparisonStudioAssets',4076),
('object-molecule','object',None,'SingleMolecule','MoleculeAssets',396),
('object-droplet','object',None,'OpeningDroplet','DropletAssets',0)]
def read(stage,revision):
return json.loads((PROJECT/f'stages/{stage}/r{revision}/artifact.json').read_text(encoding='utf-8'))
def write(stage,request,inputs):
path=PROJECT/f'stages/{stage}/r2/artifact.json'
existing=json.loads(path.read_text(encoding='utf-8')) if path.exists() else None
assert not existing or not existing['payload']['result'], '완료 또는 부분 실제 결과 덮어쓰기 금지'
value=dict(schema_version='1.1',project_id=PROJECT.name,stage_id=stage,revision=2,inputs=[dict(stage_id=s,revision=r) for s,r in inputs],payload=dict(request=request,result=None))
path.write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
if __name__=='__main__':
parser=argparse.ArgumentParser()
parser.add_argument('--stage',choices=['10-references','11-images','12-motion'],required=True)
args=parser.parse_args()
plans=read('08-scenes',3)['payload']['scenes']
if args.stage!='12-motion':
entities=read('09-entities',3)['payload']
read('02-research',1)
if args.stage=='10-references':
backgrounds={b['id']:b for b in entities['backgrounds']}
items=[]
for id,kind,entity,scene,obj,frame in REFERENCE_PLAN:
prompt='승인 08 r3의 '+scene+' 상태와 교육용 3D 고정 특징. 실제 '+obj+' 자산. '
if entity:
prompt+=' '.join(backgrounds[entity]['fixed_features'])
elif kind=='object':
prompt+='O1H2·V자·내부 남색 실선과 금색 분자간 H···O의 구별을 유지한다.' if id=='object-molecule' else '매끈한 청색 닫힌 mesh와 같은 물의 양을 유지하며 별도 껍질이 없다.'
else:
prompt+='references/1.png를 보존하고 단순 형태·정돈된 색·부드러운 조명·크림 배경으로 해석한다. 글자는 합성한다.'
items.append(dict(id=id,kind=kind,entity_id=entity,prompt=prompt))
write(args.stage,dict(style_reference='references/1.png',items=items,engine='Blender',render_profile=PROFILE),[('02-research',1),('08-scenes',3),('09-entities',3)])
elif args.stage=='11-images':
read('03-script',1)
cuts=read('07-cuts',1)['payload']['cuts']
reference=read('10-references',2)
assert reference['payload']['result'] and len(reference['payload']['result']['items'])==7
appearances={p['cut_id']:p for p in entities['appearances']}
mapping={'bg-droplet':'background-droplet','bg-molecules':'background-molecules','bg-interface':'background-interface','bg-comparison':'background-comparison'}
frames=[]
for cut,plan in zip(cuts,plans):
scene=plan['start_state'].split(';')[0].split('=')[1]
refs=['style-3d']+[mapping[b] for b in appearances[cut['id']]['background_ids']]
refs+=['object-molecule'] if scene in ['SingleMolecule','BondPair','LiquidNetwork','Interface'] else ['object-droplet']
if scene=='AnswerDroplet':
refs.append('object-molecule')
for position,key in [('start','start_ms'),('end','end_ms')]:
frames.append(dict(id=cut['id']+'-'+position,cut_id=cut['id'],position=position,prompt=plan['description']+' '+plan[position+'_state']+' '+plan['camera'],reference_ids=refs,scene_name=scene,frame_index=cut[key]//20))
write(args.stage,dict(frames=frames,engine='Blender',render_profile=PROFILE,scene_path='projects/'+PROJECT.name+'/stages/11-images/r2/generated-source/animation.blend'),[('02-research',1),('03-script',1),('07-cuts',1),('08-scenes',3),('09-entities',3),('10-references',2)])
else:
cuts=read('07-cuts',1)['payload']['cuts']
images=read('11-images',2)
assert images['payload']['result'] and len(images['payload']['result']['frames'])==62
frames={f['id']:f for f in images['payload']['request']['frames']}
clips=[]
for cut,plan in zip(cuts,plans):
start,end=frames[cut['id']+'-start'],frames[cut['id']+'-end']
clips.append(dict(cut_id=cut['id'],start_frame_id=start['id'],end_frame_id=end['id'],duration_ms=cut['end_ms']-cut['start_ms'],motion_prompt=plan['description']+' '+plan['start_state']+' → '+plan['end_state']+' '+plan['camera'],scene_name=start['scene_name'],start_frame=start['frame_index'],end_frame=end['frame_index']))
write(args.stage,dict(clips=clips,engine='Blender',render_profile=PROFILE,scene_path=images['payload']['result']['scene_file']['path']),[('07-cuts',1),('08-scenes',3),('11-images',2)])
@@ -0,0 +1,43 @@
"""실제 10/11 원본을 각각 다시 열어 모델·재질·기준 컬렉션 재사용을 대조한다."""
import hashlib
import json
from pathlib import Path
import bpy
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
def signature(source):
bpy.ops.wm.open_mainfile(filepath=str(source))
assert not bpy.data.libraries and not bpy.data.images and not bpy.data.fonts and not bpy.data.sounds
meshes={}
for obj in bpy.data.objects:
if obj.type=='MESH':
meshes[obj.name]=dict(vertices=[tuple(v.co) for v in obj.data.vertices],polygons=[tuple(p.vertices) for p in obj.data.polygons],materials=[m.name for m in obj.data.materials])
materials={}
for material in bpy.data.materials:
nodes=[]
for node in material.node_tree.nodes if material.use_nodes else []:
values=[]
for socket in node.inputs:
if hasattr(socket,'default_value'):
value=socket.default_value
if not isinstance(value,(str,int,float,bool)):
value=list(value) if hasattr(value,'__iter__') else str(value)
values.append((socket.name,value))
nodes.append((node.type,values))
materials[material.name]=dict(diffuse=list(material.diffuse_color),nodes=nodes)
return dict(meshes=meshes,materials=materials,collections={c.name:sorted(o.name for o in c.objects) for c in bpy.data.collections},scenes={s.name:dict(objects=sorted(o.name for o in s.objects),camera=s.camera.name,ortho_scale=s.camera.data.ortho_scale) for s in bpy.data.scenes})
reference=PROJECT/'stages/10-references/r2/generated-source/references.blend'
animation=PROJECT/'stages/11-images/r2/generated-source/animation.blend'
baseline=signature(reference)
actual=signature(animation)
assert baseline==actual, '실제 기준 모델·재질·컬렉션·카메라 재사용 불일치'
reference_hash=hashlib.sha256(reference.read_bytes()).hexdigest()
assert all(s['reference_source_sha256']==reference_hash for s in bpy.data.scenes), '10 원본 해시 연결 불일치'
value=dict(result='GREEN',reference_source_sha256=reference_hash,animation_source_sha256=hashlib.sha256(animation.read_bytes()).hexdigest(),actual_reopened_sources=2,mesh_objects=len(actual['meshes']),materials=len(actual['materials']),scenes=len(actual['scenes']),matching_fields=['local_mesh_vertices','polygons','material_slots','material_nodes','collection_members','scene_members','actual_camera_name','orthographic_scale'])
(PROJECT/'stages/11-images/r2/reuse-check.json').write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('실제 10→11 기준 재사용 GREEN',value['mesh_objects'])
@@ -0,0 +1,45 @@
"""정식 원본의 실제 누락·포함·부피 실패와 수정 성공을 기록한다."""
import argparse
import json
from pathlib import Path
import subprocess
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
BLENDER='C:/Program Files/Blender Foundation/Blender 5.2/blender.exe'
VERIFY=PROJECT/'blender/verify_production.py'
parser=argparse.ArgumentParser()
parser.add_argument('--stage',choices=['references','images'],required=True)
args=parser.parse_args()
BASE=PROJECT/f'stages/{"10-references" if args.stage=="references" else "11-images"}/r2'
SOURCE=BASE/f'generated-source/{"references" if args.stage=="references" else "animation"}.blend'
records=[]
def execute(arguments,expected,reason=None):
command=[BLENDER,'--background','--disable-autoexec','--python-exit-code','1']+arguments
value=subprocess.run(command,cwd=ROOT,capture_output=True,text=True,encoding='utf-8')
records.append(dict(command=command,exit_code=value.returncode,expected_exit_code=expected,matched_reason=reason,assertion_errors=[line for line in value.stderr.splitlines() if line.startswith('AssertionError:')]))
(BASE/'model-test-results.json').write_text(json.dumps(dict(result='RUNNING',executions=records),ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
assert value.returncode==expected,(value.stdout,value.stderr)
assert not reason or reason in value.stderr,(reason,value.stderr)
def check(source,expected,reason=None,out=None):
options=['--source',str(source),'--out',str(out or BASE/'generated-source/invalid-result.json')]
if args.stage=='images':
options.append('--animated')
execute(['--python',str(VERIFY),'--']+options,expected,reason)
check(BASE/'generated-source/missing.blend',1,'원본 파일 없음')
check(PROJECT/'stages/08-scenes/r3/generated-source/preview.blend',1,'대표 Scene 누락/혼입')
for name,edit,reason in [
('missing-scene',"bpy.data.scenes.remove(bpy.data.scenes['SingleMolecule'])",'대표 Scene 누락/혼입'),
('outside-mesh',"bpy.data.objects['Liquid9_H1'].data.vertices[0].co.x=10",'물 영역 밖 또는 여백 부족')]:
fixture=BASE/f'generated-source/{name}.blend'
expression=f'import bpy; bpy.ops.wm.open_mainfile(filepath={str(SOURCE)!r}); {edit}; bpy.ops.wm.save_as_mainfile(filepath={str(fixture)!r})'
execute(['--python-expr',expression],0)
check(fixture,1,reason)
check(SOURCE,0,out=BASE/'model-check.json')
(BASE/'model-test-results.json').write_text(json.dumps(dict(result='GREEN',negative_cases=4,positive_cases=1,executions=records),ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('정식 원본 RED→GREEN 확인',args.stage)
@@ -0,0 +1,103 @@
"""실제 끝점과 MP4 디코드의 모든 컷 상태를 모바일 비교 화면으로 만든다."""
import argparse
import hashlib
import json
from pathlib import Path
import subprocess
from PIL import Image,ImageChops,ImageStat,ImageDraw
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
FFMPEG='C:/git/ShortVideoGenerator/projects/water-hydrogen-bond-surface-tension/renderer/node_modules/@remotion/compositor-win32-x64-msvc/ffmpeg.exe'
parser=argparse.ArgumentParser()
parser.add_argument('--stage',choices=['11-images','12-motion'],required=True)
args=parser.parse_args()
BASE=PROJECT/f'stages/{args.stage}/r2'
artifact=json.loads((BASE/'artifact.json').read_text(encoding='utf-8'))['payload']
folder=BASE/'generated-source/inspection'
folder.mkdir(exist_ok=True)
records_path=BASE/'visual-cli-commands.json'
records=json.loads(records_path.read_text(encoding='utf-8')) if records_path.exists() else []
def sheet(paths,labels,out,columns):
rows=(len(paths)+columns-1)//columns
result=Image.new('RGB',(360*columns,664*rows),'white')
draw=ImageDraw.Draw(result)
for index,(path,label) in enumerate(zip(paths,labels)):
x,y=(index%columns)*360,(index//columns)*664
draw.text((x+8,y+5),label,fill='black')
result.paste(Image.open(path).convert('RGB').resize((360,640),Image.Resampling.LANCZOS),(x,y+24))
result.save(out)
return dict(path=out.relative_to(ROOT).as_posix(),sha256=hashlib.sha256(out.read_bytes()).hexdigest())
views=[]
if args.stage=='11-images':
frames={f['id']:f for f in artifact['request']['frames']}
groups={}
seen=set()
for item in artifact['result']['frames']:
frame=frames[item['frame_id']]
if item['media']['path'] in seen:
continue
seen.add(item['media']['path'])
groups.setdefault(frame['scene_name'],[]).append((ROOT/item['media']['path'],str(frame['frame_index'])))
for scene,images in groups.items():
path=folder/(scene+'.png')
asset=sheet([p for p,label in images],['F'+label for p,label in images],path,min(4,len(images)))
views.append(dict(scene_name=scene,unique_frames=len(images),sheet=asset,visual_review='not_checked'))
else:
requests={c['cut_id']:c for c in artifact['request']['clips']}
endpoints=json.loads((PROJECT/'stages/11-images/r2/artifact.json').read_text(encoding='utf-8'))['payload']
endpoint_media={f['frame_id']:ROOT/f['media']['path'] for f in endpoints['result']['frames']}
for item in artifact['result']['clips']:
clip=requests[item['cut_id']]
video=ROOT/item['media']['path']
sha=hashlib.sha256(video.read_bytes()).hexdigest()
count=clip['end_frame']-clip['start_frame']
indices=[0,count//2,count-1]
decoded=[video.parent/f'decoded-review-{index:03}.png' for index in [1,2,3]]
if not all(p.is_file() for p in decoded):
commands=[
[str(Path(FFMPEG).with_name('ffprobe.exe')),'-v','error','-count_frames','-show_streams','-of','json',str(video)],
[FFMPEG,'-hide_banner','-loglevel','error','-xerror','-i',str(video),'-map','0:v:0','-c:v','rawvideo','-f','null','-']]
commands += [[FFMPEG,'-hide_banner','-loglevel','error','-n','-i',str(video),'-ss',str(index/50),'-frames:v','1',str(path)] for index,path in zip(indices,decoded) if not path.is_file()]
for command_index,command in enumerate(commands):
value=subprocess.run(command,capture_output=True,text=True,encoding='utf-8')
record=dict(command=command,exit_code=value.returncode,media_sha256=sha)
if value.returncode:
record['error']=value.stderr
records.append(record)
records_path.write_text(json.dumps(records,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
assert value.returncode==0,value.stderr
if command_index==0:
streams=json.loads(value.stdout)['streams']
assert len(streams)==1 and streams[0]['codec_name']=='h264'
assert int(streams[0]['nb_read_frames'])==count
record['decoded_frames']=count
record['expected_frames']=count
records_path.write_text(json.dumps(records,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
logical=[clip['start_frame']+index for index in indices]
originals=[video.parent/f'frame-{frame:08}.png' for frame in logical]
errors=[sum(ImageStat.Stat(ImageChops.difference(Image.open(a).convert('RGB'),Image.open(b).convert('RGB'))).mean)/3 for a,b in zip(decoded,originals)]
assert max(errors)<5, 'MP4 실제 디코드와 원본 렌더 화면 불일치'
start_png=endpoint_media[clip['start_frame_id']]
end_png=endpoint_media[clip['end_frame_id']]
start_error=sum(ImageStat.Stat(ImageChops.difference(Image.open(start_png).convert('RGB'),Image.open(originals[0]).convert('RGB'))).mean)/3
assert start_error<.1, '11 시작 PNG와 12 실제 시작 상태 불일치'
end_difference=sum(ImageStat.Stat(ImageChops.difference(Image.open(end_png).convert('RGB'),Image.open(originals[-1]).convert('RGB'))).mean)/3
path=folder/(clip['cut_id']+'.png')
labels=[clip['cut_id']+' F'+str(frame) for frame in logical]+['11 end F'+str(clip['end_frame'])]
asset=sheet(decoded+[end_png],labels,path,4)
views.append(dict(cut_id=clip['cut_id'],scene_name=clip['scene_name'],media_sha256=sha,logical_frames=logical,end_png_logical_frame=clip['end_frame'],last_video_frame_delta=1,last_video_time_delta_ms=20,start_endpoint_png_mean_error=start_error,end_boundary_last_render_mean_difference=end_difference,decoded_frames=[dict(path=p.relative_to(ROOT).as_posix(),sha256=hashlib.sha256(p.read_bytes()).hexdigest()) for p in decoded],render_frames=[dict(path=p.relative_to(ROOT).as_posix(),sha256=hashlib.sha256(p.read_bytes()).hexdigest()) for p in originals],mean_decode_render_pixel_error=errors,sheet=asset,visual_review='not_checked'))
previous=json.loads((BASE/'visual-check.json').read_text(encoding='utf-8')) if (BASE/'visual-check.json').exists() else {}
for view in views:
old=next((v for v in previous.get('views',[]) if v.get('sheet')==view['sheet']),None)
if old and old.get('visual_review')=='checked':
view['visual_review']='checked'
view['observation']=old['observation']
source=artifact['result']['scene_file'] if args.stage=='11-images' else json.loads((PROJECT/'stages/11-images/r2/artifact.json').read_text(encoding='utf-8'))['payload']['result']['scene_file']
complete=len(artifact['result']['frames'])==62 if args.stage=='11-images' else len(views)==31
value=dict(result='READY_FOR_VISUAL_REVIEW' if complete else 'PARTIAL',source_sha256=source['sha256'],width=1080,height=1920,panel_width=360,panel_height=640,views=views)
(BASE/'visual-check.json').write_text(json.dumps(value,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('실제 관찰 자료',args.stage,len(views))
@@ -19,6 +19,7 @@ def containment(scene):
evaluated = body.evaluated_get(deps) evaluated = body.evaluated_get(deps)
mesh = evaluated.to_mesh() mesh = evaluated.to_mesh()
water = np.array([tuple(evaluated.matrix_world @ v.co) for v in mesh.vertices]) water = np.array([tuple(evaluated.matrix_world @ v.co) for v in mesh.vertices])
faces=[list(face.vertices) for face in mesh.polygons]
evaluated.to_mesh_clear() evaluated.to_mesh_clear()
low, high = water.min(axis=0), water.max(axis=0) low, high = water.min(axis=0), water.max(axis=0)
# 실제 물 표시 mesh에서 경계를 얻고 중심·별도 메타데이터만으로 판정하지 않는다. # 실제 물 표시 mesh에서 경계를 얻고 중심·별도 메타데이터만으로 판정하지 않는다.
@@ -26,6 +27,7 @@ def containment(scene):
projection = np.array([tuple(world_to_camera_view(scene,scene.camera,Vector(b))-origin)[:2] for b in ((1,0,0),(0,1,0),(0,0,1))]) projection = np.array([tuple(world_to_camera_view(scene,scene.camera,Vector(b))-origin)[:2] for b in ((1,0,0),(0,1,0),(0,0,1))])
offset = np.array(tuple(origin)[:2]) offset = np.array(tuple(origin)[:2])
screen = (water @ projection + offset) * (1080,1920) screen = (water @ projection + offset) * (1080,1920)
face_polygons=[screen[face] for face in faces]
points = sorted(set(map(tuple,screen))) points = sorted(set(map(tuple,screen)))
def cross(a,b,c): def cross(a,b,c):
return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0]) return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])
@@ -57,6 +59,19 @@ def containment(scene):
minimum = np.minimum(minimum,margin) minimum = np.minimum(minimum,margin)
projected = (vertices @ projection + offset) * (1080,1920) projected = (vertices @ projection + offset) * (1080,1920)
assert (projected.min(axis=0)>0).all() and (projected.max(axis=0)<(1080,1920)).all(), '분자 화면 잘림' assert (projected.min(axis=0)>0).all() and (projected.max(axis=0)<(1080,1920)).all(), '분자 화면 잘림'
covered=np.zeros(len(projected),dtype=bool)
for polygon in face_polygons:
following=np.roll(polygon,-1,axis=0)
area=sum(a[0]*b[1]-b[0]*a[1] for a,b in zip(polygon,following))
if abs(area)<.001:
continue
inside=np.ones(len(projected),dtype=bool)
for a,b in zip(polygon,following):
edge=b-a
signed=(edge[0]*(projected[:,1]-a[1])-edge[1]*(projected[:,0]-a[0]))*(1 if area>0 else -1)
inside &= signed>=-.001
covered |= inside
assert covered.all(), f'실제 파란 절개 면 투영 밖: F{logical} {obj.name}'
for a,b in zip(hull,np.roll(hull,-1,axis=0)): for a,b in zip(hull,np.roll(hull,-1,axis=0)):
edge = b-a edge = b-a
distance = (edge[0]*(projected[:,1]-a[1])-edge[1]*(projected[:,0]-a[0]))/np.linalg.norm(edge) distance = (edge[0]*(projected[:,1]-a[1])-edge[1]*(projected[:,0]-a[0]))/np.linalg.norm(edge)
@@ -68,13 +83,13 @@ def containment(scene):
for face in body.data.polygons: for face in body.data.polygons:
points=np.array([tuple(body.matrix_world @ body.data.vertices[i].co) for i in face.vertices]) points=np.array([tuple(body.matrix_world @ body.data.vertices[i].co) for i in face.vertices])
assert not np.allclose(points[:,1],low[1]) and not np.allclose(points[:,2],high[2]), '앞면·윗면 절개가 닫힘' assert not np.allclose(points[:,1],low[1]) and not np.allclose(points[:,2],high[2]), '앞면·윗면 절개가 닫힘'
return dict(logical_frame_start=2468,logical_frame_end=3172,frames=705,water_bounds=[low.tolist(),high.tolist()],minimum_axis_margin=minimum.tolist(),minimum_projected_margin_px=pixel_margin,evaluated_vertices=vertex_count,mesh_roles=['atom','covalent','active_hydrogen_bond'],open_faces=['front','top']) return dict(logical_frame_start=2468,logical_frame_end=3172,frames=705,water_bounds=[low.tolist(),high.tolist()],minimum_axis_margin=minimum.tolist(),minimum_projected_margin_px=pixel_margin,evaluated_vertices=vertex_count,mesh_roles=['atom','covalent','active_hydrogen_bond'],open_faces=['front','top'],projection_check='actual_face_union_and_outer_hull')
def verify(source): def verify(source, expected=None):
assert source.is_file(), "원본 파일 없음" assert source.is_file(), "원본 파일 없음"
bpy.ops.wm.open_mainfile(filepath=str(source)) bpy.ops.wm.open_mainfile(filepath=str(source))
expected = {"BondPair": (905, 1637), "Interface": (2469, 3173), "DropletStretch": (3173, 4077)} expected = expected or {"BondPair": (905, 1637), "Interface": (2469, 3173), "DropletStretch": (3173, 4077)}
assert set(bpy.data.scenes) and set(s.name for s in bpy.data.scenes) == set(expected), "대표 Scene 누락/혼입" assert set(bpy.data.scenes) and set(s.name for s in bpy.data.scenes) == set(expected), "대표 Scene 누락/혼입"
assert not any(d.library for d in bpy.data.objects), "외부 객체" assert not any(d.library for d in bpy.data.objects), "외부 객체"
assert not bpy.data.images and not bpy.data.fonts and not bpy.data.movieclips, "외부 자산" assert not bpy.data.images and not bpy.data.fonts and not bpy.data.movieclips, "외부 자산"
@@ -0,0 +1,113 @@
"""정식 물 영상의 실제 9개 장면·기준 재사용·부피·주석 위치를 검사한다."""
import argparse
import hashlib
import json
from pathlib import Path
import sys
import bpy
import bmesh
from bpy_extras.object_utils import world_to_camera_view
sys.path.insert(0,str(Path(__file__).resolve().parent))
from verify_model import verify
ROOT=Path(__file__).resolve().parents[3]
PROJECT=ROOT/'projects/water-hydrogen-bond-surface-tension'
CUTS=json.loads((PROJECT/'stages/07-cuts/r1/artifact.json').read_text(encoding='utf-8'))['payload']['cuts']
SCENES=json.loads((PROJECT/'stages/08-scenes/r3/artifact.json').read_text(encoding='utf-8'))['payload']['scenes']
EXPECTED={}
for cut,plan in zip(CUTS,SCENES):
name=plan['start_state'].split(';')[0].split('=')[1]
start,end=cut['start_ms']//20+1,cut['end_ms']//20+1
EXPECTED[name]=(min(EXPECTED.get(name,(start,end))[0],start),end)
def volume(obj):
evaluated=obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
mesh=evaluated.to_mesh()
bm=bmesh.new()
bm.from_mesh(mesh)
assert all(e.is_manifold for e in bm.edges), '닫힌 물방울 mesh 아님'
bm.transform(evaluated.matrix_world)
value=abs(bm.calc_volume(signed=True))
bm.free()
evaluated.to_mesh_clear()
return value
def run(source,animated):
result=verify(source,EXPECTED)
assert bpy.context.scene.get('approved_source_sha256') or bpy.data.scenes['BondPair'].get('approved_source_sha256'), '승인 원본 연결 없음'
assert not bpy.data.libraries and not bpy.data.images and not bpy.data.fonts and not bpy.data.sounds
comparison=bpy.data.scenes['EqualVolumeComparison']
bpy.context.window.scene=comparison
comparison.frame_set(comparison.frame_start)
volumes=[volume(comparison.objects[name]) for name in ['ComparisonLong','ComparisonRound']]
assert abs(volumes[0]/volumes[1]-1)<.01, '비교 두 물방울 부피 불일치'
result['comparison_volumes']=volumes
anchors=[]
for cut,plan in zip(CUTS,SCENES):
scene=bpy.data.scenes[plan['start_state'].split(';')[0].split('=')[1]]
bpy.context.window.scene=scene
start,end=cut['start_ms']//20,cut['end_ms']//20
samples=[]
for logical in sorted(set([start,(start+end)//2,end])):
scene.frame_set(logical+1)
labels={}
for obj in scene.objects:
if obj.get('anchor'):
point=world_to_camera_view(scene,scene.camera,obj.matrix_world.translation)
assert 0<point.x<1 and 0<point.y<1 and point.z>0, '주석 위치 화면 밖'
labels[obj['anchor']]=dict(object_name=obj.name,x=round(point.x*1080,3),y=round((1-point.y)*1920,3),depth=round(point.z,3))
assert labels, '컷 주석 앵커 누락'
samples.append(dict(logical_frame=logical,blender_frame=logical+1,labels=labels))
anchors.append(dict(cut_id=cut['id'],scene_name=scene.name,start_frame=start,end_frame=end,samples=samples))
result['cut_anchors']=anchors
if animated:
assert bpy.data.scenes['BondPair'].get('reference_source_sha256'), '실제 10 원본 연결 없음'
scene=bpy.data.scenes['DropletStretch']
bpy.context.window.scene=scene
values=[]
for logical in range(3172,4077):
scene.frame_set(logical+1)
values.append(volume(scene.objects['Droplet']))
relative=max(abs(v/values[0]-1) for v in values)
assert relative<.01, '복귀 포함 물방울 부피 1% 초과'
scene.frame_set(3853)
assert max(abs(s-1) for s in scene.objects['Droplet'].scale)<.001, 'c21 구형 복귀 없음'
result['full_drop_volume']=dict(logical_start=3172,logical_end=4076,frames=905,relative_change=relative)
network=bpy.data.scenes['LiquidNetwork']
bpy.context.window.scene=network
pair_sets=[]
maximum=0
for frame in range(1637,2470):
network.frame_set(frame)
pairs=set()
for obj in network.objects:
if obj.get('role')!='hydrogen_bond' or obj.scale.x<=.0011:
continue
h,o=network.objects[obj['hydrogen']],network.objects[obj['oxygen']]
assert h.parent!=o.parent and h['element']=='H' and o['element']=='O'
a,b=h.matrix_world.translation,o.matrix_world.translation
error=(obj.location-a).cross(b-a).length
assert error<.003, '액체 점선이 실제 H···O 선분에서 벗어남'
maximum=max(maximum,error)
pairs.add((h.name,o.name))
if frame in [1637,2019,2257,2469]:
pair_sets.append(sorted(pairs))
assert pair_sets[0]!=pair_sets[1] and pair_sets[1]!=pair_sets[2], '액체 연결 반복 교체 없음'
result['network_frames']=833
result['network_active_pairs']=pair_sets
result['network_maximum_line_error']=maximum
result['animated']=animated
result['result']='GREEN'
return result
if __name__=='__main__':
parser=argparse.ArgumentParser()
parser.add_argument('--source',required=True)
parser.add_argument('--out',required=True)
parser.add_argument('--animated',action='store_true')
args=parser.parse_args(sys.argv[sys.argv.index('--')+1:])
result=run(Path(args.source),args.animated)
Path(args.out).write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
print('정식 원본 검사 GREEN',result['source_sha256'])
@@ -0,0 +1,2 @@
/generated-source/
/render-attempts/
@@ -8,10 +8,6 @@
"stage_id": "02-research", "stage_id": "02-research",
"revision": 1 "revision": 1
}, },
{
"stage_id": "03-script",
"revision": 1
},
{ {
"stage_id": "08-scenes", "stage_id": "08-scenes",
"revision": 3 "revision": 3
@@ -24,7 +20,50 @@
"payload": { "payload": {
"request": { "request": {
"style_reference": "references/1.png", "style_reference": "references/1.png",
"items": [], "items": [
{
"id": "style-3d",
"kind": "style",
"entity_id": null,
"prompt": "승인 08 r3의 BondPair 상태와 교육용 3D 고정 특징. 실제 StyleAssets 자산. references/1.png를 보존하고 단순 형태·정돈된 색·부드러운 조명·크림 배경으로 해석한다. 글자는 합성한다."
},
{
"id": "background-droplet",
"kind": "background",
"entity_id": "bg-droplet",
"prompt": "승인 08 r3의 OpeningDroplet 상태와 교육용 3D 고정 특징. 실제 DropletStudioAssets 자산. references/1.png의 단순 형태·정돈된 색·정보량을 교육용 3D로 해석한다. 크림색 단순 공간·부드러운 조명·선명한 형태를 반복한다. 산호색 O·미색 H·남색 내부 O–H 실선·금색 서로 다른 분자 H···O 점선·청색 물을 일관되게 사용한다. 상하단에는 제목·자막용 빈 공간을 두고 부분전하·한국어 주석은 나중 Remotion에서 검증해 합성한다. 3D에 글자를 굽지 않는다. 청색 물방울은 닫힌 mesh이며 별도 껍질·막을 만들지 않는다. 같은 물의 양 변형은 실제 평가 mesh 부피를 시작·중간·끝에서 검사한다. 물리 시뮬레이션으로 주장하지 않는다."
},
{
"id": "background-molecules",
"kind": "background",
"entity_id": "bg-molecules",
"prompt": "승인 08 r3의 BondPair 상태와 교육용 3D 고정 특징. 실제 MoleculeStudioAssets 자산. references/1.png의 단순 형태·정돈된 색·정보량을 교육용 3D로 해석한다. 크림색 단순 공간·부드러운 조명·선명한 형태를 반복한다. 산호색 O·미색 H·남색 내부 O–H 실선·금색 서로 다른 분자 H···O 점선·청색 물을 일관되게 사용한다. 상하단에는 제목·자막용 빈 공간을 두고 부분전하·한국어 주석은 나중 Remotion에서 검증해 합성한다. 3D에 글자를 굽지 않는다. 온전한 물 분자마다 O1H2·V자 형태·내부 O–H 실선 두 개를 유지한다. 각도·거리·힘 수치를 표시하지 않는다. 분자간 H···O 점선은 서로 다른 분자의 원자만 연결한다. 액체는 불규칙한 이웃·변하는 연결이고 모든 분자의 연결 수를 고정하지 않는다."
},
{
"id": "background-interface",
"kind": "background",
"entity_id": "bg-interface",
"prompt": "승인 08 r3의 Interface 상태와 교육용 3D 고정 특징. 실제 InterfaceAssets 자산. references/1.png의 단순 형태·정돈된 색·정보량을 교육용 3D로 해석한다. 크림색 단순 공간·부드러운 조명·선명한 형태를 반복한다. 산호색 O·미색 H·남색 내부 O–H 실선·금색 서로 다른 분자 H···O 점선·청색 물을 일관되게 사용한다. 상하단에는 제목·자막용 빈 공간을 두고 부분전하·한국어 주석은 나중 Remotion에서 검증해 합성한다. 3D에 글자를 굽지 않는다. 전체 분자·결합이 물 체적 안에 포함되고 앞면·윗면만 열린 절개 설명 모형이며 위 공기는 진공 주장이 아니다. 별도 고체 표면층·전체 아래 가속 화살표가 없다. 안쪽은 다양한 방향의 액체 이웃, 표면은 위쪽 액체 이웃이 적은 환경으로 구별한다. 강조고리는 설명 표식이고 물 껍질이 아니다."
},
{
"id": "background-comparison",
"kind": "background",
"entity_id": "bg-comparison",
"prompt": "승인 08 r3의 EqualVolumeComparison 상태와 교육용 3D 고정 특징. 실제 ComparisonStudioAssets 자산. references/1.png의 단순 형태·정돈된 색·정보량을 교육용 3D로 해석한다. 크림색 단순 공간·부드러운 조명·선명한 형태를 반복한다. 산호색 O·미색 H·남색 내부 O–H 실선·금색 서로 다른 분자 H···O 점선·청색 물을 일관되게 사용한다. 상하단에는 제목·자막용 빈 공간을 두고 부분전하·한국어 주석은 나중 Remotion에서 검증해 합성한다. 3D에 글자를 굽지 않는다. 동일 부피 비교와 외부 영향 조건 예시는 별도 Scene으로 구별한다. 바닥은 조건 예시에만 등장한다. 중력·접촉각·크기 경계·표면장력 수치를 임의로 표시하지 않는다."
},
{
"id": "object-molecule",
"kind": "object",
"entity_id": null,
"prompt": "승인 08 r3의 SingleMolecule 상태와 교육용 3D 고정 특징. 실제 MoleculeAssets 자산. O1H2·V자·내부 남색 실선과 금색 분자간 H···O의 구별을 유지한다."
},
{
"id": "object-droplet",
"kind": "object",
"entity_id": null,
"prompt": "승인 08 r3의 OpeningDroplet 상태와 교육용 3D 고정 특징. 실제 DropletAssets 자산. 매끈한 청색 닫힌 mesh와 같은 물의 양을 유지하며 별도 껍질이 없다."
}
],
"engine": "Blender", "engine": "Blender",
"render_profile": { "render_profile": {
"engine": "BLENDER_EEVEE", "engine": "BLENDER_EEVEE",
@@ -36,6 +75,99 @@
"seed": 0 "seed": 0
} }
}, },
"result": null "result": {
"scene_file": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"verified": true,
"sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535",
"blender_version": "5.2.2 LTS"
},
"items": [
{
"reference_id": "style-3d",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-290a15795637/frame-00001017.png",
"kind": "image",
"verified": true,
"duration_ms": null,
"width": 1080,
"height": 1920
},
"object_name": "StyleAssets"
},
{
"reference_id": "background-droplet",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-67b4f3e57dea/frame-00000000.png",
"kind": "image",
"verified": true,
"duration_ms": null,
"width": 1080,
"height": 1920
},
"object_name": "DropletStudioAssets"
},
{
"reference_id": "background-molecules",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-617e10851e75/frame-00001017.png",
"kind": "image",
"verified": true,
"duration_ms": null,
"width": 1080,
"height": 1920
},
"object_name": "MoleculeStudioAssets"
},
{
"reference_id": "background-interface",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/e527731ea65e24ba-261c9014707e/frame-00003050.png",
"kind": "image",
"verified": true,
"duration_ms": null,
"width": 1080,
"height": 1920
},
"object_name": "InterfaceAssets"
},
{
"reference_id": "background-comparison",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/54594cb9148b3dc4-147aec5db4d6/frame-00004076.png",
"kind": "image",
"verified": true,
"duration_ms": null,
"width": 1080,
"height": 1920
},
"object_name": "ComparisonStudioAssets"
},
{
"reference_id": "object-molecule",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/d0e3d93322a66d41-d7c0a66bfe07/frame-00000396.png",
"kind": "image",
"verified": true,
"duration_ms": null,
"width": 1080,
"height": 1920
},
"object_name": "MoleculeAssets"
},
{
"reference_id": "object-droplet",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-265621c5f8be/frame-00000000.png",
"kind": "image",
"verified": true,
"duration_ms": null,
"width": 1080,
"height": 1920
},
"object_name": "DropletAssets"
}
]
}
} }
} }
@@ -0,0 +1,280 @@
[
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"inspect",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"BondPair",
"--require-object",
"PairA",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe"
],
"exit_code": 0
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"render",
"--mode",
"production",
"--project",
"projects/water-hydrogen-bond-surface-tension/project.json",
"--artifact",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"--output-revision",
"2",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"BondPair",
"--require-object",
"StyleAssets",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--ffmpeg",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffmpeg.exe",
"--ffprobe",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffprobe.exe",
"--reference-id",
"style-3d",
"--frame",
"1017"
],
"exit_code": 0,
"evidence_path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-290a15795637/evidence.json",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"render",
"--mode",
"production",
"--project",
"projects/water-hydrogen-bond-surface-tension/project.json",
"--artifact",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"--output-revision",
"2",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"OpeningDroplet",
"--require-object",
"DropletStudioAssets",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--ffmpeg",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffmpeg.exe",
"--ffprobe",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffprobe.exe",
"--reference-id",
"background-droplet",
"--frame",
"0"
],
"exit_code": 0,
"evidence_path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-67b4f3e57dea/evidence.json",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"render",
"--mode",
"production",
"--project",
"projects/water-hydrogen-bond-surface-tension/project.json",
"--artifact",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"--output-revision",
"2",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"BondPair",
"--require-object",
"MoleculeStudioAssets",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--ffmpeg",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffmpeg.exe",
"--ffprobe",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffprobe.exe",
"--reference-id",
"background-molecules",
"--frame",
"1017"
],
"exit_code": 0,
"evidence_path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-617e10851e75/evidence.json",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"render",
"--mode",
"production",
"--project",
"projects/water-hydrogen-bond-surface-tension/project.json",
"--artifact",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"--output-revision",
"2",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"Interface",
"--require-object",
"InterfaceAssets",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--ffmpeg",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffmpeg.exe",
"--ffprobe",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffprobe.exe",
"--reference-id",
"background-interface",
"--frame",
"3050"
],
"exit_code": 0,
"evidence_path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/e527731ea65e24ba-261c9014707e/evidence.json",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"render",
"--mode",
"production",
"--project",
"projects/water-hydrogen-bond-surface-tension/project.json",
"--artifact",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"--output-revision",
"2",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"EqualVolumeComparison",
"--require-object",
"ComparisonStudioAssets",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--ffmpeg",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffmpeg.exe",
"--ffprobe",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffprobe.exe",
"--reference-id",
"background-comparison",
"--frame",
"4076"
],
"exit_code": 0,
"evidence_path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/54594cb9148b3dc4-147aec5db4d6/evidence.json",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"render",
"--mode",
"production",
"--project",
"projects/water-hydrogen-bond-surface-tension/project.json",
"--artifact",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"--output-revision",
"2",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"SingleMolecule",
"--require-object",
"MoleculeAssets",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--ffmpeg",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffmpeg.exe",
"--ffprobe",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffprobe.exe",
"--reference-id",
"object-molecule",
"--frame",
"396"
],
"exit_code": 0,
"evidence_path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/d0e3d93322a66d41-d7c0a66bfe07/evidence.json",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/run.py",
"render",
"--mode",
"production",
"--project",
"projects/water-hydrogen-bond-surface-tension/project.json",
"--artifact",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"--output-revision",
"2",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"--scene",
"OpeningDroplet",
"--require-object",
"DropletAssets",
"--blender",
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--ffmpeg",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffmpeg.exe",
"--ffprobe",
"C:\\git\\ShortVideoGenerator\\projects\\water-hydrogen-bond-surface-tension\\renderer\\node_modules\\@remotion\\compositor-win32-x64-msvc\\ffprobe.exe",
"--reference-id",
"object-droplet",
"--frame",
"0"
],
"exit_code": 0,
"evidence_path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-265621c5f8be/evidence.json",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
}
]
@@ -0,0 +1,98 @@
{
"result": "GREEN",
"stage_id": "10-references",
"source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535",
"requested": 7,
"actual": 7,
"render_profile": {
"engine": "BLENDER_EEVEE",
"device": "GPU",
"fps": 50,
"width": 1080,
"height": 1920,
"samples": 64,
"seed": 0
},
"actual_assets": [
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/artifact.json",
"sha256": "be7cb254d502872b1bbfff98d4a7b96fded23f456b810fa26e95974f5b1d1471"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/cli-commands.json",
"sha256": "50e9550e9406417c201d1ec0d4f4565f2ba0508cc5454de4d06be317f5ca37a7"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/generated-source/references.blend",
"sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/model-check.json",
"sha256": "3ae674967add04b8ed14852b39ea3e0133d78d1f1aad0123d54c5859fc9ce9ae"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/model-test-results.json",
"sha256": "4b87920122d9c92b0da106cf065e365a2e73ccdd6d851d88a576e5212480ab0e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/54594cb9148b3dc4-147aec5db4d6/evidence.json",
"sha256": "6ea5c3982691a2a452abd92003c93bd83b44f39872674b5650df5b5c77e8892c"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/54594cb9148b3dc4-147aec5db4d6/frame-00004076.png",
"sha256": "ea94068aa8cd186db146a2d7468971700fc0ed07532b6aa3a02b66791e10d939"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-290a15795637/evidence.json",
"sha256": "bae54e6dc7ddc91f3938b8f6c135333c9e30e14252fe717c3855c698a6802b5b"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-290a15795637/frame-00001017.png",
"sha256": "630024bc90fa92f5e4b979064c0da724ed2cda038110ed1caf422277cd7675e9"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-617e10851e75/evidence.json",
"sha256": "30623b58dd8f95592b476c2f0255f5c8e9fdf948d90870bfc6e0260b85547394"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/5d436e0aa3cc9859-617e10851e75/frame-00001017.png",
"sha256": "67291a652954e5b02b7f438a87c876d24f3c5c829f5dc3b8496dcfc5ee06851c"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-265621c5f8be/evidence.json",
"sha256": "4d25f85bf4ae89abcb1290308bf48e3d3be4f2ecacdf30400f02e47edb988a1d"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-265621c5f8be/frame-00000000.png",
"sha256": "f202b5a2ac214253a54f42e23260c7475aba76d29c52a40532a868a2ff0c2837"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-67b4f3e57dea/evidence.json",
"sha256": "994d38c8b49c1d2dd67e7a6e41d4034576eed8b01c9e4b459e09bbc05b487df5"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/9822f3b332c3a147-67b4f3e57dea/frame-00000000.png",
"sha256": "22e1104778f76a9162e9ccb4b66957483b2eeb38620bcd02fccd8f7468bc6ef1"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/d0e3d93322a66d41-d7c0a66bfe07/evidence.json",
"sha256": "029ece780957c2f04d7ae870101784fb88811eb4f60aa56f32a4eb9c1d8a104e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/d0e3d93322a66d41-d7c0a66bfe07/frame-00000396.png",
"sha256": "0107b8a52ae6876447f12a45670c2c3782e90c70876f87d2a4c051459628d946"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/e527731ea65e24ba-261c9014707e/evidence.json",
"sha256": "c0116157279ddd02094977bcdfd78d34f904d61eb4abb6453996e1163a912605"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/render-attempts/e527731ea65e24ba-261c9014707e/frame-00003050.png",
"sha256": "da7758cf70f81897ff38a65f1a7b73b900e88acab9d23dbc556ba496060c467b"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r2/review.md",
"sha256": "edfe8a20bc217378677bc2b8b1d0cef06dc46f2e8e7495743a272ce25c2befdf"
}
]
}
@@ -0,0 +1,145 @@
{
"result": "GREEN",
"negative_cases": 4,
"positive_cases": 1,
"executions": [
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\missing.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\invalid-result.json"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "원본 파일 없음",
"assertion_errors": [
"AssertionError: 원본 파일 없음"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\08-scenes\\r3\\generated-source\\preview.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\invalid-result.json"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "대표 Scene 누락/혼입",
"assertion_errors": [
"AssertionError: 대표 Scene 누락/혼입"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python-expr",
"import bpy; bpy.ops.wm.open_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\10-references\\\\r2\\\\generated-source\\\\references.blend'); bpy.data.scenes.remove(bpy.data.scenes['SingleMolecule']); bpy.ops.wm.save_as_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\10-references\\\\r2\\\\generated-source\\\\missing-scene.blend')"
],
"exit_code": 0,
"expected_exit_code": 0,
"matched_reason": null,
"assertion_errors": []
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\missing-scene.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\invalid-result.json"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "대표 Scene 누락/혼입",
"assertion_errors": [
"AssertionError: 대표 Scene 누락/혼입"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python-expr",
"import bpy; bpy.ops.wm.open_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\10-references\\\\r2\\\\generated-source\\\\references.blend'); bpy.data.objects['Liquid9_H1'].data.vertices[0].co.x=10; bpy.ops.wm.save_as_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\10-references\\\\r2\\\\generated-source\\\\outside-mesh.blend')"
],
"exit_code": 0,
"expected_exit_code": 0,
"matched_reason": null,
"assertion_errors": []
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\outside-mesh.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\invalid-result.json"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "물 영역 밖 또는 여백 부족",
"assertion_errors": [
"AssertionError: 물 영역 밖 또는 여백 부족: F2468 Liquid9_H1, 축 여백 [-1.628933072090149, -0.05580306053161621, 0.7182001715540887]"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\generated-source\\references.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\10-references\\r2\\model-check.json"
],
"exit_code": 0,
"expected_exit_code": 0,
"matched_reason": null,
"assertion_errors": []
}
]
}
@@ -0,0 +1,25 @@
# 정식 기준 자산 검토 · 10 r2
사용자의 수정본 승인으로 07 r1/08 r3/09 r3 scenes 경계를 충족한 뒤 제작했다. 기존 대본·제목·WAV·스타일 원본을 보존하고 실제 승인 원본을 읽기만 하여 모델·재질을 복사했다. build_preview.py는 실행하거나 import하지 않았다.
실제 기준 원본은 generated-source/references.blend이며 9개 계획 Scene과 모델을 포함한다. 승인 원본의 O1H2·V자·내부 남색 실선·금색 분자간 H···O·불규칙 이웃·실제 물 영역 포함·매끈한 청색 닫힌 방울을 유지한다. 새 비교 모형의 두 실제 mesh 부피는 1% 이내로 같다. 조건 예시의 작은 비접촉 방울과 큰 접촉 방울은 동일 부피 비교가 아니다. 고체 물 껍질·MD·유체 시뮬레이션·새 과학 수치를 주장하지 않는다.
style 1개, 09의 반복 배경 4개, 분자/물방울 object 2개를 빠짐없이 요청했다. 모든 결과의 object_name은 실제로 존재하는 고유 컬렉션 StyleAssets·DropletStudioAssets·MoleculeStudioAssets·InterfaceAssets·ComparisonStudioAssets·MoleculeAssets·DropletAssets다. 같은 모델의 실제 재사용을 허용하며 실물 없는 이름이나 가짜 character를 만들지 않았다.
7개 기준 PNG를 직접 열었다. 청색 방울·두 종류 결합·O1H2·실제 물 영역 안의 전체 원자와 결합·같은 부피 비교가 읽히고 주요 도형이 화면 밖으로 잘리지 않는다. references/1.png의 단순 형태·정돈된 색을 밝은 크림 배경·부드러운 명암의 교육용 3D로 해석하며 캠핑 소재·평면 복제를 적용하지 않는다. 글자·부분전하·과학 주석은 T5에서 실제 camera 좌표로 합성한다.
전체 705프레임 Interface 평가 mesh 정점을 물 경계 및 실제 네 절개 면의 투영 합집합·외부 윤곽과 대조했다. 중심을 유지한 정점 오류 fixture·원본 부재·Scene 누락·세 대표만 있는 원본은 실제 RED exit 1이며, 최신 정식 원본은 GREEN exit 0이다. 실제 AssertionError와 명령은 model-test-results.json, 포함·부피·9개 Scene 및 컷 앵커는 model-check.json에 있다. 처음 부재한 미사용 Studio_Cream 재질을 요청한 작성 시도는 exit 1이었으며 새 로컬 조건 바닥 재질을 만들어 해결했다. 제출 전 컬렉션을 고유하게 정리한 앞선 원본도 generated-source/history에 보존했고 해당 해시의 검사를 새 원본 검사로 재사용하지 않는다.
실제 7 PNG는 기존 tools/blender/run.py의 production 승인·입력·해시·Scene·객체·프레임·GPU 설정 검사와 probe·전체 디코드를 통과했다. 모든 결과는 EEVEE/GPU/50fps/1080×1920/samples64/seed0이다. exact argv·exit는 cli-commands.json, 원본과 PNG/evidence/작은 증거의 복사 경로·SHA는 manifest.json에 기록한다. .blend·PNG·로그·과거 시도는 좁은 .gitignore로 보존한다.
실행 명령은 저장소 루트 기준이며 모두 기존 설치를 사용했다.
```powershell
& 'C:/Program Files/Blender Foundation/Blender 5.2/blender.exe' --background --disable-autoexec --python-exit-code 1 --python projects/water-hydrogen-bond-surface-tension/blender/build_production.py -- --stage references
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_requests.py --stage 10-references
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_render.py --stage 10-references
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_test.py --stage references
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_check.py --stage 10-references
```
위 최종 실행은 exit 0이며 실패 fixture 네 개는 기대대로 exit 1이다. 10 완료 체크포인트를 총괄에게 제출하며 로컬 project.json의 완료 상태는 총괄만 기록한다. 그 기록 전 11 렌더를 실행하지 않는다. 최종 영상·자막·음성 검수는 아직 완료가 아니다.
@@ -0,0 +1,2 @@
/generated-source/
/render-attempts/
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,410 @@
{
"result": "GREEN",
"stage_id": "11-images",
"source_sha256": "026a3ba659fc292cf3e29fa07adeb2f0beb6bb7ab8b432be2e628d482fc66337",
"requested": 62,
"actual": 62,
"render_profile": {
"engine": "BLENDER_EEVEE",
"device": "GPU",
"fps": 50,
"width": 1080,
"height": 1920,
"samples": 64,
"seed": 0
},
"actual_assets": [
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/anchors.json",
"sha256": "5b9710406a47847b165531dadeb8a4c860c153c11790090f836abb066904c6bc"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/artifact.json",
"sha256": "158a491609ee9b8744fc2d29891e8c4c5f4d765ba61a5c0f20c0d7307a0b5cff"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/cli-commands.json",
"sha256": "c9c555bf5f437612fa0b4503fe52e5357bfe69d82343ea3345bb5e1307d5833e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/animation.blend",
"sha256": "026a3ba659fc292cf3e29fa07adeb2f0beb6bb7ab8b432be2e628d482fc66337"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/AnswerDroplet.png",
"sha256": "593edce67ff870657cdfd45d3a1fc5a2d5154415382b4eb7039d8278e39f5821"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/BondPair.png",
"sha256": "b9b71e1956a293e5ceac9d7a79b3db6fc46075781c3cbadaa3289cde24a8b091"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/DropletStretch.png",
"sha256": "e57d0b06c6fffa3dbb139e645a4ac88b0c94dc17221327814880f84eb095d1c1"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/EqualVolumeComparison.png",
"sha256": "995ec51d718ae6216f01b8b18cee6f3d1e8b02419eed4d9b028d43b4595d3325"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/Interface.png",
"sha256": "a89dca40d6fc7e847f45f1ee89b07f7486a64c0e6971a83d9ba376635d87b984"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/LiquidNetwork.png",
"sha256": "fb60daa328ff776c4bcdf2e1b359ae8c48fa1c42a6444e2deb8b377b23936ed4"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/OpeningDroplet.png",
"sha256": "bb9e16c67a6a5f68e42ab2cffad51c9486b863f0b193de5a15bcad91ab13ef77"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/ShapeConditions.png",
"sha256": "fc85da0311b55f04455ce4f95a1c27b514d47b445f0fdd814ee2d8ddb4f39dc1"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/SingleMolecule.png",
"sha256": "99d3b1f129d5700d66e3a80c58a132f870b4f824cd0788a38daa9e3952954c90"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/model-check.json",
"sha256": "c5764e0dfbceb3b1b9b493caf2dc2529113ca826115d63bc5c0eaffa186d2128"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/model-test-results.json",
"sha256": "a0fdf630df62be53f40e16affda61d6bda19c397d8989410d1dfa5b22bf73f77"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/0a5439e99503db73-2edbdc00ed00/evidence.json",
"sha256": "c6074544ff231382bcbad9cb65bb324f5dd48ed6b21bea60bfe1eb7fe6cf446e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/0a5439e99503db73-2edbdc00ed00/frame-00004369.png",
"sha256": "5c6c60bf67a227895df95beda44d3a46dac0f4d13b30e48c9a4b0412ba7d3d63"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/2a61073a43a9343a-e6a8d9de8345/evidence.json",
"sha256": "64ca1a1fe9ef3e491496ce47e5fb131d4fab4aa029a5377ee7cdcba83bf5fb32"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/2a61073a43a9343a-e6a8d9de8345/frame-00000396.png",
"sha256": "d49cfda3178d349d1b52c5d0438ac1c766be87f12745c102fcfe1728e11181a6"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/2c1886219e2aff34-e301d0f7af9c/evidence.json",
"sha256": "518be5e36aa080b1b7ba695cb583318352bdbd63dc81ed5e63851dc3178412a2"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/2c1886219e2aff34-e301d0f7af9c/frame-00001219.png",
"sha256": "b7ffc4d30ab3434d91091cb61ff6fe74815412f76153c6037aa5001b5a5db351"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/2e8bbb8c6594aaa0-def0a7bfe880/evidence.json",
"sha256": "43d9d3ebf7f8ab13ca4c8401fcf596a9e93d825a4aa70404f472b264df8e7ca3"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/2e8bbb8c6594aaa0-def0a7bfe880/frame-00004076.png",
"sha256": "bd048d3de93872157b23c249866af80418ef5478b5d0253073cf6bfe3820b27e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/3166410897b06779-2890ad208420/evidence.json",
"sha256": "83cdd62768455ed5337b91669d44fb0731f06cfa15476cb3536c6e6a40507086"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/3166410897b06779-2890ad208420/frame-00000904.png",
"sha256": "f61a9c5e3fe74fcdc44c89a05efbe20a03b31b997551b3e3144591e861222c8d"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/38b6d97f57adc763-26375bd218ee/evidence.json",
"sha256": "1e8c53e03ae284b7dc084a23fc740f43a6fab30172dcafb00dd1da73e33929b1"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/38b6d97f57adc763-26375bd218ee/frame-00004888.png",
"sha256": "484270b4edde9cc8b3ab0bdec0b6cf349c0e7fc5ca6a33030ef93f182a925dff"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/39c7256545a44cde-67df439ca819/evidence.json",
"sha256": "5204302b07b67db088bd77cfc33f055f287083c63fde4e51b97233f3a953d396"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/39c7256545a44cde-67df439ca819/frame-00004808.png",
"sha256": "19248592b56c0b6e599c748750780f58cabda479ac616c04a6035fb22578404b"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/4489d04927480c60-43d58b5c5c71/evidence.json",
"sha256": "ee024d31b99815974e716744bee1ffd2d48680fd0aca18c02634037eed51e482"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/4489d04927480c60-43d58b5c5c71/frame-00001424.png",
"sha256": "0e75527ab8ddf01d8037d70f7070527e02fe3c66ab8afa333fd8df8a66277ffd"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/471293238141725e-4977fea535c8/evidence.json",
"sha256": "548d17206439058ad2f60f53429499d6705c3962b86ee7706f79b5815d5c5fba"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/471293238141725e-4977fea535c8/frame-00001636.png",
"sha256": "e2bedcb333fb0a6842a140a070db019268d280a698a490acc89cd03c43d560db"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/4b5e080737fffb00-6ecb7aa35cfa/evidence.json",
"sha256": "a58e01eaa929f546d8c81215668a5aef5fb324e0af0bf44ef25562fd43a1122f"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/4b5e080737fffb00-6ecb7aa35cfa/frame-00002468.png",
"sha256": "c9996aad32de814b6790ea6e8750217b6cde0e95b96587b45032150e4ad09d96"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/5015db85374eefb2-5ee201f44b81/evidence.json",
"sha256": "1ba832e5ee357370cee71a5e14f2778bebea2b9766e61c2ea725c7d40ec6c831"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/5015db85374eefb2-5ee201f44b81/frame-00005228.png",
"sha256": "f7d92214d4552776a4fa414ff9fc322bb511d3eed1ae50cdb786664ff3df151f"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/59711eb98fd3b126-bcb9d2314188/evidence.json",
"sha256": "6a2dcb16ac9978abff7806f6874a7ef1d78bcfc8e312531916191be23fd4d66c"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/59711eb98fd3b126-bcb9d2314188/frame-00002468.png",
"sha256": "4778784dcbc51c7f9dc1a3833444916a0c1cb936db3d5b6c074b690f760bc029"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/620386e84717d28b-272fdd403a71/evidence.json",
"sha256": "2b450b7a8faf0df95837aad3ee6f3b4129ae4da04be4a8cdf667e739f238b5da"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/620386e84717d28b-272fdd403a71/frame-00001778.png",
"sha256": "c95f84c117fc8bb4ae698b4c49de79716fd0c4f5b8e6a6f1208883e8556e9aca"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/6b3b1e0dbfafbfd1-06ffb68103ab/evidence.json",
"sha256": "461d439c69779468253c88d02cd2f3ba975710be7c2de5bbf6c273baa0f78481"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/6b3b1e0dbfafbfd1-06ffb68103ab/frame-00001636.png",
"sha256": "9312e05f97f137ba35ceb099e7fb8b983102a037815c0d74845c323432cd290b"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/6b9f757d18ba16f5-c3bbc1880b93/evidence.json",
"sha256": "4b0c6ed0f187b770925c528537a38b7280f560b63246acca15153e31c1a26e34"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/6b9f757d18ba16f5-c3bbc1880b93/frame-00004369.png",
"sha256": "f4aeb7a900bb30036de7341cf68a81d4d8aed863829e7aedf5aa828debe42e64"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/703f74321d129a67-a039b447c8d2/evidence.json",
"sha256": "a2e64afe6cfdd7326fee7e4ed8932f46852ae9ef67cd28e7d5efe1ae3d4563f6"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/703f74321d129a67-a039b447c8d2/frame-00004576.png",
"sha256": "655617f930005a91eb7408e957bc7328471396849fda5c4c1a679fbb94038b3c"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/72202d8a02b9b518-571eb34d0e66/evidence.json",
"sha256": "f9719096b4da2191d9729aa981591e22c31bb52caf380fa5a7d7b012ef203ab6"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/72202d8a02b9b518-571eb34d0e66/frame-00001017.png",
"sha256": "61f8229f4eac2bb1dfea76815f9b9353eab15b3ad39dc94c80ded6343539a3c0"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/7a0c66381ab5d96e-e4e343b4e374/evidence.json",
"sha256": "e890ddc015d491c11c5f0f0077af626692f6899326053981d39af6a15dbc7de0"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/7a0c66381ab5d96e-e4e343b4e374/frame-00005528.png",
"sha256": "79de5a3bd3588cafebd429e789718d4ee164d6281073e70f5a3bb822405abe88"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/7cba7a1b0a5e8a54-0499de3b7908/evidence.json",
"sha256": "cf0d0875dd82f4525d38396d46100174432fd7be759399e93ac0ae0bdba430c0"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/7cba7a1b0a5e8a54-0499de3b7908/frame-00002604.png",
"sha256": "d76bdcfde6d86cd529c47fb4bbf9dc0704fc516dbb4209e14b62da9757dffc9b"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/81d347e0b695cd95-5df7cc4a2d7b/evidence.json",
"sha256": "d6dcb8429a63b475fcd8e619060542e915930b31cd57b152b13b137c6e42b3eb"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/81d347e0b695cd95-5df7cc4a2d7b/frame-00004888.png",
"sha256": "78f9e4ed438e03305cdbdecf98ecc8530cbf5a89481d6ec8bf7013c9983e279d"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/8b49aff39abda799-0e397238bedc/evidence.json",
"sha256": "625a789405f0562fc3fe7d6c2ad9fef7be27ecb28d93dbf8251523d47fbb5cd4"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/8b49aff39abda799-0e397238bedc/frame-00003050.png",
"sha256": "09c3132a786d6ad43ab81c8c6e485ee89133ca0ecfa495a8e3880f4f4100aad3"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/8e2d2251a9e7ca03-d10bf6213245/evidence.json",
"sha256": "969cb9fcf5e8807a00a5cb387b26426e0ca755458e3f63ed110afeaea6cca4ad"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/8e2d2251a9e7ca03-d10bf6213245/frame-00000396.png",
"sha256": "d0b2507fe34c584791989cd33d8def1d339665c4de842f466d247aae4b70427e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/8e82178d9d3ff3cf-fbd4c36f05d1/evidence.json",
"sha256": "11e437003dac00e2614abe6299c9c2fb59df7af885184a108f1b13c71856ed5a"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/8e82178d9d3ff3cf-fbd4c36f05d1/frame-00003372.png",
"sha256": "4c29fe79a6294f8865a736653c48ff4515087cd47fe62d348373cc99b20f9ef4"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/922c180b63e836c4-d8dbdd7e1c44/evidence.json",
"sha256": "b3127b0593e2384c74634ee32be7dc5310149f85cdbebab6b8e27e7aa1fc111b"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/922c180b63e836c4-d8dbdd7e1c44/frame-00003852.png",
"sha256": "b563fa3b8b8d28a2351fe7e69854aacb86993f7aca5fbf21b1cc0ebcbfd4980e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/93f11e712dc76070-b2bc719d1819/evidence.json",
"sha256": "3492c254ec561f93e8d84a19bf368ef5c6303508bf9b5a2a1470bab646dcbead"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/93f11e712dc76070-b2bc719d1819/frame-00003172.png",
"sha256": "0cf17f463e9bb184277a5e9e4ab6d3e9aa505154b343293b1c031c526e91c317"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/9e85bc64b1dc1135-2df583699642/evidence.json",
"sha256": "993d3078f02cdafc0b308b3f08d189d55f23b9dcad6cf2607038e45956b23596"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/9e85bc64b1dc1135-2df583699642/frame-00000219.png",
"sha256": "dc9ec98c91e861d13c10581f9474378decc83902db9cea60946eacb0f8832d31"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/a69295fb41b99c45-eee1faa8f23d/evidence.json",
"sha256": "4bcca0a6f7fa52c08b4bed48ba599af7ca753096150b525c562b200d66efd934"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/a69295fb41b99c45-eee1faa8f23d/frame-00002018.png",
"sha256": "2150cdbb0c99d4f39983210ecf321d90a30ca9b5988b50f5909c56d831379305"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/b2935a4b5f1fca41-d3dfb8ccf71f/evidence.json",
"sha256": "399f57cac7a36250ae0f722d1dc31c497873cc70e5568cee7d78d6e7590b7046"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/b2935a4b5f1fca41-d3dfb8ccf71f/frame-00000904.png",
"sha256": "1a43e3bd7dc8b0d46e3718a558ddeb4627b42c58e01fe5b5643d5a2d0a55280d"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/b2f2ef23502da43b-04ce6ae6c5a8/evidence.json",
"sha256": "872a3f773f5d7748c36d6aee1191938ebbfccb00e89a03d66e740ed11e3b7fcc"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/b2f2ef23502da43b-04ce6ae6c5a8/frame-00003172.png",
"sha256": "4930354a34801299255fe52f7a30f238ad8596ec01efcb5e713ec39f25c297c8"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/bded3512eda34dbd-f3cd7cb0e2ea/evidence.json",
"sha256": "eb15f02ed0e7952b4de709fb1b613487fe18419e9c1818edae63b6acee3f2194"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/bded3512eda34dbd-f3cd7cb0e2ea/frame-00003656.png",
"sha256": "df81d8e7bd028feab4eb650686cbfc8f43f58fa589db0f2d1ffc63d434bcbfac"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/be4f823405bb2411-7cbc1ccf0414/evidence.json",
"sha256": "244c1df4dab8c6517c689f7b9e6a78fc75619e901f617df9742da26799662091"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/be4f823405bb2411-7cbc1ccf0414/frame-00005368.png",
"sha256": "0b04437d39f3e417a3ffcc2772ff56d10b6f046c20c22e903b6b664b1cd35dff"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/bea548ef123714ec-3deed141e990/evidence.json",
"sha256": "65aee42b785b769436ac93525876853c52e208b6f61bb5b7de82f8de06cecaa5"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/bea548ef123714ec-3deed141e990/frame-00000739.png",
"sha256": "ff515fcd3bb0d1f33a0a0ac06238a65370662d86952921784c8409211587e626"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/c206b0d65cdf01bf-bacd8af3fcac/evidence.json",
"sha256": "a96f287d69968a73fa285a43e46598f641b93638f94c230104c44e170d3fb168"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/c206b0d65cdf01bf-bacd8af3fcac/frame-00004148.png",
"sha256": "3d0df0d2150a695a1496e4698dc4c737d1bc7178d0a884e95039307025b53bac"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/d66f9e130d7705d2-05dea693d5be/evidence.json",
"sha256": "1fa539811b90dfdc5950eb0d0bf5d2816e8d686eb6982c9cce2e8bea3702a32c"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/d66f9e130d7705d2-05dea693d5be/frame-00002256.png",
"sha256": "bc1a48ca041bed029a4f16bcef53e1ac8c777ae64b046d4b626098103cf5e765"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/d698c89725adcadd-7faf2e39d7d0/evidence.json",
"sha256": "200a15c9a2a67399c5fb2436337a92dd2edeeedeade4d763587360cd1dc2076f"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/d698c89725adcadd-7faf2e39d7d0/frame-00002806.png",
"sha256": "7ec69b0e575c743262d52f9445ded7502afb1cad6842f97c1faec97c1ed6a0ce"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/e178fb86cda83b76-b9f8dbdcd666/evidence.json",
"sha256": "693a3cd9c40e22e101339d4d2f8196a7a81771cc4ee3509ff4ca01b3d3f7c36e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/e178fb86cda83b76-b9f8dbdcd666/frame-00003541.png",
"sha256": "a96a545a1c7523aaab715d712351959659480ba509dd79ed798eb5b22098d98c"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/e198b4d26867192e-2f434f6d95c0/evidence.json",
"sha256": "5783963c6a2492a7e6833fb3253cff0e64202692a76346d9517f22cb5a8cbede"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/e198b4d26867192e-2f434f6d95c0/frame-00000000.png",
"sha256": "86629b898bd387c49f99326fafd0ecc86d00d6ae92003f534e11de1884c89870"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/e2e74767ab2fa109-7bfefaafc30f/evidence.json",
"sha256": "1d86d9c91e8d54b1e9fb9087441a2f02b15f8d0d8a5fc70b3ca1b40b4e6a2608"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/e2e74767ab2fa109-7bfefaafc30f/frame-00004076.png",
"sha256": "ddb14c5a827ba7075624e73707d994f897639d31b6f7342c87eab7b9f643f3aa"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/ee3bb2af99826828-af5e717abc81/evidence.json",
"sha256": "4651b16420cdb2025326a49b820d0bcd93876ae02ed90631439c61e8d8c6437e"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/ee3bb2af99826828-af5e717abc81/frame-00005099.png",
"sha256": "d043583466d487b66165f815c9ae74f7037b2adc218490ef1ea0fe84b0f66108"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/ff4dc3aa45cdf983-b7a5f46fdff9/evidence.json",
"sha256": "b5303e0f405e6309e1fa90fba9a366c60e077a427a9dd9d187d62eccb151ae85"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/render-attempts/ff4dc3aa45cdf983-b7a5f46fdff9/frame-00000596.png",
"sha256": "c3d6caed751853ca822fdd47d77d7c2ddc06a83d79dbbda8bdd0d3734eb9cea7"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/reuse-check.json",
"sha256": "121af2fafa72157da2f744bd5f2ff5af8d0e6cc62d8a90b112b62df07e8d300a"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/review.md",
"sha256": "95254f55bc5bf70a9aeca8fd008c062416d3a9341466b06332ce88d8517f3344"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/visual-check.json",
"sha256": "3b3a4280dd75276e5e4336e4b3efb2aadc78df21bc260a6ce4188a24c1a02fb1"
}
]
}
@@ -0,0 +1,150 @@
{
"result": "GREEN",
"negative_cases": 4,
"positive_cases": 1,
"executions": [
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\missing.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\invalid-result.json",
"--animated"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "원본 파일 없음",
"assertion_errors": [
"AssertionError: 원본 파일 없음"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\08-scenes\\r3\\generated-source\\preview.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\invalid-result.json",
"--animated"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "대표 Scene 누락/혼입",
"assertion_errors": [
"AssertionError: 대표 Scene 누락/혼입"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python-expr",
"import bpy; bpy.ops.wm.open_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\11-images\\\\r2\\\\generated-source\\\\animation.blend'); bpy.data.scenes.remove(bpy.data.scenes['SingleMolecule']); bpy.ops.wm.save_as_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\11-images\\\\r2\\\\generated-source\\\\missing-scene.blend')"
],
"exit_code": 0,
"expected_exit_code": 0,
"matched_reason": null,
"assertion_errors": []
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\missing-scene.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\invalid-result.json",
"--animated"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "대표 Scene 누락/혼입",
"assertion_errors": [
"AssertionError: 대표 Scene 누락/혼입"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python-expr",
"import bpy; bpy.ops.wm.open_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\11-images\\\\r2\\\\generated-source\\\\animation.blend'); bpy.data.objects['Liquid9_H1'].data.vertices[0].co.x=10; bpy.ops.wm.save_as_mainfile(filepath='C:\\\\git\\\\ShortVideoGenerator\\\\.dryforge\\\\worktrees\\\\T4\\\\projects\\\\water-hydrogen-bond-surface-tension\\\\stages\\\\11-images\\\\r2\\\\generated-source\\\\outside-mesh.blend')"
],
"exit_code": 0,
"expected_exit_code": 0,
"matched_reason": null,
"assertion_errors": []
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\outside-mesh.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\invalid-result.json",
"--animated"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "물 영역 밖 또는 여백 부족",
"assertion_errors": [
"AssertionError: 물 영역 밖 또는 여백 부족: F2468 Liquid9_H1, 축 여백 [-1.628933072090149, -0.05580306053161621, 0.7182001715540887]"
]
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\blender\\verify_production.py",
"--",
"--source",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\generated-source\\animation.blend",
"--out",
"C:\\git\\ShortVideoGenerator\\.dryforge\\worktrees\\T4\\projects\\water-hydrogen-bond-surface-tension\\stages\\11-images\\r2\\model-check.json",
"--animated"
],
"exit_code": 0,
"expected_exit_code": 0,
"matched_reason": null,
"assertion_errors": []
}
]
}
@@ -0,0 +1,19 @@
{
"result": "GREEN",
"reference_source_sha256": "c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535",
"animation_source_sha256": "026a3ba659fc292cf3e29fa07adeb2f0beb6bb7ab8b432be2e628d482fc66337",
"actual_reopened_sources": 2,
"mesh_objects": 282,
"materials": 8,
"scenes": 9,
"matching_fields": [
"local_mesh_vertices",
"polygons",
"material_slots",
"material_nodes",
"collection_members",
"scene_members",
"actual_camera_name",
"orthographic_scale"
]
}
@@ -0,0 +1,31 @@
# 정식 끝점·애니메이션 검토 · 11 r2
사용자가 승인한 07 r1/08 r3/09 r3의 31개 장면과 30개 연결을 구현했다. 총괄이 실제 10 r2 원본·7 PNG·검토 문서·manifest를 확인하고 로컬 10 완료를 기록한 뒤 11 렌더를 시작했다. 대본·제목·실측 컷·WAV·기존 결과는 변경하지 않았다.
실제 원본은 [animation.blend](generated-source/animation.blend)이며 SHA는 `026a3ba659fc292cf3e29fa07adeb2f0beb6bb7ab8b432be2e628d482fc66337`이다. 실제 10 원본 `c5b642b9f4548216329ccf6a8c8af037a9ef6b589bb80934437d3f1056571535`를 다시 열어 작성했다. 두 파일을 따로 재열어 mesh 282개의 실제 정점·면·재질·컬렉션·Scene 구성·카메라를 대조한 [reuse-check.json](reuse-check.json)이 기준 재사용을 확인한다. 승인 시제품 파일은 읽기만 했으며 새 결과로 위장하지 않았다.
OpeningDroplet·SingleMolecule·BondPair·LiquidNetwork·Interface·DropletStretch·EqualVolumeComparison·ShapeConditions·AnswerDroplet의 9개 Scene에 62개의 고유 frame ID를 요청했다. 실제 PNG는 40개다. continuous 연결 22개는 동일 Scene·논리 경계·실제 PNG 파일을 공유하고 서로 다른 ID를 유지한다. cut 연결 8개는 서로 다른 Scene으로 시점을 바꾼다. Blender 프레임은 논리 프레임+1이며 끝 PNG는 exclusive 경계다. 12의 MP4 마지막 표시 프레임은 그보다 1프레임/20ms 앞서야 한다.
9개 장면의 모든 고유 끝점 PNG를 실제 파일에서 각 360×640 패널로 만들어 직접 열었다. [visual-check.json](visual-check.json)에 실제 모바일 비교 화면·해시·장면별 관찰을 남겼다. O1H2의 V자·내부 남색 실선과 분자간 금색 점선·불규칙한 두 연결 쌍 교체·안쪽에서 표면으로 이동하는 강조·같은 부피의 길어짐/유지/구형 복귀가 읽힌다. Interface의 전체 원자와 결합은 파란 영역 안에 있고 늘어난 방울과 조건 모형도 화면 밖으로 잘리지 않는다. 거시 방울과 답 회수 분자 개념도는 공간적으로 분리된다. 정지 유지 경계는 승인된 설명 상태다.
[model-check.json](model-check.json)은 실제 9개 Scene·O1H2·두 결합 종류·외부 링크 부재·Interface 전체 705프레임의 11,747,388개 평가 정점·실제 네 절개 면 투영을 검사했다. 최소 축 여백은 약 0.325/0.454/0.115이고 최소 투영 여백은 약 47.94px다. 앞면과 윗면이 열린 물 표시는 설명용 절개이며 고체 막·벽이라는 주장이 아니다. 공기 공간은 진공이나 수증기 부재를 뜻하지 않는다.
실제 닫힌 방울의 변형·유지·복귀 전체 905프레임에서 상대 부피 변화는 `1.945e-7`로 1% 이내다. c21 끝에서 구형으로 복귀했다. 비교 두 mesh 부피는 약 3.431466으로 1% 이내로 같다. 이 값은 구현 검증용 단위이며 과학 수치로 화면에 표시하지 않는다. 별도 조건 모형의 작은 비접촉/큰 접촉 방울은 동일 부피 비교가 아니다. Network 전체 833프레임에서 실제 H···O 대상·평가 위치와 두 연결 쌍의 교체를 확인했다. 모델은 설명용 개념도이며 MD·유체 시뮬레이션이 아니다.
원본 부재·정식 Scene 누락·세 대표만 있는 원본·중심은 내부에 유지한 채 실제 원자 정점만 물 밖으로 옮긴 fixture는 실제 RED exit 1이다. 수정 정식 원본은 GREEN exit 0이다. 정확한 명령·종료 코드·AssertionError는 [model-test-results.json](model-test-results.json)에 있다.
첫 PNG 뒤 c01-end 실행은 부분 결과에서 아직 없는 frame ID를 찾는 범용 실행기 오류로 exit 1이었다. 당시 성공 파일·실패 명령을 보존했다. 총괄이 별도 담당의 회귀 RED→GREEN 최소 수정 `5ee2b2e`를 연결한 뒤 같은 원본으로 재개하여 나머지 요청이 모두 exit 0이었다. 담당 소유권 밖의 tools를 이 작업에서 수정하지 않았다. 실제 실행 명령·종료 코드는 [cli-commands.json](cli-commands.json)에 누적했다.
[anchors.json](anchors.json)은 실제 1080×1920 카메라의 전체 31컷 시작·중간·경계 target/bbox와 2,144개의 필요한 좌표 키를 제공한다. 원자 옆 부분전하, 안쪽/표면/공기, 같은 부피 좌우, 조건 예시, 답의 거시 방울/분자 위치를 포함한다. 좌표는 화면 왼쪽 위 기준 픽셀이며 과거 945×1680 색상 추정을 사용하지 않았다. 글자는 3D에 굽지 않는다. label_pixel은 실제 mesh 위 32px의 배치 시작점으로, T5가 글자 크기·충돌·제목/자막 여백을 실제 합성에서 검수한다.
```powershell
& 'C:/Program Files/Blender Foundation/Blender 5.2/blender.exe' --background --disable-autoexec --python-exit-code 1 --python projects/water-hydrogen-bond-surface-tension/blender/build_production.py -- --stage images
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_test.py --stage images
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_render.py --stage 11-images
& 'C:/Program Files/Blender Foundation/Blender 5.2/blender.exe' --background --disable-autoexec --python-exit-code 1 --python projects/water-hydrogen-bond-surface-tension/blender/export_anchors.py -- --source projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/animation.blend --out projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/anchors.json
& 'C:/Program Files/Blender Foundation/Blender 5.2/blender.exe' --background --disable-autoexec --python-exit-code 1 --python projects/water-hydrogen-bond-surface-tension/blender/production_reuse.py
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_views.py --stage 11-images
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_check.py --stage 11-images
```
최종 실행은 모두 exit 0이다. 원본·PNG·증거·주석 JSON·실제 관찰 화면의 복사 경로와 SHA는 [manifest.json](manifest.json)에 있다. 원본·렌더·캐시는 해당 버전의 좁은 .gitignore로 보존한다. 총괄에게 11 완료 체크포인트를 제출하며 총괄의 로컬 완료 기록 전 12 렌더를 시작하지 않는다. 최종 합성·음성·자막·사용자 final 승인은 아직 완료가 아니다.
@@ -0,0 +1,100 @@
{
"result": "GREEN",
"source_sha256": "026a3ba659fc292cf3e29fa07adeb2f0beb6bb7ab8b432be2e628d482fc66337",
"width": 1080,
"height": 1920,
"panel_width": 360,
"panel_height": 640,
"views": [
{
"scene_name": "OpeningDroplet",
"unique_frames": 3,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/OpeningDroplet.png",
"sha256": "bb9e16c67a6a5f68e42ab2cffad51c9486b863f0b193de5a15bcad91ab13ef77"
},
"visual_review": "checked",
"observation": "청색 닫힌 방울을 유지하며 위아래 합성 여백이 남는다. 확대 위치 주석은 별도 합성 앵커다."
},
{
"scene_name": "SingleMolecule",
"unique_frames": 4,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/SingleMolecule.png",
"sha256": "99d3b1f129d5700d66e3a80c58a132f870b4f824cd0788a38daa9e3952954c90"
},
"visual_review": "checked",
"observation": "산호색 O 하나·미색 H 둘의 V자와 남색 내부 실선 두 개가 읽힌다. 부분전하 글자는 굽지 않았다."
},
{
"scene_name": "BondPair",
"unique_frames": 5,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/BondPair.png",
"sha256": "b9b71e1956a293e5ceac9d7a79b3db6fc46075781c3cbadaa3289cde24a8b091"
},
"visual_review": "checked",
"observation": "c07에서 이웃 분자가 가까워지고 금색 점선이 짧아지며 두 분자의 내부 실선은 유지된다. 이후 유지 상태도 정상이다."
},
{
"scene_name": "LiquidNetwork",
"unique_frames": 5,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/LiquidNetwork.png",
"sha256": "fb60daa328ff776c4bcdf2e1b359ae8c48fa1c42a6444e2deb8b377b23936ed4"
},
"visual_review": "checked",
"observation": "불규칙한 열 분자의 연결 수가 다르며 c11·c12 경계에서 금색 연결 쌍이 바뀐다. 원자는 온전한 분자에 남는다."
},
{
"scene_name": "Interface",
"unique_frames": 5,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/Interface.png",
"sha256": "a89dca40d6fc7e847f45f1ee89b07f7486a64c0e6971a83d9ba376635d87b984"
},
"visual_review": "checked",
"observation": "모든 원자·실선·점선이 파란 물 표시 영역 안에 보인다. 안쪽 강조가 커진 뒤 표면으로 옮겨지며 공기 방향의 액체 이웃이 적다."
},
{
"scene_name": "DropletStretch",
"unique_frames": 6,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/DropletStretch.png",
"sha256": "e57d0b06c6fffa3dbb139e645a4ac88b0c94dc17221327814880f84eb095d1c1"
},
"visual_review": "checked",
"observation": "매끈한 닫힌 방울이 길어지고 유지된 다음 c21 경계에서 둥근 형태로 복귀한다. 늘어난 끝도 화면 안에 있다."
},
{
"scene_name": "EqualVolumeComparison",
"unique_frames": 3,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/EqualVolumeComparison.png",
"sha256": "995ec51d718ae6216f01b8b18cee6f3d1e8b02419eed4d9b028d43b4595d3325"
},
"visual_review": "checked",
"observation": "같은 부피의 왼쪽 긴 방울과 오른쪽 둥근 방울을 구별할 수 있고 두 실루엣이 화면 안에 있다."
},
{
"scene_name": "ShapeConditions",
"unique_frames": 4,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/ShapeConditions.png",
"sha256": "fc85da0311b55f04455ce4f95a1c27b514d47b445f0fdd814ee2d8ddb4f39dc1"
},
"visual_review": "checked",
"observation": "왼쪽 작은 비접촉 방울과 오른쪽 큰 바닥 접촉 방울이 구별된다. 항상 완전한 구라는 표현이 아니다."
},
{
"scene_name": "AnswerDroplet",
"unique_frames": 5,
"sheet": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/inspection/AnswerDroplet.png",
"sha256": "593edce67ff870657cdfd45d3a1fc5a2d5154415382b4eb7039d8278e39f5821"
},
"visual_review": "checked",
"observation": "거시 방울과 위쪽 분자 개념도가 분리되며 별도 껍질이 없다. 온전한 분자와 내부/분자간 결합을 함께 회수한다."
}
]
}
@@ -0,0 +1,2 @@
/generated-source/
/render-attempts/
@@ -19,7 +19,318 @@
], ],
"payload": { "payload": {
"request": { "request": {
"clips": [], "clips": [
{
"cut_id": "c01",
"start_frame_id": "c01-start",
"end_frame_id": "c01-end",
"duration_ms": 4380,
"motion_prompt": "작은 청색 물방울의 둥근 형태를 보여주며 그 모양이 생기는 이유를 질문한다. Scene=OpeningDroplet; F1. 작은 청색 물방울이 크림색 공간 중앙에 구형에 가깝게 떠 있고 부드러운 하이라이트가 표면의 입체감을 보여준다. → Scene=OpeningDroplet; F220. 작은 물방울을 유지하고 오른쪽 위에 분자 확대의 위치를 표시하는 합성 주석 앵커를 제시한다. 정면보다 약간 위의 3D 전경, 중심 물방울; 다음 분자 확대는 새 Scene에서 제시한다.",
"scene_name": "OpeningDroplet",
"start_frame": 0,
"end_frame": 219
},
{
"cut_id": "c02",
"start_frame_id": "c02-start",
"end_frame_id": "c02-end",
"duration_ms": 3540,
"motion_prompt": "거시적인 물방울에서 물속 분자 수준의 설명으로 관심을 옮긴다. Scene=OpeningDroplet; F220. 작은 물방울을 유지하고 오른쪽 위에 분자 확대의 위치를 표시하는 합성 주석 앵커를 제시한다. → Scene=OpeningDroplet; F397. 물방울과 확대 위치를 유지한 채 분자 설명으로 넘어갈 준비 상태다. 정면보다 약간 위의 3D 전경, 중심 물방울; 다음 분자 확대는 새 Scene에서 제시한다.",
"scene_name": "OpeningDroplet",
"start_frame": 219,
"end_frame": 396
},
{
"cut_id": "c03",
"start_frame_id": "c03-start",
"end_frame_id": "c03-end",
"duration_ms": 4000,
"motion_prompt": "물 분자가 산소 하나와 수소 둘로 이루어진 온전한 V자 구조임을 보여준다. Scene=SingleMolecule; F397. 산호색 O 구 하나·미색 H 구 둘·남색 O–H 원통 두 개의 V자 3D 분자; 주석은 합성용 앵커만 마련한다. → Scene=SingleMolecule; F597. 온전한 V자 분자가 유지되고 산소 주석 앵커가 강조 위치에 있다. 3D 분자 근접, 약간 비스듬한 고정 카메라; 산소와 두 수소가 모두 가려지지 않는다.",
"scene_name": "SingleMolecule",
"start_frame": 396,
"end_frame": 596
},
{
"cut_id": "c04",
"start_frame_id": "c04-start",
"end_frame_id": "c04-end",
"duration_ms": 2860,
"motion_prompt": "산소 쪽의 부분 음전하를 강조하며 물 분자 전체가 음이온인 것으로 표현하지 않는다. Scene=SingleMolecule; F597. 온전한 V자 분자가 유지되고 산소 주석 앵커가 강조 위치에 있다. → Scene=SingleMolecule; F740. 분자 전체 중성을 유지하며 Oδ− 합성 주석 위치만 강조한다. 3D 분자 근접, 약간 비스듬한 고정 카메라; 산소와 두 수소가 모두 가려지지 않는다.",
"scene_name": "SingleMolecule",
"start_frame": 596,
"end_frame": 739
},
{
"cut_id": "c05",
"start_frame_id": "c05-start",
"end_frame_id": "c05-end",
"duration_ms": 3300,
"motion_prompt": "수소 쪽의 부분 양전하를 산소 쪽과 구별하며 분자 전체의 중성은 유지한다. Scene=SingleMolecule; F740. 분자 전체 중성을 유지하며 Oδ− 합성 주석 위치만 강조한다. → Scene=SingleMolecule; F905. Oδ− 및 Hδ+ 합성 앵커가 모두 마련되며 원자 분리·전체 이온 표시는 없다. 3D 분자 근접, 약간 비스듬한 고정 카메라; 산소와 두 수소가 모두 가려지지 않는다.",
"scene_name": "SingleMolecule",
"start_frame": 739,
"end_frame": 904
},
{
"cut_id": "c06",
"start_frame_id": "c06-start",
"end_frame_id": "c06-end",
"duration_ms": 2260,
"motion_prompt": "한 물 분자의 수소와 이웃 물 분자의 산소가 끌림의 양 끝이라는 관계를 보여준다. Scene=BondPair; F905. PairA=(-1.8,0,-0.3), PairB=(1.65,0.15,0.45). 두 온전한 V자 분자의 내부 남색 실선과 서로 다른 분자 H···O 금색 분절 원통을 구별한다. → Scene=BondPair; F1018. Blender F1018에서 PairA=(-1.8,0,-0.3), PairB=(1.65,0.15,0.45); H···O 점선의 양 끝이 실제 H/O 위치에 대응한다. 실제 대표 Scene BondPair. 정면 위쪽 직교 카메라 (0,-12,6), ortho_scale=11.5; 내부 실선과 분자간 점선을 한 화면에 읽는다.",
"scene_name": "BondPair",
"start_frame": 904,
"end_frame": 1017
},
{
"cut_id": "c07",
"start_frame_id": "c07-start",
"end_frame_id": "c07-end",
"duration_ms": 4040,
"motion_prompt": "두 온전한 분자가 가까워지며 분자 사이 H···O 점선 길이가 줄고 내부 O–H 실선은 유지된다. Scene=BondPair; F1018. Blender F1018에서 PairA=(-1.8,0,-0.3), PairB=(1.65,0.15,0.45); H···O 점선의 양 끝이 실제 H/O 위치에 대응한다. → Scene=BondPair; F1220. Blender F1220에서 PairB=(0.85,0.15,0.45)로 가까워져 H···O 점선 길이가 줄었다. 내부 O–H 실선 두 개씩은 그대로다. 실제 대표 Scene BondPair. 정면 위쪽 직교 카메라 (0,-12,6), ortho_scale=11.5; 내부 실선과 분자간 점선을 한 화면에 읽는다.",
"scene_name": "BondPair",
"start_frame": 1017,
"end_frame": 1219
},
{
"cut_id": "c08",
"start_frame_id": "c08-start",
"end_frame_id": "c08-end",
"duration_ms": 4100,
"motion_prompt": "서로 다른 분자의 H···O 사이 끌림에 수소결합이라는 이름을 연결한다. Scene=BondPair; F1220. Blender F1220에서 PairB=(0.85,0.15,0.45)로 가까워져 H···O 점선 길이가 줄었다. 내부 O–H 실선 두 개씩은 그대로다. → Scene=BondPair; F1425. 같은 두 V자 분자·같은 카메라·금색 H···O 점선 유지; 수소결합 명칭은 합성 단계에서 별도로 배치한다. 실제 대표 Scene BondPair. 정면 위쪽 직교 카메라 (0,-12,6), ortho_scale=11.5; 내부 실선과 분자간 점선을 한 화면에 읽는다.",
"scene_name": "BondPair",
"start_frame": 1219,
"end_frame": 1424
},
{
"cut_id": "c09",
"start_frame_id": "c09-start",
"end_frame_id": "c09-end",
"duration_ms": 4240,
"motion_prompt": "한 분자 내부의 O–H 실선과 분자 사이의 H···O 점선을 동시에 보여 두 결합을 구별한다. Scene=BondPair; F1425. 같은 두 V자 분자·같은 카메라·금색 H···O 점선 유지; 수소결합 명칭은 합성 단계에서 별도로 배치한다. → Scene=BondPair; F1637. 같은 두 분자의 남색 내부 O–H 실선과 금색 분자간 H···O 점선이 동시에 보이고 모든 원자는 원래 분자에 남아 있다. 실제 대표 Scene BondPair. 정면 위쪽 직교 카메라 (0,-12,6), ortho_scale=11.5; 내부 실선과 분자간 점선을 한 화면에 읽는다.",
"scene_name": "BondPair",
"start_frame": 1424,
"end_frame": 1636
},
{
"cut_id": "c10",
"start_frame_id": "c10-start",
"end_frame_id": "c10-end",
"duration_ms": 2840,
"motion_prompt": "불규칙한 액체 분자 집단으로 범위를 넓혀 수소결합이 영구적인 끈은 아님을 설명한다. Scene=LiquidNetwork; F1637. 여러 온전한 V자 분자가 불규칙한 위치·방향에 있고 서로 다른 수의 금색 분자간 연결이 보인다. → Scene=LiquidNetwork; F1779. 분자 전체가 소폭 이동하는 동안 일부 H···O 점선 연결이 사라질 준비 상태; 내부 실선은 유지된다. 불규칙한 3D 액체 분자 집단 근접. 고정 격자 대신 깊이·방향을 달리하고 카메라는 유지한다.",
"scene_name": "LiquidNetwork",
"start_frame": 1636,
"end_frame": 1778
},
{
"cut_id": "c11",
"start_frame_id": "c11-start",
"end_frame_id": "c11-end",
"duration_ms": 4800,
"motion_prompt": "온전한 분자들이 소폭 움직이며 일부 이웃 사이 연결이 사라지고 새 연결이 생기는 과정을 보여준다. Scene=LiquidNetwork; F1779. 분자 전체가 소폭 이동하는 동안 일부 H···O 점선 연결이 사라질 준비 상태; 내부 실선은 유지된다. → Scene=LiquidNetwork; F2019. 첫 H···O 점선 쌍이 사라지고 다른 분자 쌍의 점선이 나타난다. 원자 분리나 화학 반응은 없다. 불규칙한 3D 액체 분자 집단 근접. 고정 격자 대신 깊이·방향을 달리하고 카메라는 유지한다.",
"scene_name": "LiquidNetwork",
"start_frame": 1778,
"end_frame": 2018
},
{
"cut_id": "c12",
"start_frame_id": "c12-start",
"end_frame_id": "c12-end",
"duration_ms": 4760,
"motion_prompt": "수소결합 연결이 반복해서 바뀌어 액체의 이웃 관계가 고정된 격자가 아님을 보여준다. Scene=LiquidNetwork; F2019. 첫 H···O 점선 쌍이 사라지고 다른 분자 쌍의 점선이 나타난다. 원자 분리나 화학 반응은 없다. → Scene=LiquidNetwork; F2257. 새 이웃과의 H···O 연결이 보이며 분자마다 연결 수·방향이 다르다. 영구 끈·정지 격자는 없다. 불규칙한 3D 액체 분자 집단 근접. 고정 격자 대신 깊이·방향을 달리하고 카메라는 유지한다.",
"scene_name": "LiquidNetwork",
"start_frame": 2018,
"end_frame": 2256
},
{
"cut_id": "c13",
"start_frame_id": "c13-start",
"end_frame_id": "c13-end",
"duration_ms": 4240,
"motion_prompt": "많은 분자 사이의 끌림이 물끼리 함께 뭉치려는 응집 성질에 기여함을 연결한다. Scene=LiquidNetwork; F2257. 새 이웃과의 H···O 연결이 보이며 분자마다 연결 수·방향이 다르다. 영구 끈·정지 격자는 없다. → Scene=LiquidNetwork; F2469. 불규칙한 집단을 유지하며 분자 사이의 응집이 거시적 물의 뭉침에 기여함을 다음 경계 모형으로 연결한다. 불규칙한 3D 액체 분자 집단 근접. 고정 격자 대신 깊이·방향을 달리하고 카메라는 유지한다.",
"scene_name": "LiquidNetwork",
"start_frame": 2256,
"end_frame": 2468
},
{
"cut_id": "c14",
"start_frame_id": "c14-start",
"end_frame_id": "c14-end",
"duration_ms": 2720,
"motion_prompt": "청색 물 체적과 위쪽 공기가 만나는 경계를 절개 설명 모형으로 제시한다. Scene=Interface; F2469. 아래쪽 청색 물 체적과 위쪽 공기 공간이 보이며 실제 청색 물 영역 안에 불규칙한 물 분자가 모두 포함된다. 공기 이름은 추후 합성한다. → Scene=Interface; F2605. 물-공기 경계를 유지하고 안쪽 Liquid4 주변의 다양한 이웃이 보이도록 근접 강조를 시작한다. 실제 대표 Scene Interface. 실제 물 경계 x=[-2.85,2.85], y=[-0.85,1.35], z=[-3.15,0.80] 안에 전체 원자·결합을 포함하고 앞면·윗면을 연 절개 설명 모형, 카메라 (0,-14,6), ortho_scale=13. 색 경계에 고체막을 추가하지 않는다.",
"scene_name": "Interface",
"start_frame": 2468,
"end_frame": 2604
},
{
"cut_id": "c15",
"start_frame_id": "c15-start",
"end_frame_id": "c15-end",
"duration_ms": 4040,
"motion_prompt": "물 안쪽 분자를 강조해 여러 방향에 다른 물 분자 이웃이 있다는 환경을 보여준다. Scene=Interface; F2605. 물-공기 경계를 유지하고 안쪽 Liquid4 주변의 다양한 이웃이 보이도록 근접 강조를 시작한다. → Scene=Interface; F2807. Blender F2807에서 Liquid4 안쪽 강조고리 scale=1, Liquid1 표면 강조고리 scale=0.01. 안쪽은 위·아래·옆에 이웃 물 분자가 있다. 실제 대표 Scene Interface. 실제 물 경계 x=[-2.85,2.85], y=[-0.85,1.35], z=[-3.15,0.80] 안에 전체 원자·결합을 포함하고 앞면·윗면을 연 절개 설명 모형, 카메라 (0,-14,6), ortho_scale=13. 색 경계에 고체막을 추가하지 않는다.",
"scene_name": "Interface",
"start_frame": 2604,
"end_frame": 2806
},
{
"cut_id": "c16",
"start_frame_id": "c16-start",
"end_frame_id": "c16-end",
"duration_ms": 4880,
"motion_prompt": "강조 대상을 물 안쪽에서 표면으로 옮겨 공기 방향에는 액체 물 분자 이웃이 적다는 차이를 보여준다. Scene=Interface; F2807. Blender F2807에서 Liquid4 안쪽 강조고리 scale=1, Liquid1 표면 강조고리 scale=0.01. 안쪽은 위·아래·옆에 이웃 물 분자가 있다. → Scene=Interface; F3051. Blender F3051에서 안쪽 강조는 작아지고 Liquid1 표면 강조고리 scale=1. 표면보다 위 공기 쪽에는 액체 물 이웃을 배치하지 않는다; 별도 막은 없다. 실제 대표 Scene Interface. 실제 물 경계 x=[-2.85,2.85], y=[-0.85,1.35], z=[-3.15,0.80] 안에 전체 원자·결합을 포함하고 앞면·윗면을 연 절개 설명 모형, 카메라 (0,-14,6), ortho_scale=13. 색 경계에 고체막을 추가하지 않는다.",
"scene_name": "Interface",
"start_frame": 2806,
"end_frame": 3050
},
{
"cut_id": "c17",
"start_frame_id": "c17-start",
"end_frame_id": "c17-end",
"duration_ms": 2440,
"motion_prompt": "내부와 표면의 불규칙한 분자 배치를 유지해 액체 이웃 환경의 차이를 비교한다. Scene=Interface; F3051. Blender F3051에서 안쪽 강조는 작아지고 Liquid1 표면 강조고리 scale=1. 표면보다 위 공기 쪽에는 액체 물 이웃을 배치하지 않는다; 별도 막은 없다. → Scene=Interface; F3173. 안쪽과 표면의 환경 차이가 보이는 불규칙한 분자 배치 유지. 공기는 단순화한 공간이며 진공이나 수증기 부재 주장이 아니다. 실제 대표 Scene Interface. 실제 물 경계 x=[-2.85,2.85], y=[-0.85,1.35], z=[-3.15,0.80] 안에 전체 원자·결합을 포함하고 앞면·윗면을 연 절개 설명 모형, 카메라 (0,-14,6), ortho_scale=13. 색 경계에 고체막을 추가하지 않는다.",
"scene_name": "Interface",
"start_frame": 3050,
"end_frame": 3172
},
{
"cut_id": "c18",
"start_frame_id": "c18-start",
"end_frame_id": "c18-end",
"duration_ms": 4000,
"motion_prompt": "같은 부피의 닫힌 물방울을 길게 변형해 표면을 넓히는 과정을 입체로 보여준다. Scene=DropletStretch; F3173. Blender F3173의 닫힌 Droplet mesh scale=(1,1,1); 부드러운 청색 구형 물방울. 물의 양은 같은 조건이다. → Scene=DropletStretch; F3373. Blender F3373의 Droplet scale=(1.85,1/sqrt(1.85),1/sqrt(1.85)); 같은 부피를 유지하며 좌우로 길어지고 표면이 넓어지는 설명용 변형이다. 실제 대표 Scene DropletStretch. 닫힌 청색 3D mesh, (0,-12,5) 직교 카메라 ortho_scale=10.5. 같은 물의 양·설명용 모형 합성 앵커, 표면층 없음.",
"scene_name": "DropletStretch",
"start_frame": 3172,
"end_frame": 3372
},
{
"cut_id": "c19",
"start_frame_id": "c19-start",
"end_frame_id": "c19-end",
"duration_ms": 3380,
"motion_prompt": "같은 물의 양으로 길어진 모형을 유지하며 표면을 더 넓히는 데 에너지가 필요함을 설명한다. Scene=DropletStretch; F3373. Blender F3373의 Droplet scale=(1.85,1/sqrt(1.85),1/sqrt(1.85)); 같은 부피를 유지하며 좌우로 길어지고 표면이 넓어지는 설명용 변형이다. → Scene=DropletStretch; F3542. 길어진 같은 부피 물방울 유지; 에너지 필요 주석을 추후 합성한다. 실제 힘·에너지 수치는 표시하지 않는다. 실제 대표 Scene DropletStretch. 닫힌 청색 3D mesh, (0,-12,5) 직교 카메라 ortho_scale=10.5. 같은 물의 양·설명용 모형 합성 앵커, 표면층 없음.",
"scene_name": "DropletStretch",
"start_frame": 3372,
"end_frame": 3541
},
{
"cut_id": "c20",
"start_frame_id": "c20-start",
"end_frame_id": "c20-end",
"duration_ms": 2300,
"motion_prompt": "같은 부피의 물방울이 표면 넓이를 줄이는 쪽으로 모양을 바꾸려는 경향을 연결한다. Scene=DropletStretch; F3542. 길어진 같은 부피 물방울 유지; 에너지 필요 주석을 추후 합성한다. 실제 힘·에너지 수치는 표시하지 않는다. → Scene=DropletStretch; F3657. 같은 부피 물방울이 되돌아갈 준비 상태이며 물 표면의 확장 저항을 설명한다. 실제 대표 Scene DropletStretch. 닫힌 청색 3D mesh, (0,-12,5) 직교 카메라 ortho_scale=10.5. 같은 물의 양·설명용 모형 합성 앵커, 표면층 없음.",
"scene_name": "DropletStretch",
"start_frame": 3541,
"end_frame": 3656
},
{
"cut_id": "c21",
"start_frame_id": "c21-start",
"end_frame_id": "c21-end",
"duration_ms": 3920,
"motion_prompt": "길어진 물방울이 같은 부피를 유지하며 구형에 가까워지는 계획으로 표면을 줄이는 변화를 보여준다. Scene=DropletStretch; F3657. 같은 부피 물방울이 되돌아갈 준비 상태이며 물 표면의 확장 저항을 설명한다. → Scene=DropletStretch; F3853. 같은 부피의 닫힌 mesh가 구형에 가깝게 복귀한다. 복귀 애니메이션은 scenes 승인 후 정식 제작한다. 실제 대표 Scene DropletStretch. 닫힌 청색 3D mesh, (0,-12,5) 직교 카메라 ortho_scale=10.5. 같은 물의 양·설명용 모형 합성 앵커, 표면층 없음.",
"scene_name": "DropletStretch",
"start_frame": 3656,
"end_frame": 3852
},
{
"cut_id": "c22",
"start_frame_id": "c22-start",
"end_frame_id": "c22-end",
"duration_ms": 4480,
"motion_prompt": "물 표면이 넓어지는 데 저항하는 성질을 표면장력이라는 이름과 연결한다. Scene=DropletStretch; F3853. 같은 부피의 닫힌 mesh가 구형에 가깝게 복귀한다. 복귀 애니메이션은 scenes 승인 후 정식 제작한다. → Scene=DropletStretch; F4077. 구형에 가까운 물방울과 표면 확장 저항 설명이 남는다. 껍질·고체 표면층은 없다. 실제 대표 Scene DropletStretch. 닫힌 청색 3D mesh, (0,-12,5) 직교 카메라 ortho_scale=10.5. 같은 물의 양·설명용 모형 합성 앵커, 표면층 없음.",
"scene_name": "DropletStretch",
"start_frame": 3852,
"end_frame": 4076
},
{
"cut_id": "c23",
"start_frame_id": "c23-start",
"end_frame_id": "c23-end",
"duration_ms": 1440,
"motion_prompt": "길어진 물방울과 둥근 물방울의 모양을 같은 물의 양이라는 공통 조건에서 비교한다. Scene=EqualVolumeComparison; F4077. 왼쪽 길어진 닫힌 물방울과 오른쪽 구형 물방울을 동일 부피 조건으로 나란히 배치한다. → Scene=EqualVolumeComparison; F4149. 같은 물의 양 주석 앵커를 유지하며 두 형태의 겉면 차이를 보여준다. 3D 비교 전경, 좌우 모형을 같은 카메라·같은 물의 양으로 비교한다. 수치·픽셀면적 비교 없음.",
"scene_name": "EqualVolumeComparison",
"start_frame": 4076,
"end_frame": 4148
},
{
"cut_id": "c24",
"start_frame_id": "c24-start",
"end_frame_id": "c24-end",
"duration_ms": 4420,
"motion_prompt": "같은 부피일 때 구형이 겉면을 가장 작게 만드는 관계를 두 입체 모형으로 보여준다. Scene=EqualVolumeComparison; F4149. 같은 물의 양 주석 앵커를 유지하며 두 형태의 겉면 차이를 보여준다. → Scene=EqualVolumeComparison; F4370. 같은 부피에서 구형의 표면적이 작다는 관계를 청색 형태와 합성 주석으로 강조한다. 3D 비교 전경, 좌우 모형을 같은 카메라·같은 물의 양으로 비교한다. 수치·픽셀면적 비교 없음.",
"scene_name": "EqualVolumeComparison",
"start_frame": 4148,
"end_frame": 4369
},
{
"cut_id": "c25",
"start_frame_id": "c25-start",
"end_frame_id": "c25-end",
"duration_ms": 4140,
"motion_prompt": "외부 영향이 작은 작은 물방울이 구형에 가까워지는 조건을 제시한다. Scene=ShapeConditions; F4370. 작은 비접촉 물방울과 더 큰 바닥 접촉 물방울을 별도의 조건 예시로 배치한다. 이 컷은 동일 부피 비교가 아니다. → Scene=ShapeConditions; F4577. 외부 영향이 작은 작은 물방울이 구형에 가깝고 오른쪽 방울은 바닥과 접촉해 모양이 달라지는 예시다. 조건 예시 3D 전경, 왼쪽 작은 비접촉 방울과 오른쪽 바닥에 닿은 큰 방울. 앞 비교와 별도 Scene.",
"scene_name": "ShapeConditions",
"start_frame": 4369,
"end_frame": 4576
},
{
"cut_id": "c26",
"start_frame_id": "c26-start",
"end_frame_id": "c26-end",
"duration_ms": 4640,
"motion_prompt": "작은 비접촉 방울과 큰 바닥 접촉 방울을 별도 조건 예시로 구별한다. Scene=ShapeConditions; F4577. 외부 영향이 작은 작은 물방울이 구형에 가깝고 오른쪽 방울은 바닥과 접촉해 모양이 달라지는 예시다. → Scene=ShapeConditions; F4809. 바닥 접촉 방울의 납작한 형태가 보이며 중력·접촉면 영향의 조건 주석 앵커가 있다. 조건 예시 3D 전경, 왼쪽 작은 비접촉 방울과 오른쪽 바닥에 닿은 큰 방울. 앞 비교와 별도 Scene.",
"scene_name": "ShapeConditions",
"start_frame": 4576,
"end_frame": 4808
},
{
"cut_id": "c27",
"start_frame_id": "c27-start",
"end_frame_id": "c27-end",
"duration_ms": 1600,
"motion_prompt": "바닥 접촉과 외부 영향으로 실제 물방울의 모양이 달라질 수 있음을 보여준다. Scene=ShapeConditions; F4809. 바닥 접촉 방울의 납작한 형태가 보이며 중력·접촉면 영향의 조건 주석 앵커가 있다. → Scene=ShapeConditions; F4889. 두 조건 예시를 유지한다. 항상 완전한 구라는 단정·접촉각이나 크기 경계값은 없다. 조건 예시 3D 전경, 왼쪽 작은 비접촉 방울과 오른쪽 바닥에 닿은 큰 방울. 앞 비교와 별도 Scene.",
"scene_name": "ShapeConditions",
"start_frame": 4808,
"end_frame": 4888
},
{
"cut_id": "c28",
"start_frame_id": "c28-start",
"end_frame_id": "c28-end",
"duration_ms": 4220,
"motion_prompt": "작은 청색 물방울로 돌아와 그 주변에 별도 고체 껍질이나 막이 없음을 보여준다. Scene=AnswerDroplet; F4889. 구형에 가까운 청색 물방울과 분리된 분자 개념도 위치가 보인다. 물방울 주변에 별도 껍질은 없다. → Scene=AnswerDroplet; F5100. 동일 물방울 표면을 유지하며 추가 막이 없음을 보여준다. 도입과 같은 크림색 3D 공간·청색 방울 전경을 새 Scene에서 재제시하고 분자 개념도를 별도 위치에 둔다.",
"scene_name": "AnswerDroplet",
"start_frame": 4888,
"end_frame": 5099
},
{
"cut_id": "c29",
"start_frame_id": "c29-start",
"end_frame_id": "c29-end",
"duration_ms": 2580,
"motion_prompt": "분자 개념도에서 이웃 분자 사이 수소결합을 회수하며 원자와 내부 결합은 유지한다. Scene=AnswerDroplet; F5100. 동일 물방울 표면을 유지하며 추가 막이 없음을 보여준다. → Scene=AnswerDroplet; F5229. 별도 분자 개념도에서 온전한 V자 분자 사이 금색 H···O 점선이 변하고 내부 남색 실선은 유지된다. 도입과 같은 크림색 3D 공간·청색 방울 전경을 새 Scene에서 재제시하고 분자 개념도를 별도 위치에 둔다.",
"scene_name": "AnswerDroplet",
"start_frame": 5099,
"end_frame": 5228
},
{
"cut_id": "c30",
"start_frame_id": "c30-start",
"end_frame_id": "c30-end",
"duration_ms": 2800,
"motion_prompt": "물방울과 분자 개념도를 함께 유지해 표면을 넓히기 어려운 성질을 답의 원인으로 연결한다. Scene=AnswerDroplet; F5229. 별도 분자 개념도에서 온전한 V자 분자 사이 금색 H···O 점선이 변하고 내부 남색 실선은 유지된다. → Scene=AnswerDroplet; F5369. 물방울과 분자 개념도를 유지하며 표면을 넓히기 어려운 성질을 합성 주석으로 회수한다. 도입과 같은 크림색 3D 공간·청색 방울 전경을 새 Scene에서 재제시하고 분자 개념도를 별도 위치에 둔다.",
"scene_name": "AnswerDroplet",
"start_frame": 5228,
"end_frame": 5368
},
{
"cut_id": "c31",
"start_frame_id": "c31-start",
"end_frame_id": "c31-end",
"duration_ms": 3200,
"motion_prompt": "분자 사이 끌림과 표면장력이 외부 영향이 작은 작은 물방울의 둥근 형태를 만든다는 답을 회수한다. Scene=AnswerDroplet; F5369. 물방울과 분자 개념도를 유지하며 표면을 넓히기 어려운 성질을 합성 주석으로 회수한다. → Scene=AnswerDroplet; F5529. 외부 영향이 작은 작은 물방울의 둥근 형태와 수소결합·표면장력의 설명이 함께 남아 도입 질문에 답한다. 도입과 같은 크림색 3D 공간·청색 방울 전경을 새 Scene에서 재제시하고 분자 개념도를 별도 위치에 둔다.",
"scene_name": "AnswerDroplet",
"start_frame": 5368,
"end_frame": 5528
}
],
"engine": "Blender", "engine": "Blender",
"render_profile": { "render_profile": {
"engine": "BLENDER_EEVEE", "engine": "BLENDER_EEVEE",
@@ -30,8 +341,352 @@
"samples": 64, "samples": 64,
"seed": 0 "seed": 0
}, },
"scene_path": "" "scene_path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r2/generated-source/animation.blend"
}, },
"result": null "result": {
"clips": [
{
"cut_id": "c01",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/f0fb77b44b3a7e0f-ff638f566c58/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4380,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c02",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/fe41e9c1c4904295-226129fb3aae/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 3540,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c03",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/252c4d4827f07900-062240cdd127/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4000,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c04",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/3b097e55a9067a3d-88be52721626/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2860,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c05",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/00a79a4042b7aa67-cbbd262fba23/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 3300,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c06",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/350ae6aed2bc06e0-83e884eeb30e/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2260,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c07",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/f89fd3b5663c4f93-b0e80fa17c3a/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4040,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c08",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/8a143b259afd678d-ff53e01947d9/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4100,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c09",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/c5f863130bf54d86-99ecdd8c5b89/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4240,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c10",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/31cefa5ad87204fd-de1e57c80c46/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2840,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c11",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/962ede97c96eb35d-c66d07f83cf5/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4800,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c12",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/12d62510bbd4ad00-f93131809a2d/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4760,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c13",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/9d662faa9dd42767-87fe9d242858/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4240,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c14",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/64e94571b9b8e28c-9cbd2e7a4324/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2720,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c15",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/fb66ebe6435f6afa-906d837beca1/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4040,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c16",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/9aeebb0967d39f44-bef15dce6126/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4880,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c17",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/54d5f46a632a9695-ee9ec262318b/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2440,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c18",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/d17c810836884739-389283a9c2fa/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4000,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c19",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/d0d9e6a51128af61-c22fe8c8a04d/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 3380,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c20",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/74d4e59c54be4564-f2850c103ea7/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2300,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c21",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/ed6625d7d0fbb445-2ef6d66052af/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 3920,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c22",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/a4a62fd0d88b0899-d8fd3685254d/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4480,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c23",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/b51248951a76a664-ddf9f89c29c9/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 1440,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c24",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/3aee06d7644dd6d1-880195cf81ad/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4420,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c25",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/82376fb41de0e0f4-f407ebbc225f/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4140,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c26",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/8ef05230d2035e52-30a666b43b90/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4640,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c27",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/d666eb5f1fcd316a-fdb7aee7aab7/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 1600,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c28",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/30cfd4ebd99f655e-e837add0d29e/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 4220,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c29",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/120ae7727066d6ac-4784e9f0c12b/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2580,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c30",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/4267f4ad755c8d75-5a4cfec4ec07/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 2800,
"width": 1080,
"height": 1920
}
},
{
"cut_id": "c31",
"media": {
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r2/render-attempts/4c2c0967c96e947c-ca2c859c571e/clip.mp4",
"kind": "video",
"verified": true,
"duration_ms": 3200,
"width": 1080,
"height": 1920
}
}
]
}
} }
} }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,140 @@
{
"result": "GREEN",
"executions": [
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"contracts/check.py"
],
"exit_code": 0,
"tests": 10
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/test_runner.py"
],
"exit_code": 0,
"tests": 17
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"projects/water-hydrogen-bond-surface-tension/blender/production_preserve.py"
],
"exit_code": 0,
"unchanged_files": 89
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"projects/water-hydrogen-bond-surface-tension/blender/production_reuse.py"
],
"exit_code": 0,
"reopened_sources": 2,
"matching_mesh_objects": 282
},
{
"command": [
"C:/Program Files/Blender Foundation/Blender 5.2/blender.exe",
"--background",
"--disable-autoexec",
"--python-exit-code",
"1",
"--python",
"projects/water-hydrogen-bond-surface-tension/blender/verify_model.py",
"--",
"--source",
"projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/generated-source/preview.blend",
"--out",
"projects\\water-hydrogen-bond-surface-tension\\stages\\12-motion\\r2\\generated-source\\invalid-old-r2.json"
],
"exit_code": 1,
"expected_exit_code": 1,
"matched_reason": "물 영역 밖 또는 여백 부족",
"assertion_errors": [
"AssertionError: 물 영역 밖 또는 여백 부족: F2468 Liquid0_O, 축 여백 [0.49120567332267306, -1.2587944235348747, -0.10879997844696021]"
]
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"contracts/check.py"
],
"exit_code": 0
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"tools/blender/test_runner.py"
],
"exit_code": 0
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"projects/water-hydrogen-bond-surface-tension/blender/production_preserve.py"
],
"exit_code": 0
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"projects/water-hydrogen-bond-surface-tension/blender/production_check.py",
"--stage",
"10-references"
],
"exit_code": 0
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"projects/water-hydrogen-bond-surface-tension/blender/production_check.py",
"--stage",
"11-images"
],
"exit_code": 0
},
{
"command": [
"C:/Users/baram/anaconda3/python.exe",
"-B",
"-X",
"utf8",
"projects/water-hydrogen-bond-surface-tension/blender/production_check.py",
"--stage",
"12-motion"
],
"exit_code": 0
}
]
}
@@ -0,0 +1,452 @@
{
"result": "GREEN",
"pinned_commit": "6b00a8f",
"unchanged_files": 89,
"files": [
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/generated-source/preview.blend",
"sha256": "ba0850a2148824cc8416704c03f14e5714ca01a16620a4eb26981e238af78c73",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/11205a80f142fff0-8a4233b821a3/frame-00003050.png",
"sha256": "ed62bbb40e4c49581e9e6746347d5904a7d7888957b4a9fe86f98b92f02c8b6d",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/11205a80f142fff0-8a4233b821a3/evidence.json",
"sha256": "bf0b2c2e1625c8b2751fc7f013b75ad9da9338bfeb10745ba341fea6c051e2e4",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/55c4cb5b4dd88ff3-69267d17d19b/frame-00001219.png",
"sha256": "a90d41c2d81ba0fe0b92d9dee6c3d4d4b0011be13684e5f02f3a2bf9ff3f5160",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/55c4cb5b4dd88ff3-69267d17d19b/evidence.json",
"sha256": "30c03afccf8b004206c39d8963520007ccc1a12de3f49517132ec78d20ddeffe",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/9347ba3e949e9407-0ed92949aacd/frame-00003372.png",
"sha256": "a822284e864610cf7766234c12e945ac7df78d7bd2658bd7df5b859ac295b38b",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/9347ba3e949e9407-0ed92949aacd/evidence.json",
"sha256": "c956248853e9e7584422ae5720e8ec2bb5c5e77827e4da6e8549ebd13dee4a46",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/ade2a043a51a3c9f-855ff69849d6/frame-00001017.png",
"sha256": "64e4339aca608bdd3935e9043ef2a344416562f95d2ca916f3e5cd39b9ebc206",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/ade2a043a51a3c9f-855ff69849d6/frame-00001118.png",
"sha256": "7658351a85ac470a6c56b4c8e63d378bdff0fcfe3499ef1a83ae36aff4fe35c6",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/ade2a043a51a3c9f-855ff69849d6/frame-00001218.png",
"sha256": "327dfd77c03334b7900d16c2bb420dcfd6f49d9ba5c7a7282fca0aced5bfb201",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/ade2a043a51a3c9f-855ff69849d6/clip.mp4",
"sha256": "fcda5c8e23a14e51bf55f99cdb4b1a41630eed8c88ab55eb9cbaab8978c8b620",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/ade2a043a51a3c9f-855ff69849d6/evidence.json",
"sha256": "a895f887d9aad8e3c23ee522b86438c8ddc99c9c6d10ebc13ef8a202e7bc0b73",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/9af0672466949ffe-e876251fcde8/frame-00002806.png",
"sha256": "61e9aa6d5e1b334600a74cc7e7342ccea97f041f7e332d8cc3c91d8752f9e427",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/9af0672466949ffe-e876251fcde8/frame-00002928.png",
"sha256": "b863a1cce4d81caeb67a2d6027436ef836c9d2243af8dbce742dea85b6ecf4c4",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/9af0672466949ffe-e876251fcde8/frame-00003049.png",
"sha256": "b3a9ea178d98306afb4392a885a955ec1264c3011094df581df7bc0e872f600c",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/9af0672466949ffe-e876251fcde8/clip.mp4",
"sha256": "43790be2888b6fa18217d342af28985f61884f0caccc326af8a439dcdf640c64",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/9af0672466949ffe-e876251fcde8/evidence.json",
"sha256": "c9633fbc6219b99361b552c5e8de44d042ce887cf5d7977654b19e6604e7b8a2",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/c152ea48087b78fb-dc5644146e6c/frame-00003172.png",
"sha256": "080f63f3d3d7cd3c4c45bdfe2169626a7e5102dae7d5a3baa70c8c9f05edceb3",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/c152ea48087b78fb-dc5644146e6c/frame-00003272.png",
"sha256": "9098e93b84f4b2893a95d175371a034c943297b2c28720b2c6ee4d7984de224d",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/c152ea48087b78fb-dc5644146e6c/frame-00003371.png",
"sha256": "f0daca6ad25b1ff83047e71e1dcc008ad043d822afbff73dadd78377d5a25626",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/c152ea48087b78fb-dc5644146e6c/clip.mp4",
"sha256": "db354bf19edf75ad275c2a5f0cd895abfa6be765c5bce4d85bbe35a4db68ee80",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/render-attempts/c152ea48087b78fb-dc5644146e6c/evidence.json",
"sha256": "ad8398fa95a8c99d7916cc1de4d4a42c7d59b17edb9fc5d41c904771bd8c8845",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/generated-source/mobile-c07.png",
"sha256": "b0ee1e3baf1561e5976fdeeacfb8e84d068237c5af2e0e7cdda1756ea502fe4c",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/generated-source/mobile-c16.png",
"sha256": "6f66c4560031126a8c22e32d429605d09131f5ffbffbf21a1a5862ec5cfcaa8c",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/generated-source/mobile-c18.png",
"sha256": "ae6708539c7ef2cea4e20383fd50517f8872b887d195d89354ab91c680e7df60",
"baseline": "08 r3 승인 실물 목록"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/generated-source/preview.blend",
"sha256": "90300fd78369d88371f727a69f6c4b03f2d0cd3bb950d2698c48018e111a9aeb",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/narration.wav",
"sha256": "aeac3c60acf481195be1af093d633cf16ea4ea6645778d762f529e49ca5ec905",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u01.wav",
"sha256": "6da0cdca317b77019a8c6d3a2624a66f888118aca49f4771823c174d0566fed0",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u02.wav",
"sha256": "6fde36df1fa98e5ad4de6775b3ac21071ab0f33aa54f6c0ac14aaec9af4088bc",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u03.wav",
"sha256": "2cd48379dfc704a6c8a06b8de21085f9f40405d94c22f5893525c9c47f0da9e4",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u04.wav",
"sha256": "9dbfb2df6d9888127921dd17cb9c4ada4785934c3fc194aad9313dad9ec02d7e",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u05.wav",
"sha256": "84cbd278321539207318a3a7a7cd8c3527778f5950de6cfb77b18b1352738d01",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u06.wav",
"sha256": "5c7e19466ea3d3d01b853d9bae00e52703df6e50906aad38219689db63a60ce0",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u07.wav",
"sha256": "38a200bab0e80f85e80719900870823d3166d5071f19866e0e44aab02d7d8fc9",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/media/06-tts/r1/u08.wav",
"sha256": "55d16e7f65e5f183cc17d194898e9d80b70a9308a370a36c7e4c4367fdf5a904",
"baseline": "08 r3 보존 기준"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/01-topic/r1/artifact.json",
"sha256": "b6185f25aaa719da54bd3404ff919d3888b1d7c99bda38080af418b48c0ac9e7",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/01-topic/r1/review.md",
"sha256": "9e71b7f64f175ce3af5f4196dffd465289c988718f9b9a11d664a51bd1af8603",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/02-research/r1/artifact.json",
"sha256": "b72b06006b5befca6b4fa5f0e140269fe40a913b42601418920018ddce930b8b",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/02-research/r1/review.md",
"sha256": "0305208c45022d5229508106b4af79a4660cc843543542898c9a58bb54832bb0",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/03-script/r1/artifact.json",
"sha256": "1e04070b9a98eee5e72fc42d94b2ec754c68ac7a1b97e93da9906be17d6596f6",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/03-script/r1/review.md",
"sha256": "f9a12d9ef6854a8ed9e776198d5d5de43729e9249f07c63710ea30a6a4d703dc",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/04-title/r1/artifact.json",
"sha256": "bc98e18db9f3eef2a9cc9b6f07918c8ae6f143f5f4000fcd3a626559d58756d0",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/04-title/r1/review.md",
"sha256": "9abadef520707799d37dc3a7c6ce4deef50bb5055d8bdbc64bba996461a232f9",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/05-tts-split/r1/artifact.json",
"sha256": "fffb9f187dcb0df7bf0aa82f732c0594cedfd9f5b03d86969c9d733ff9f539ca",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/05-tts-split/r1/review.md",
"sha256": "9bec89a7411207d1b01d2ce08e064ad3331269be76169798bc042bccbbd9bff6",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/06-tts/r1/artifact.json",
"sha256": "70d362484ac7fc17a0abb4550d0e505bdef20ae78e3a1207492d32c306fb5873",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/06-tts/r1/review.md",
"sha256": "d46aaa8643195bc0197d625f64cc221c219423bdd24c09bc929cfec831acf451",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/07-cuts/r1/alignment.json",
"sha256": "15f9ca15f09b47691feec74efec487549da1f595dc0a167142d47a76317159ba",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/07-cuts/r1/artifact.json",
"sha256": "d5ba99ef50ea270e45ca018977d0e8cce2157929a9aebaf045ac7e5d6d463bff",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/07-cuts/r1/review.md",
"sha256": "39f89bcb1a9f1a03c4a7fbf64c747db61c09298efb6546c98a0b5072262d4597",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r1/artifact.json",
"sha256": "f4c675747fc8c1d4becdc90f54c843b4b8dade114bb32b1d2637b44a5a924f2f",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r1/review.md",
"sha256": "3bfefcfd8d462dae7879efea7fd64c592d71d8d240af550b44161c3dfe67a7eb",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/.gitignore",
"sha256": "c45dc687539a9dc2c4b84b6d9918bd61db68620dbef6fb74f761856959903c0e",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/artifact.json",
"sha256": "9209aea8383ae6bc5c0d921153c8cf8271666426ccf407ea408429c9d6a86e0a",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/cli-commands.json",
"sha256": "3851ad8e37c40905b92a75376b4bf7048c713f8380645d8b246cbf2d2bf7e37b",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/design-check.json",
"sha256": "5812c551c98d826dc0edd45a5d0d0561cec14c72c945ceae590fe789cc312f23",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/model-check.json",
"sha256": "6648003fbddb00e4e95646fb686445e00d9329419134ee3623307aee05f98964",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/model-test-results.json",
"sha256": "e264e25b8f8e0614cf39434ea4f4a1f5ba5e036205c82a79a4fab1ba91c5ec4a",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/motion-check.json",
"sha256": "9ace6ea70590502cc4d7de947ee1c6ffef0b86459f63440ab5e3ab33aac654dc",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/preservation-check.json",
"sha256": "d36a295dd4fcf36d5d3ebc03babb9e72bdb3c7d79cfa7a865142f002ebf9769a",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r2/review.md",
"sha256": "63bab7d3ee86c4b74a0ca8f60f0d05b3bc0c8488012fa776bed9aef9ada4f111",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/.gitignore",
"sha256": "c45dc687539a9dc2c4b84b6d9918bd61db68620dbef6fb74f761856959903c0e",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/artifact.json",
"sha256": "b314c36ec1e71ccb904f8828dd0feb174c7698575b18c8baa7ff72035b8f534c",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/cli-commands.json",
"sha256": "2cb604c8ecb57988e6800895ab11942e09356efbcc8c4dbdecff3a477cfcaf32",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/design-check.json",
"sha256": "eebbc04545841ce9d01c32633e20bdeb1b9beef025cd83cfedae4f15457157ce",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/model-check.json",
"sha256": "5c61769ed10115fdbd420b695c738ddd8670f616232aaa84b0ab577471f0c0cf",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/model-test-results.json",
"sha256": "27867a75a68c2ffe6ed658e6d6a326ee936cef9ab1c8c774098c4202f8136358",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/motion-check.json",
"sha256": "40f3b5b1ecdf0e2f0fc1a1b8d21f0d7c56775720745a177711a09091b68cc001",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/preservation-check.json",
"sha256": "67d9e76a1a48c5bc959b731aebb730341e9683a2d686816938911d3f25b704b6",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/08-scenes/r3/review.md",
"sha256": "2632218957e30e7a75ed968ec5482a09c70c46f9d9a077283d9c2f5ef522a0cd",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/09-entities/r1/artifact.json",
"sha256": "979617dc9247f1d969419b573b082e37eeab07a305b9229540595ed53b21bb56",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/09-entities/r1/review.md",
"sha256": "b34c221f961e5d5942b7673e9012529a8b5cfbc20804d3a93b3847356c295ba9",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/09-entities/r2/artifact.json",
"sha256": "6ca47aa11942348fa3d8977bac5d3848f2485ae299f010d2d993f86eccba3029",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/09-entities/r2/review.md",
"sha256": "16b064881b34a1a1975147f146bbd28c58fa41a19f002d7260df2c06cb2522bb",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/09-entities/r3/artifact.json",
"sha256": "e4aea30ef57cc0d3844b38da0861001b7853dc6aa30aa4a87c57e17f83040bb6",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/09-entities/r3/review.md",
"sha256": "669c4f975f24102d196098f26e472a8755c7ac3618254849ebe40e21959ea02d",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r1/artifact.json",
"sha256": "41e4154ab5b2ebaf5ffa8bdaf4acd59fe78177cde75b6340bfd2b2a7360af4f8",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/10-references/r1/review.md",
"sha256": "a1c6019a9dbc429ead36cd2f6fbbe33e10547abf5b464895ea87f960479fd96f",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r1/artifact.json",
"sha256": "749e05193e2b049a2f76cfe7a054b3d91778731a490e62179a0515b5807718e0",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/11-images/r1/review.md",
"sha256": "c686d6bb7d69587830ee9d4638612046700d925071f6073cc2ebe5e5cd6ec0c9",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r1/artifact.json",
"sha256": "f597eb5c1cf7dc8eadf70f674117515b0ffc78d40dabbe0ef49f1edf06f644ba",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/12-motion/r1/review.md",
"sha256": "54e3df8fbd276313f63762ac01dee71f2d9195bd2bbb1e3390468601e7aa02bb",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/13-compose/r1/artifact.json",
"sha256": "4b2f45a9dec2643b24fd1f9452f3ec617b7a8cb326b7af9bc4a2128b0da35e33",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/13-compose/r1/review.md",
"sha256": "469fb391f8695ceb1071195f2308c3840bdd42084ffd3690aa310f02678cf2f5",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/14-review/r1/artifact.json",
"sha256": "bca491941d5ab5aa452618f203e3421f0c49d875b02ae10c009e7db75170d177",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "projects/water-hydrogen-bond-surface-tension/stages/14-review/r1/review.md",
"sha256": "152cfed1657c3dc29d8cfabed5422adb70dcc5a6d483120aa4e5555adf3c72d4",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "references/1.png",
"sha256": "5d83e03add8cdc29f5d7b1f1d06fb87f7738a6dc32579f554fb824fb36b83e44",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "references/shorts/analysis.md",
"sha256": "33b653d98c55503e6bb5c45464c7053d482a193f0d5d6dd658ca2abadfa404d8",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
},
{
"path": "references/shorts/source-manifest.json",
"sha256": "4821a02e81621cab595bb9a45db14bf48f96220984426337a2ac84db3ffa2cb7",
"baseline": "6b00a8f 작업 사본 체크아웃 바이트"
}
]
}
@@ -0,0 +1,27 @@
# 정식 컷 영상 검토 · 12 r2
총괄이 실제 10 기준 원본·7 PNG와 11 애니메이션 원본·62 frame ID·40 PNG·검토 문서·manifest를 순서대로 확인하고 로컬 완료를 기록한 뒤 제작했다. 사용자가 승인한 07 r1/08 r3/09 r3의 31개 컷·30개 연결·110560ms를 유지한다. 기존 대본·제목·38개 자막 구간·WAV 9개·스타일·과거 결과를 변경하지 않았다.
모든 영상은 실제 [11 원본](../../11-images/r2/generated-source/animation.blend)에서 렌더했다. SHA는 `026a3ba659fc292cf3e29fa07adeb2f0beb6bb7ab8b432be2e628d482fc66337`이며 11 끝점과 12 영상의 Scene·원본 해시·설정이 같다. 프로필은 EEVEE/GPU/50fps/1080×1920/64 samples/seed 0이다. 실제 결과는 무음 H264 MP4 31개·5528 프레임·110560ms이며 모든 컷은 5초 이하다. 2D 확대·이동으로 3D 애니메이션을 대신하지 않았다.
Blender 프레임은 논리 프레임+1이다. 각 영상은 `[start,end)`를 실제 디코드했고 마지막 표시 프레임은 end−1이다. 11 끝 PNG는 exclusive 경계여서 마지막 표시 프레임보다 정확히 1프레임/20ms 뒤다. continuous 22개 연결의 실제 공유 PNG/Scene/경계 상태와 cut 8개 연결의 다른 Scene 시점 전환을 11 검사와 함께 유지한다. 실제 31개 영상 시작 렌더와 11 시작 PNG의 평균 픽셀 오차는 모두 0이다.
모든 MP4를 전체 디코드하고 시작·중간·마지막의 실제 PNG 93개를 추출했다. 각 360×640 패널과 11 끝 PNG를 나란히 직접 열어 31개 컷을 관찰했다. [visual-check.json](visual-check.json)에 각 컷의 관찰·화면 경로/SHA·프레임·끝 경계 차이를 남겼다. MP4 디코드와 같은 프레임의 원본 렌더 사이 최대 RGB 평균 오차는 1.090/255다. 압축 영상과 PNG가 동일 픽셀이라고 주장하지 않는다.
실제 c07의 분자 접근, c11/c12의 두 연결 쌍 교체, c15의 안쪽 강조, c16의 안쪽→표면 강조와 연결 교체, c18의 길어짐, c21의 구형 복귀, c29의 점선 소멸/재등장이 분명하다. 유지 구간은 승인 계획에 맞춰 정상으로 판정했다. 원자 분리·반응 없이 O1H2/V자·내부 남색 실선·서로 다른 분자의 금색 H···O 점선을 구별한다. Interface c14–c17의 전체 원자와 결합은 파란 물 영역 안에 보이며 위쪽 공기 방향의 액체 이웃이 적다. 열린 앞면·윗면은 설명용 절개이고 고체 벽·막이 아니다. 공기 공간은 진공이나 수증기 부재라는 주장이 아니다.
길어짐·유지·복귀 전체 905 프레임의 실제 닫힌 mesh 부피 상대 변화는 `1.945e-7`로 1% 이내다. 같은 부피 비교 두 mesh도 1% 이내로 같으며 작은 비접촉/큰 바닥 접촉 조건 모형은 별도 사례다. 조건에 따라 실제 모양이 다를 수 있음을 유지하고 항상 완전한 구·새 과학 수치·MD·유체 시뮬레이션을 주장하지 않는다. 전체 705 프레임 Interface의 실제 평가 정점 11,747,388개와 물 경계/네 면 투영, 불규칙 이웃·연결·부피·기준 자산 재사용 증거는 [11 모델 검사](../../11-images/r2/model-check.json), [재사용 검사](../../11-images/r2/reuse-check.json)에 있다.
원본 부재·Scene 누락·중심은 물 안에 있으나 정점이 밖인 실제 fixture는 [11 RED→GREEN 검사](../../11-images/r2/model-test-results.json)에 기록했다. 보존된 r2 실패 원본도 다시 열어 정확한 포함 관계 AssertionError로 RED exit 1임을 확인했다. 해당 명령·실패 문구와 계약 10개/실행기 17개 회귀 검사 exit 0은 [pipeline-check.json](pipeline-check.json)에 있다. 승인 실물·WAV·선행 단계·스타일 등 89개 파일의 실제 바이트 보존은 [preservation-check.json](preservation-check.json)에 있다. 텍스트의 고정 작업 사본 체크아웃 줄바꿈도 기준 바이트로 대조했다.
실제 렌더·인코딩·probe·전체 디코드를 포함한 31개 production 실행은 모두 exit 0이다. 컷별 정확한 argv·exit·소요·원본 해시·evidence는 [cli-commands.json](cli-commands.json)에 있다. 관찰 추출에서 기존 FFmpeg에 select 필터와 rawvideo 출력 muxer가 없는 실패를 확인하고 기록을 보존했다. 지원되는 개별 output seek PNG 경로로 수정했으며 전체 디코드는 rawvideo codec/null muxer로 검증했다. 초기 두 컷의 성공한 RGB 스트리밍도 프레임 하나씩만 메모리에 읽은 실제 결과이며 보존했다. 실패 argv와 최종 추출/probe/디코드 exit는 [visual-cli-commands.json](visual-cli-commands.json)에 있다. 새 설치·전역 설정·원본 스타일·외부 유료 도구를 사용하지 않았다.
```powershell
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_requests.py --stage 12-motion
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_render.py --stage 12-motion
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_views.py --stage 12-motion
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_preserve.py
& 'C:/Users/baram/anaconda3/python.exe' -B -X utf8 projects/water-hydrogen-bond-surface-tension/blender/production_check.py --stage 12-motion
```
최종 실행은 exit 0이다. 실제 원본·31 MP4·93개 원본 표본·93개 디코드 표본·31개 모바일 화면·evidence·작은 검증 JSON의 복사 경로와 SHA는 [manifest.json](manifest.json)에 있다. 실패 시도·전체 프레임·로그·캐시는 좁은 .gitignore 아래 실물로 보존하며 커밋하지 않는다. 부분전하·국문 글자·주석은 [실제 카메라 앵커](../../11-images/r2/anchors.json)로 합성 단계에서 배치한다. 음성·자막·최종 합성과 final 사용자 승인은 후속 단계이며 이번 정식 컷 제작 완료를 최종 영상 완료로 처리하지 않는다. project.json은 총괄만 작성한다.