[{"data":1,"prerenderedAt":342},["ShallowReactive",2],{"finding:p1-a-1-brush-eval-audit":3,"finding-runs:p1-a-1-brush-eval-audit":296,"finding-related:p1-a-1-brush-eval-audit":297},{"meta":4,"impact":35,"sections":41},{"id":5,"title":6,"subtitle":7,"eyebrow":8,"date":9,"status":10,"category":11,"polarity":12,"axes":13,"tags":16,"task_code":26,"related_runs":27,"related_findings":30},"p1-a-1-brush-eval-audit","P1.A.1 brush eval audit — 数式定式化 + diff 観点 12 項目","brush trainer (\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush, Apache 2.0 OSS) の eval pipeline を 完全に read-only で audit。PSNR = 10·log10(1\u002FMSE), MAX=1, MSE は H·W·3 mean。GT は AlphaMode::Transparent デフォルトで RGB を α で premultiply、render は bg=Vec3::ZERO で固定、loss は composite_bg=None で premultiplied 同士の差分。NeRF Synthetic では α=0 の透明領域 (画面の大部分) で pred=0, gt=0 となり完全一致 → 大量の free PSNR。これが apparent gap (-3〜-6 dB) の主仮説。test split は transforms_test.json の全 view、8-bit roundtrip + RGB-only (α-match 無効) + SH degree=3。splat-rs A.2 と比較すべき diff 観点を 12 項目に list 化、次 phase A.2\u002FA.3 に引き渡す。","Audit · brush eval pipeline · 12-axis diff checklist","2026-05-24","stable","audit","neutral",[14,15],1,2,[17,18,19,11,20,21,22,23,24,25],"p1","a-1","brush","psnr","ssim","eval","alpha","premultiplied","convention","P1.A.1",[28,29],"brush-lego-sh3-30k (37.40 dB report)","splat-rs-lego-sh3-30k (24.879 dB)",[31,32,33,34],"p1-a-2-splat-rs-eval-audit (未作成、並列 A.2 で作成中)","m4-brush-bench","brush-vs-splat-37dB-gap-analysis","a-4-nerf-synthetic-scene-results",{"summary":36,"rank":37,"verdict":38,"delta_wallclock":39,"delta_psnr":40},"brush eval は (1) AlphaMode::Transparent で GT を α premultiply、(2) bg=Vec3::ZERO の黒背景に render、(3) composite_bg=None で premultiplied 同士を直接比較、(4) 8-bit roundtrip 後に MSE = mean((pred−gt)²) over H·W·3、(5) PSNR = 10·log10(1\u002FMSE)。これは NeRF Synthetic (RGBA で α=0 の透明領域が支配的) において **透明領域は pred=gt=0 で完全一致** となり、MSE 分母に 0 寄与が大量に入る → conventional 「白背景に composite してから PSNR」より高く出る。splat-rs 側の eval 規約を A.2 で確認し、A.3 で「同 convention 下での真の gap」を測定する必要あり。","high","audit-complete","N\u002FA (audit task)","N\u002FA (apparent gap mechanism の特定が主目的)",[42,45,50,53,57,65,67,70,76,78,82,85,87,92,94,96,102,104,112,114,116,122,124,129,131,199,201,215,217,220,226,228,234,236,287,289],{"type":43,"text":44},"lead","brush trainer は \u003Ccode>\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002F\u003C\u002Fcode> (Apache 2.0 OSS、別 repo、本 audit 中は \u003Cstrong>read-only\u003C\u002Fstrong>) で公開されており、lego sh3 30k で PSNR \u003Cstrong>37.40 dB\u003C\u002Fstrong>。splat-rs (24.879 dB) との \u003Cstrong>12.56 dB gap\u003C\u002Fstrong> の真因切り分け前提として、brush 側 eval (PSNR\u002FSSIM\u002Fsplit\u002F背景\u002F前処理) を完全に式化する。",{"type":46,"label":47,"variant":48,"text":49},"callout","Headline","warning","\u003Cstrong>brush eval は premultiplied-α 空間で bg=black に対して PSNR を計算する。\u003C\u002Fstrong> NeRF Synthetic の透明領域 (画面の大部分) では pred=gt=0 となり完全一致 → MSE 分母に 0 寄与が大量に入り、conventional 「composite-on-white してから PSNR」より構造的に高く出る。これが apparent gap (-3〜-6 dB) の主仮説。\u003Cstrong>A.2 で splat-rs 側の convention を確認するまでは断定不可\u003C\u002Fstrong>。",{"type":51,"text":52},"heading","1. PSNR 数式 (核心)",{"type":54,"lang":55,"text":56},"code","rust","\u002F\u002F crates\u002Fbrush-train\u002Fsrc\u002Feval.rs:36-53\nlet (img, render_aux) =\n    render_splats(splats, gt_cam, res, Vec3::ZERO, None, TextureMode::Float).await;\nlet render_rgb = img.slice(s![.., .., 0..3]);\n\n\u002F\u002F Simulate an 8-bit roundtrip for fair comparison.\nlet render_rgb = (render_rgb * 255.0).round() \u002F 255.0;\n\nlet cfg = |l1, ssim| ImageLossConfig {\n    l1_weight: l1,\n    ssim_weight: ssim,\n    composite_bg: None,\n    mask: false,\n};\n\u002F\u002F MSE = mean(L1^2) since |a - b|^2 == (a - b)^2.\nlet mse = image_loss_eval(render_rgb.clone(), gt_packed.clone(), cfg(1.0, 0.0))\n    .powi_scalar(2)\n    .mean();\nlet psnr = mse.recip().log() * 10.0 \u002F std::f32::consts::LN_10;\n",{"type":58,"items":59},"list",[60,61,62,63,64],"\u003Cstrong>定式\u003C\u002Fstrong>: \u003Ccode>PSNR = 10·log10(1 \u002F MSE)\u003C\u002Fcode> — \u003Ccode>MAX² = 1\u003C\u002Fcode> 暗黙固定 (画像は [0,1] f32)","\u003Cstrong>MSE 範囲\u003C\u002Fstrong>: \u003Ccode>mean((pred_rgb − gt_rgb)²)\u003C\u002Fcode> over \u003Cstrong>H × W × 3\u003C\u002Fstrong> 全 voxel (RGB 3 ch を均等に平均、α 含まず)","\u003Cstrong>weighted by alpha なし\u003C\u002Fstrong>: \u003Ccode>mask: false\u003C\u002Fcode> なので α-mask は適用されない","\u003Cstrong>8-bit roundtrip\u003C\u002Fstrong>: pred を \u003Ccode>(x*255).round()\u002F255\u003C\u002Fcode> で量子化してから比較 — GT は元から u8 packed なので「両者 u8 精度で揃える」公平化","\u003Cstrong>log base\u003C\u002Fstrong>: \u003Ccode>natural log \u002F ln(10)\u003C\u002Fcode> = \u003Ccode>log10\u003C\u002Fcode> (`std::f32::consts::LN_10` 使用)",{"type":51,"text":66},"2. 背景処理 (apparent gap の主因候補)",{"type":68,"text":69},"paragraph","brush eval は 3 段階で background を扱う:",{"type":58,"ordered":71,"items":72},true,[73,74,75],"\u003Cstrong>render 側\u003C\u002Fstrong>: \u003Ccode>render_splats(..., Vec3::ZERO, ..., TextureMode::Float)\u003C\u002Fcode> で \u003Cstrong>黒背景 (0,0,0)\u003C\u002Fstrong> に合成 (\u003Ccode>eval.rs:37\u003C\u002Fcode>)","\u003Cstrong>GT 側\u003C\u002Fstrong>: \u003Ccode>view_to_sample_image(gt_img, alpha_mode)\u003C\u002Fcode> が \u003Ccode>AlphaMode::Transparent\u003C\u002Fcode> かつ RGBA の場合 \u003Cstrong>byte 空間で α premultiply\u003C\u002Fstrong> (\u003Ccode>scene.rs:212-232\u003C\u002Fcode>)","\u003Cstrong>loss 側\u003C\u002Fstrong>: \u003Ccode>composite_bg: None\u003C\u002Fcode> なので kernel 内 \u003Ccode>gt_eff = gt_c\u003C\u002Fcode> (premultiplied bytes をそのまま比較、再 composite なし) (\u003Ccode>brush-loss\u002Fsrc\u002Flib.rs:228-233\u003C\u002Fcode>)",{"type":54,"lang":55,"text":77},"\u002F\u002F crates\u002Fbrush-dataset\u002Fsrc\u002Fscene.rs:212-232\npub fn view_to_sample_image(image: DynamicImage, alpha_mode: AlphaMode) -> DynamicImage {\n    if image.color().has_alpha() && alpha_mode == AlphaMode::Transparent {\n        let mut rgba_bytes = image.to_rgba8();\n        \u002F\u002F Assume image has un-multiplied alpha and convert it to pre-multiplied.\n        for pixel in rgba_bytes.chunks_exact_mut(4) {\n            let (r, g, b, a) = (pixel[0], pixel[1], pixel[2], pixel[3]);\n            pixel[0] = ((r as u16 * a as u16 + 127) \u002F 255) as u8;\n            pixel[1] = ((g as u16 * a as u16 + 127) \u002F 255) as u8;\n            pixel[2] = ((b as u16 * a as u16 + 127) \u002F 255) as u8;\n            pixel[3] = a;\n        }\n        DynamicImage::ImageRgba8(rgba_bytes)\n    } else { image }\n}\n",{"type":46,"label":79,"variant":80,"text":81},"数式上の意味","info","NeRF Synthetic GT は \u003Ccode>(r,g,b,a)\u003C\u002Fcode>; \u003Ccode>a=0\u003C\u002Fcode> の透明領域では premultiply 後 \u003Ccode>(0,0,0,0)\u003C\u002Fcode>。brush render は \u003Ccode>bg=Vec3::ZERO\u003C\u002Fcode> で α-blend 出力なので、透明領域では \u003Cstrong>render output も (0,0,0)\u003C\u002Fstrong>。→ \u003Ccode>(pred − gt)² = 0\u003C\u002Fcode> が \u003Cstrong>透明領域全ピクセルで成立\u003C\u002Fstrong>。NeRF Synthetic では透明領域が画面の半分以上を占めることが多い (例: lego の car 周辺、ficus の枝間) ため、MSE が機械的に半分以下になり、PSNR は構造的に \u003Cstrong>+3 dB 以上\u003C\u002Fstrong> 嵩上げされる。",{"type":51,"level":83,"text":84},3,"AlphaMode の決定ロジック",{"type":54,"lang":55,"text":86},"\u002F\u002F crates\u002Fbrush-dataset\u002Fsrc\u002Fscene.rs:46-52\nlet alpha_mode = override_alpha_mode.unwrap_or_else(|| {\n    if mask_path.is_some() {\n        AlphaMode::Masked        \u002F\u002F mask file あり → Masked (premultiply しない)\n    } else {\n        AlphaMode::Transparent   \u002F\u002F mask file なし → Transparent (premultiply する)\n    }\n});\n",{"type":58,"items":88},[89,90,91],"\u003Ccode>--alpha-mode\u003C\u002Fcode> CLI で override 可能 (\u003Ccode>config.rs:33\u003C\u002Fcode>)","\u003Cstrong>NeRF Synthetic は mask file なし\u003C\u002Fstrong>なので \u003Ccode>Transparent\u003C\u002Fcode> がデフォルト → premultiply 経路","もし brush の 37.4 dB が \u003Ccode>--alpha-mode=masked\u003C\u002Fcode> で取られていた場合、本 audit の主仮説は崩れる → \u003Cstrong>A.2 で brush run command を確認すること\u003C\u002Fstrong>",{"type":51,"text":93},"3. test split の選び方",{"type":54,"lang":55,"text":95},"\u002F\u002F crates\u002Fbrush-dataset\u002Fsrc\u002Fformats\u002Fnerfstudio.rs:259-308\nlet eval_trans_path = json_files\n    .iter()\n    .find(|x| x.ends_with(\"transforms_val.json\"))\n    .or_else(|| json_files.iter().find(|x| x.ends_with(\"transforms_test.json\")));\n\u002F\u002F ...\nfor (i, view) in train_handles.into_iter().enumerate() {\n    if let Some(eval_period) = load_args.eval_split_every {\n        if i % eval_period == 0 && val_views.is_none() {\n            eval_views.push(view);\n        } else { train_views.push(view); }\n    } else { train_views.push(view); }\n}\nif let Some(val_views) = val_views { eval_views.extend(val_views); }\n",{"type":58,"items":97},[98,99,100,101],"\u003Cstrong>優先順位\u003C\u002Fstrong>: \u003Ccode>transforms_val.json\u003C\u002Fcode> → \u003Ccode>transforms_test.json\u003C\u002Fcode> → (なければ \u003Ccode>eval_split_every\u003C\u002Fcode> で train から間引き)","\u003Cstrong>NeRF Synthetic\u003C\u002Fstrong>: \u003Ccode>transforms_test.json\u003C\u002Fcode> が存在 (200 views) → \u003Cstrong>全 200 view が eval\u003C\u002Fstrong>、subsample なし","\u003Ccode>--eval-split-every\u003C\u002Fcode> は val_views が存在する場合 \u003Cstrong>無視される\u003C\u002Fstrong> (`val_views.is_none()` 条件)","eval は \u003Ccode>run_eval()\u003C\u002Fcode> (\u003Ccode>train_stream.rs:455-513\u003C\u002Fcode>) で全 view を逐次回し、psnr\u002Fssim を単純平均",{"type":51,"text":103},"4. image 前処理",{"type":58,"items":105},[106,107,108,109,110,111],"\u003Cstrong>normalization\u003C\u002Fstrong>: pred は [0,1] f32 (renderer 出力); GT は u8 packed RGBA → kernel 内で \u003Ccode>byte * (1\u002F255)\u003C\u002Fcode> で [0,1] f32 に変換 (\u003Ccode>brush-loss\u002Fsrc\u002Flib.rs:82, 119-122\u003C\u002Fcode>)","\u003Cstrong>premultiply\u003C\u002Fstrong>: GT は \u003Ccode>view_to_sample_image\u003C\u002Fcode> で byte 空間で premultiply (前述); render 側はそのまま","\u003Cstrong>tonemap \u002F gamma \u002F sRGB\u003C\u002Fstrong>: \u003Cstrong>一切なし\u003C\u002Fstrong> — 線形空間のまま比較","\u003Cstrong>resize\u003C\u002Fstrong>: \u003Ccode>max_resolution = 1920\u003C\u002Fcode> デフォルト (\u003Ccode>config.rs:21\u003C\u002Fcode>) — NeRF Synthetic 800×800 は no-op","\u003Cstrong>8-bit roundtrip\u003C\u002Fstrong>: pred のみ \u003Ccode>(*255).round()\u002F255\u003C\u002Fcode>、GT は元から u8 packed なので追加処理なし (\u003Ccode>eval.rs:41\u003C\u002Fcode>)","\u003Cstrong>channels\u003C\u002Fstrong>: eval は render を \u003Ccode>slice(s![.., .., 0..3])\u003C\u002Fcode> で \u003Cstrong>RGB 3 channel のみ\u003C\u002Fstrong>使用、α は loss 対象外 (training と異なる; training は α-match path あり)",{"type":51,"text":113},"5. SSIM 数式 (参考)",{"type":68,"text":115},"PSNR と同じ \u003Ccode>image_loss_eval\u003C\u002Fcode> を \u003Ccode>cfg(0.0, 1.0)\u003C\u002Fcode> で呼び出して per-pixel SSIM map を取得、\u003Ccode>.mean()\u003C\u002Fcode>。kernel は 11-tap 可分離 Gaussian (\u003Ccode>brush-loss\u002Fsrc\u002Flib.rs:165-341\u003C\u002Fcode>) で、定数:",{"type":58,"items":117},[118,119,120,121],"\u003Ccode>C1 = 0.01² = 1e-4\u003C\u002Fcode> (\u003Ccode>lib.rs:80\u003C\u002Fcode>)","\u003Ccode>C2 = 0.03² = 9e-4\u003C\u002Fcode> (\u003Ccode>lib.rs:81\u003C\u002Fcode>)","\u003Ccode>val = clamp((2·μ1·μ2 + C1)(2·σ12 + C2) \u002F ((μ1²+μ2²+C1)(σ1²+σ2²+C2)), -1, 1)\u003C\u002Fcode>","\u003Cstrong>σ² ≥ 0\u003C\u002Fstrong> を保証するため \u003Ccode>F::max(zero, out1 - mu1²)\u003C\u002Fcode> で clip (\u003Ccode>lib.rs:321-322\u003C\u002Fcode>)",{"type":51,"text":123},"6. 訓練側 background (eval と区別すべき)",{"type":58,"items":125},[126,127,128],"\u003Cstrong>training bg default\u003C\u002Fstrong>: \u003Ccode>(0,0,0)\u003C\u002Fcode> + uniform noise ±0.1 (\u003Ccode>config.rs:101-110\u003C\u002Fcode>) — つまり毎 iter ランダム black-ish 背景 (\u003Ccode>brush-train\u002Fsrc\u002Ftrain.rs:120-122\u003C\u002Fcode>)","\u003Cstrong>training composite_bg\u003C\u002Fstrong>: \u003Ccode>(has_alpha && background != Vec3::ZERO).then_some(background)\u003C\u002Fcode> — bg=ZERO の iter は composite 無効 (\u003Ccode>train.rs:155\u003C\u002Fcode>)","\u003Cstrong>eval bg は固定 (Vec3::ZERO)\u003C\u002Fstrong>、training と異なり noise なし、composite_bg=None で固定",{"type":51,"text":130},"7. その他 audit で確認した default",{"type":132,"columns":133,"align":137,"rows":139},"table",[134,135,136],"項目","値","source",[138,138,138],"left",[140,144,148,152,156,160,164,168,171,175,179,183,187,191,195],[141,142,143],"SH degree","3","config.rs:10 (`sh_degree` default)",[145,146,147],"total_train_iters","30000","brush-train\u002Fconfig.rs:9",[149,150,151],"lr_mean","2e-5 → 2e-7 (linear)","config.rs:16-21",[153,154,155],"lr_coeffs_dc","2e-3","config.rs:29",[157,158,159],"lr_coeffs_sh_scale","10.0 (band 1+ は \u002F10)","config.rs:33",[161,162,163],"lr_opac","0.012","config.rs:37",[165,166,167],"lr_scale","7e-3 → 5e-3","config.rs:41-45",[169,154,170],"lr_rotation","config.rs:49",[172,173,174],"ssim_weight (training)","0.2 (L1 0.8 + SSIM 0.2)","config.rs:80",[176,177,178],"match_alpha_weight (training)","0.1","config.rs:92",[180,181,182],"lpips_loss_weight (training)","0.0 (default off)","config.rs:95",[184,185,186],"growth_grad_threshold","0.0025","config.rs:62",[188,189,190],"refine_every","200 iter","config.rs:58",[192,193,194],"growth_stop_iter","15000","config.rs:71",[196,197,198],"max_splats cap","10,000,000","config.rs:53",{"type":51,"text":200},"8. splat-rs と diff すべき観点 (12 axes、次 phase A.2\u002FA.3 で使用)",{"type":58,"ordered":71,"items":202},[203,204,205,206,207,208,209,210,211,212,213,214],"\u003Cstrong>(1) 評価時 background color\u003C\u002Fstrong>: brush は \u003Ccode>Vec3::ZERO\u003C\u002Fcode> 固定。splat-rs は? 白背景に composite してから PSNR を取っているなら \u003Cstrong>機械的に -3〜-6 dB\u003C\u002Fstrong>","\u003Cstrong>(2) GT premultiplication\u003C\u002Fstrong>: brush は AlphaMode::Transparent で byte 空間 premultiply。splat-rs は? premultiply せず素の RGB を使う場合、α=0 の透明領域で GT≠0 のノイズ寄与が残る","\u003Cstrong>(3) loss 対象 channel\u003C\u002Fstrong>: brush eval は RGB 3 channel のみ (α 対象外)。splat-rs が α も MSE に含むなら分母が H·W·4 となり PSNR が下がる","\u003Cstrong>(4) MSE 平均次元\u003C\u002Fstrong>: brush は \u003Ccode>H·W·3\u003C\u002Fcode> 全 voxel 均等平均。splat-rs が per-channel mean してから平均する場合、結果は同一 (mean は結合的) だが、もし「per-channel PSNR → 平均」してると 違う値になる","\u003Cstrong>(5) PSNR MAX 値\u003C\u002Fstrong>: brush は \u003Ccode>MAX²=1\u003C\u002Fcode> 暗黙 ([0,1] f32 前提)。splat-rs が \u003Ccode>MAX=255\u003C\u002Fcode> ([0,255] 想定) で計算してると \u003Cstrong>+48 dB\u003C\u002Fstrong> シフト (検出は容易、無いはず)","\u003Cstrong>(6) log base\u003C\u002Fstrong>: brush は \u003Ccode>log10\u003C\u002Fcode>。万が一 \u003Ccode>log2\u003C\u002Fcode> や自然対数を使ってると数値が大きくズレる","\u003Cstrong>(7) 8-bit roundtrip\u003C\u002Fstrong>: brush は pred を量子化してから MSE。splat-rs が量子化しないなら f32 精度誤差で \u003Cstrong>わずかに高め\u003C\u002Fstrong>、量子化込みなら同条件","\u003Cstrong>(8) test split source\u003C\u002Fstrong>: brush は \u003Ccode>transforms_test.json\u003C\u002Fcode> の \u003Cstrong>全 200 view\u003C\u002Fstrong>。splat-rs が \u003Ccode>eval_split_every\u003C\u002Fcode> で train を間引いてる場合、評価対象が異なり single-number 比較不可","\u003Cstrong>(9) resolution\u003C\u002Fstrong>: brush は \u003Ccode>max_resolution=1920\u003C\u002Fcode> → NeRF Synthetic 800×800 は no-op。splat-rs が downsample してると別件","\u003Cstrong>(10) tonemap \u002F sRGB \u002F gamma\u003C\u002Fstrong>: brush は線形空間で素直に比較、tonemap なし。splat-rs に色空間変換が挟まると別問題","\u003Cstrong>(11) AlphaMode 経路\u003C\u002Fstrong>: brush は CLI \u003Ccode>--alpha-mode=masked\u003C\u002Fcode> で \u003Ccode>Masked\u003C\u002Fcode> に切替可能 (premultiply しない、loss-map に \u003Ccode>gt.a\u003C\u002Fcode> を掛ける)。\u003Cstrong>brush の公式 37.4 dB がどちらで取られたかは要確認\u003C\u002Fstrong> (主仮説の崩壊条件)","\u003Cstrong>(12) SSIM kernel\u003C\u002Fstrong>: brush は 11-tap 可分離 Gaussian, C1=1e-4, C2=9e-4, clamp [-1,1]。SSIM 比較する場合は constant と blur 半径を一致させる",{"type":51,"text":216},"9. 主仮説と検証手順 (A.2 + A.3 で実行)",{"type":46,"label":218,"variant":48,"text":219},"主仮説","\u003Cstrong>「brush 37.4 dB と splat-rs 24.879 dB の gap のうち 3〜6 dB は eval convention 差 (premultiplied + bg=black vs composite-on-white) で説明可能」\u003C\u002Fstrong>。残り 6〜9 dB が真の algorithmic gap。",{"type":58,"ordered":71,"items":221},[222,223,224,225],"\u003Cstrong>A.2\u003C\u002Fstrong>: splat-rs 側の eval (PSNR 計算箇所、bg 処理、test split、α handling) を本 doc と同じ axes で audit","\u003Cstrong>A.3\u003C\u002Fstrong>: 同 ckpt (brush 学習結果 .ply) を \u003Cstrong>splat-rs の eval pipeline で評価\u003C\u002Fstrong> し直し、何 dB 出るかを測る (両 trainer の output を同 eval で揃える)","\u003Cstrong>A.3b\u003C\u002Fstrong>: 同 GT を \u003Cstrong>brush の eval convention で再計算\u003C\u002Fstrong>: splat-rs render 結果を premultiplied-α + bg=black で PSNR 取り直し、何 dB shift するか測る","\u003Cstrong>A.3c\u003C\u002Fstrong>: brush の actual run command (CLI args) を README\u002Fscript から探し、\u003Ccode>--alpha-mode\u003C\u002Fcode> がどう設定されてたか確認 (axis 11 の主仮説崩壊条件 check)",{"type":51,"text":227},"10. 不明点 (深掘りせず次 phase に渡す)",{"type":58,"items":229},[230,231,232,233],"brush の公式 37.4 dB report がどの commit \u002F CLI args で取られたかは brush repo 内 README\u002FCI スクリプトを A.2 で確認する必要あり (本 audit では特定せず)","brush の \u003Ccode>render_splats\u003C\u002Fcode> 内 α-blend の precise 数式 (front-to-back vs back-to-front, premultiplied output か否か) は \u003Ccode>brush-render-bwd\u003C\u002Fcode> 内の kernel を見ないと完全確認できないが、bg=Vec3::ZERO + Transparent GT で eval する設計上 \u003Cstrong>render output は premultiplied (α合成済み RGB)\u003C\u002Fstrong> と推定 (要 A.2 で kernel 確認)","brush の LOD 機能 (\u003Ccode>lod_levels\u003C\u002Fcode>) が 30k run でどう作用するか — default 0 (off) なので lego sh3 30k は plain train + 30k iter のみと推定するが要確認","training 側で毎 iter ランダム bg を使うが eval は固定 bg=ZERO — この「training\u002Feval bg mismatch」が brush 側で意図的設計か、暗黙の bias なのかは brush DESIGN doc にあたる必要あり (本 audit scope 外)",{"type":51,"text":235},"11. file:line index (本 audit で読んだ source)",{"type":132,"columns":237,"align":240,"rows":241},[238,239],"役割","path:line",[138,138],[242,245,248,251,254,257,260,263,266,269,272,275,278,281,284],[243,244],"eval entry (PSNR\u002FSSIM)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-train\u002Fsrc\u002Feval.rs:22-63",[246,247],"eval loop (split iteration + 平均)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-process\u002Fsrc\u002Ftrain_stream.rs:455-513",[249,250],"GT premultiply","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-dataset\u002Fsrc\u002Fscene.rs:212-232",[252,253],"GT pack to u32 RGBA","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-dataset\u002Fsrc\u002Fscene.rs:239-262",[255,256],"AlphaMode 決定","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-dataset\u002Fsrc\u002Fscene.rs:38-62",[258,259],"AlphaMode enum (default Masked)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-render\u002Fsrc\u002Flib.rs:107-115",[261,262],"image_loss_eval (forward-only)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-loss\u002Fsrc\u002Flib.rs:1090-1100",[264,265],"L1 + SSIM kernel forward","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-loss\u002Fsrc\u002Flib.rs:163-341",[267,268],"read_gt (byte → [0,1] f32)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-loss\u002Fsrc\u002Flib.rs:107-126",[270,271],"ImageLossConfig","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-loss\u002Fsrc\u002Flib.rs:686-692",[273,274],"NeRF Synthetic split loader","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-dataset\u002Fsrc\u002Fformats\u002Fnerfstudio.rs:214-308",[276,277],"LoadDataseConfig (max_resolution etc.)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-dataset\u002Fsrc\u002Fconfig.rs:13-34",[279,280],"TrainConfig defaults","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-train\u002Fsrc\u002Fconfig.rs:7-146",[282,283],"render_splats (eval render)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-render\u002Fsrc\u002Fgaussian_splats.rs:252-314",[285,286],"training step (training bg \u002F composite)","\u002FUsers\u002Fotkrickey\u002Fdev\u002Fbrush\u002Fcrates\u002Fbrush-train\u002Fsrc\u002Ftrain.rs:101-214",{"type":51,"text":288},"関連",{"type":58,"items":290},[291,292,293,294,295],"P1.A.2 splat-rs eval audit (並列 subagent が作成中): \u003Ccode>p1-a-2-splat-rs-eval-audit\u003C\u002Fcode>","P1.A.3 同 ckpt cross-eval: \u003Ccode>p1-a-3-cross-eval-bridge\u003C\u002Fcode> (本 audit + A.2 の checklist を使い実測)","既存 brush vs splat 37 dB gap analysis: \u003Ccode>brush-vs-splat-37dB-gap-analysis.md\u003C\u002Fcode>","brush bench 実測: \u003Ccode>m4-brush-bench\u003C\u002Fcode>","NeRF Synthetic multi-scene results: \u003Ccode>a-4-nerf-synthetic-scene-results\u003C\u002Fcode>",[],[298,325],{"id":34,"title":299,"date":9,"status":10,"polarity":300,"category":301,"axes":302,"tags":303,"task_code":310,"related_runs":311,"delta_psnr":320,"delta_wallclock":321,"rank":37,"verdict":322,"impact_summary":323,"detail_path":324},"A.4 NeRF Synthetic 他シーン展開 — 8 シーン complete 30k 結果","mixed","experiment",[14],[304,305,306,20,307,308,309],"phase-5","nerf-synthetic","multi-scene","scene-dependency","evaluation","8-scenes","A.4",[312,313,314,315,316,317,318,319],"lego-sh3-30k","chair-30k","ficus-30k","drums-30k","hotdog-30k","mic-30k","materials-30k","ship-30k","-5.93 dB (8 シーン平均 18.95 vs lego 24.879、std ±6.0)","21-29 min (シーン非依存的、materials のみ +5 min)","partial","8 シーン complete (lego + 7 新規) 30k 完遂。シーン依存性が PSNR で 17.6 dB の幅 (materials 12.71 〜 hotdog 30.29)、mean 18.95 ± 6.0 dB。本実装の brush SoTA 比 gap は scene-dependent で -7.4 dB (hotdog) 〜 -22.3 dB (ficus 含む)。共通要因仮説: SfM init.ply の sparsity (細い枝 \u002F マイク \u002F 反射 PBR で薄い) + refine grad_threshold の lego\u002Fhotdog tuning over-fit。卒論 evaluation で「lego baseline + multi-scene mean ± std」併記必須。","\u002Ffindings\u002Fa-4-nerf-synthetic-scene-results\u002F",{"id":32,"title":326,"date":327,"status":10,"polarity":300,"category":301,"axes":328,"tags":329,"task_code":335,"related_runs":336,"delta_psnr":337,"delta_wallclock":338,"rank":37,"verdict":339,"impact_summary":340,"detail_path":341},"M4 Max 上 brush (wgpu→Metal) の 30k baseline — wgpu 抽象は自作より速かった","2026-05-23",[15],[330,19,331,332,333,334],"phase-2","wgpu","baseline","m4-max","abstraction-cost","A.3",[312],"+11.13 dB (brush 比優位)","−65.6% (brush の方が速い)","investigative","wgpu 抽象は自作 native より遅いはず、という想定が逆。同一 M4 Max 上で brush (wgpu) が 9m08s \u002F 37.40 dB、自作 (Metal 直) が 26m32s \u002F 26.27 dB。第 2 軸 (抽象コスト定量化) の主張を再 framing する必要が確定。","\u002Ffindings\u002Fm4-brush-bench\u002F",1782449788629]