Skip to content

スプライトのゴールデン画像がマシン依存で CI に落ちる問題を修正 - #43

Merged
temoki merged 1 commit into
mainfrom
fix-sprite-golden-determinism
Aug 4, 2026
Merged

スプライトのゴールデン画像がマシン依存で CI に落ちる問題を修正#43
temoki merged 1 commit into
mainfrom
fix-sprite-golden-determinism

Conversation

@temoki

@temoki temoki commented Aug 4, 2026

Copy link
Copy Markdown
Owner

概要

#42 で追加した canvas ゴールデン2件が CI(Build & Test)で失敗していました。マージ後に main でも赤のままなので、その修正です。

失敗は色ズレのみで、幾何は一致していました:

The percentage of pixels that match 0.96484375 is less than required 0.995
The lowest perceptual color precision 0.9559375 is less than required 0.98

既存の16件のシナリオゴールデンは影響を受けていません。

原因

テスト用のスプライト画像を SwiftUI ビューから ImageRenderer で生成していたため、ホストマシンに二重に依存していました。

  1. Color.red / Color.blueセマンティックカラーで、実際の成分値がディスプレイの色域(P3 / sRGB)や外観設定で変わる
  2. ImageRenderer.nsImage が返すビットマップはホストのディスプレイプロファイルでタグ付けされる

既存のシナリオゴールデンがこれを免れていたのは、Color(red:green:blue:) で明示的に構築した sRGB 色のベクター描画しかしていないためです。

対応

  • スプライトを CGImage + Image(decorative:scale:)sRGB の生ピクセルから直接構築し、色解決を経由しないようにしました。記録されたゴールデンのピクセル値が、書き込んだバイト値と完全一致することを確認済みです(赤 220,30,30 / 青 30,30,220)。
  • テストを .tortoiseViewport(.original) に固定し、元ビットマップのサイズをデバイスピクセルに 1:1 で対応させました(scale 2 で 80×80 px を 40×40 のボックスへ、160×80 px を 80×80 のボックスへ)。これで画像のリサンプリングという2つ目のブレ要因も排除しています。

プロダクションコードの変更はありません。TortoiseSprite の仕様・描画結果は #42 のままです。

関連

#42 のフォローアップ

チェックリスト

  • swift test がローカルで通る(120件)
  • xcrun swift-format lint --recursive --strict Sources Tests が通る
  • 新しい挙動に対するテストを追加(該当なし。既存テストの決定性の修正)
  • CHANGELOG.md を更新(該当なし。ユーザーから見える変更なし)
  • ゴールデン画像: 2件を再録し、目視で確認済み(.original 化で構図が変わっています)

The two canvas goldens added with `TortoiseSprite` passed locally but failed
on CI with a pure color shift (perceptual precision 0.956, geometry intact);
the 16 pre-existing scenario goldens were unaffected.

The sprite image was rendered from a SwiftUI view via `ImageRenderer`, which
made it depend on the host machine twice over: `Color.red` / `Color.blue` are
semantic colors whose components vary with display gamut and appearance, and
`ImageRenderer.nsImage` returns a bitmap tagged with the host's display
profile. The scenario goldens escaped this because they only draw vector
paths in explicitly-constructed sRGB colors.

Build the sprite from raw sRGB bytes through `CGImage` +
`Image(decorative:scale:)` instead, so no color resolution happens. The
recorded pixels now come out byte-identical to the values written in.

Also pin the tests to `.tortoiseViewport(.original)` and size the source
bitmaps so they map 1:1 onto device pixels (80x80 px at scale 2 into a 40x40
box; 160x80 into 80x80), removing image resampling as a second source of
cross-machine drift.

Both goldens were re-recorded and visually inspected.
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.57%. Comparing base (31cbc86) to head (cfb0811).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #43      +/-   ##
==========================================
+ Coverage   91.89%   92.57%   +0.68%     
==========================================
  Files          15       16       +1     
  Lines         913      943      +30     
==========================================
+ Hits          839      873      +34     
+ Misses         74       70       -4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@temoki
temoki merged commit 3fd05ff into main Aug 4, 2026
6 checks passed
@temoki
temoki deleted the fix-sprite-golden-determinism branch August 4, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant