進捗(2026-08-18) — ブランチ visionos-viewer、Phase 0〜3 すべて完了(すべて実機確認済み)。PR は #54。
3D の 🐢 には TortoiseGraphics2 2.1.0(temoki/TortoiseGraphics2#46 / #47)が要った。
「注意」の 2 件は両方とも片付いた。 プライバシーポリシーは第 6 節を追加済み、公開タイミングは #55 で pages.yml が手動実行のみになったので、マージしてもサイトはデプロイされない。マージ前の判断は残っていない。
詳細は各フェーズのコメント(Phase 0 / Phase 0 追記 / Phase 1 / Phase 2 / 配置のやりなおし)。
背景
visionOS 対応は #11 で一度「iPad アプリ相当が動く」ところまで作り、PR #52 に退避した。
動くには動くが、それが visionOS である理由がどこにもない。同じことは Apple の
iPad 互換モードが最初からやっている。そして退避した残課題
- メインウィンドウからファイルを選び直せない(
dismiss() はシステムの「書類」
ボタンを見せるだけで、openDocument / newDocument は visionOS で unavailable)
- 表示の切り替えで前の View が背景に残る(実機のみ・アプリ全域)
は、どちらも DocumentGroup と編集 UI に由来する。回避策を探し続けるより、
その機能を持たない設計にした方が早い。
一方で、visionOS でしかできないことが一つある。**タートルグラフィックスの
タートルは、元々は床を走って紙にペンで描く実物のロボットだった。**画面の中の
三角形の方が後から出てきた代用品で、机の上を 🐢 が這って線を引くのは 3D 化の
こじつけではなく原点回帰にあたる。
そこで visionOS 版は iPad/macOS 版の移植をやめ、.tortoise のビューワーとして
作り直す。
仕様案
全体像
visionOS 版は「編集しないアプリ」。3 つの面で構成する。
| 面 |
中身 |
| ImmersiveSpace(mixed) |
現実の平面に貼りついたキャンバス。その上を 🐢 が動いて描く |
| ウィンドウ 1 |
ブロック(読み取り専用)。再生位置に合わせてハイライト |
| ウィンドウ 2 |
生成された Swift コード(Phase 3、任意) |
iPad ではキャンバス/コードをトグルで切り替えているが、visionOS には面が
いくらでもある。ブロックと絵を同時に見せられるのは iPad 版より良い版であって、
機能を削った版ではない。ここが visionOS 版の存在理由になる。
ドキュメントの入口 — DocumentGroup は使わない
.fileImporter を持つ普通の WindowGroup にする。read-only なら
DocumentGroup(viewing:) という手もあるが、#52 で踏んだ「書類選択に戻れない」
問題は viewing でも同じはずなので採らない。素の fileImporter なら 1 タップで
開き直せて、あの残課題がそのまま消える。
サンプル作品をアプリに同梱する。 現状ドキュメントは iCloud ではなく
「On My iPad」のローカルフォルダに保存されるので、Vision Pro には AirDrop 等で
手で持っていくことになる。同梱しないと初回起動が空のビューワーになる。
appstore/screenshot-sources/ に既に 2 つある。
描画をどう 3D にするか — Phase 0 で実機を見て決める
- (a) テクスチャ方式 — 既存の 2D 描画をテクスチャ(
TextureResource.DrawableQueue)
にして平面エンティティに貼る。既存資産をほぼ全部再利用でき、安い。
拡大したときの解像度が頭打ちになる。
- (b) メッシュ方式 — ストロークをリボン/チューブのジオメトリにする
(LowLevelMesh 等)。拡大に強く「ペンを上げたら浮く」表現もできるが、
fill の三角形分割まで自前になる。
判断材料は「テクスチャ方式で見られる絵になるか」「10,000 コマンドで毎フレーム
更新が保つか」の 2 点だけ。まずアプリ側で試作する。 upstream-first からすると
RealityKit レンダラは TortoiseGraphics2 の話に見えるが、スケールとアンカーは
アプリ固有なので、一般化できると分かってから upstream に上げる。
ハイライトは「できれば」ではなく中核
これが無いと visionOS 版は「絵が出てくるのを眺めるもの」でしかない。ブロックと
絵の対応こそがこのアプリの教育的な核で、それを一番よく見せられるのが visionOS。
実装は既存の不変条件にただ乗りする。RunnerModel.currentBlockID は
expandedBlockIDs[player.currentCommandIndex] で、これは Tortoise.apply が
入力ストリームを index 対応でそのまま記録することに依存している(Kit の
往復テストで固定済み)。
設計上の制約: CommandPlayer はタイムラインとして残し、差し替えるのは
描画側だけにする。そうすればハイライトは自動的についてくる。新レンダラが
独自のタイムラインを持つと、この対応が壊れる。
スケールと単位
キャンバスはポイント、机はメートル。描画のバウンディングボックス
(DrawingBounds.compute は public)を、検出した平面上の一定サイズ(60cm 角程度)に
収める写像を 1 箇所に置く。PNG 書き出しの exportFrameSize と同じ考え方。
この写像は UI に依存しないので Kit 側に置いてテストする。
ターゲット構成 — 増やさない
App ターゲット 1 本のまま #if os(visionOS) で Scene ツリーだけ差し替える。
別ターゲットにすると Support/Info.plist の UTType 宣言が二重になり、署名も増える。
TortoiseBlocksApp は既に #if os(iOS) で LaunchScene を出し分けている。
バンドル ID が同じなら universal purchase も自然に効く。
やること
Phase 0 の結論が出るまで Phase 1 以降の見積もりは意味を持たない。
Phase 0 — 実機スパイク(捨てる前提)
Phase 1 — ビューワーの骨格
Phase 2 — ブロックの窓(ここまでで初めて「このアプリ」になる)
Phase 3 — 仕上げ
3 つは互いに独立しているので着手順は自由。アセット待ちの 🐢 を後回しにするのが動かしやすい。
受け入れ条件
Phase 0:
Phase 1–3:
注意(先に潰しておくもの)
プライバシーポリシーが 1 行だけ嘘になる。(NSWorldSensingUsageDescription はブランチで追加済み。site/privacy.html と App Store の質問票はまだ) 平面検出には
NSWorldSensingUsageDescription の許諾が要る。カメラ映像がアプリに渡るわけでは
ないが、site/privacy.html は現在「位置情報、連絡先、写真、カメラ、マイクへの
アクセス」なしと書いており、App Store のプライバシー質問票も同じ申告になっている。
「端末内で完結し、送信も保存もしない」と書き足せば済む話だが、忘れると審査で
止まる種類のもの。release skill の管轄。
3D の 🐢 アセットが要る。 現状は 23×32px の 2D 画像。USDZ の用意はアイコンと
同じく手作業になるので、Phase 1 は既存スプライトのビルボードで逃げて、
アセット待ちで止まらないようにする。
検証は実機でしかできない。 シミュレータに現実の平面はない。CI はビルドまで、
目視は毎回ヘッドセット。座標写像のように Kit へ落とせるロジックは落としておく。
対象年齢が噛み合わない。 Vision Pro は Apple 自身が 13 歳以上を想定している。
visionOS 版の位置づけは「大人が見せる/教室で見せる」で、site が「保護者と先生に
向けて書く」方針なのとは合う。ストア文言を iOS からそのままコピーしない理由になる。
PR #52 の扱い
この issue は #52 を置き換えるが、#52 の成果のうちプラットフォーム基盤は
そのまま活きる。
活きるもの:
project.pbxproj の SUPPORTED_PLATFORMS / XROS_DEPLOYMENT_TARGET /
TARGETED_DEVICE_FAMILY = "2,7"
- 署名スコープの修正(ad-hoc を
[sdk=macosx*] に限定。これは実機デバッグを
塞いでいたバグ修正なので、この issue と切り離して main に入れてよい)
- CI の visionOS ビルド、fastlane の
platform :visionos、metadata_check.rb の
3840×2160
- ストア文言・LP の visionOS 記述(出荷するまで公開しないこと。文面は 3 つの
リストで共有されているので、公開すると iOS/macOS のリストまで Vision Pro 対応を
名乗ってしまう)
捨てるもの:
- 3 ペインのカラム幅調整、
documentBrowserToolbar(削除済み)、コードペインの白紙化
(コードの窓を作るなら Phase 3 で参照する)
引き継ぐ知見:
hoverEffect と draggable を同じ View に付けると visionOS で segfault する
(効果を ButtonStyle 側に 1 段下げると通る)
ToolbarSpacer は visionOS に無い
- 表示切り替えの残像は実機のみ・アプリ全域。ビューワーでも踏む可能性があるので
Phase 1 で再現するか確認し、するなら Feedback Assistant へ
Refs #11, #52
背景
visionOS 対応は #11 で一度「iPad アプリ相当が動く」ところまで作り、PR #52 に退避した。
動くには動くが、それが visionOS である理由がどこにもない。同じことは Apple の
iPad 互換モードが最初からやっている。そして退避した残課題
dismiss()はシステムの「書類」ボタンを見せるだけで、
openDocument/newDocumentは visionOS で unavailable)は、どちらも DocumentGroup と編集 UI に由来する。回避策を探し続けるより、
その機能を持たない設計にした方が早い。
一方で、visionOS でしかできないことが一つある。**タートルグラフィックスの
タートルは、元々は床を走って紙にペンで描く実物のロボットだった。**画面の中の
三角形の方が後から出てきた代用品で、机の上を 🐢 が這って線を引くのは 3D 化の
こじつけではなく原点回帰にあたる。
そこで visionOS 版は iPad/macOS 版の移植をやめ、
.tortoiseのビューワーとして作り直す。
仕様案
全体像
visionOS 版は「編集しないアプリ」。3 つの面で構成する。
iPad ではキャンバス/コードをトグルで切り替えているが、visionOS には面が
いくらでもある。ブロックと絵を同時に見せられるのは iPad 版より良い版であって、
機能を削った版ではない。ここが visionOS 版の存在理由になる。
ドキュメントの入口 — DocumentGroup は使わない
.fileImporterを持つ普通のWindowGroupにする。read-only ならDocumentGroup(viewing:)という手もあるが、#52 で踏んだ「書類選択に戻れない」問題は viewing でも同じはずなので採らない。素の fileImporter なら 1 タップで
開き直せて、あの残課題がそのまま消える。
サンプル作品をアプリに同梱する。 現状ドキュメントは iCloud ではなく
「On My iPad」のローカルフォルダに保存されるので、Vision Pro には AirDrop 等で
手で持っていくことになる。同梱しないと初回起動が空のビューワーになる。
appstore/screenshot-sources/に既に 2 つある。描画をどう 3D にするか — Phase 0 で実機を見て決める
TextureResource.DrawableQueue)にして平面エンティティに貼る。既存資産をほぼ全部再利用でき、安い。
拡大したときの解像度が頭打ちになる。
(
LowLevelMesh等)。拡大に強く「ペンを上げたら浮く」表現もできるが、fill の三角形分割まで自前になる。
判断材料は「テクスチャ方式で見られる絵になるか」「10,000 コマンドで毎フレーム
更新が保つか」の 2 点だけ。まずアプリ側で試作する。 upstream-first からすると
RealityKit レンダラは TortoiseGraphics2 の話に見えるが、スケールとアンカーは
アプリ固有なので、一般化できると分かってから upstream に上げる。
ハイライトは「できれば」ではなく中核
これが無いと visionOS 版は「絵が出てくるのを眺めるもの」でしかない。ブロックと
絵の対応こそがこのアプリの教育的な核で、それを一番よく見せられるのが visionOS。
実装は既存の不変条件にただ乗りする。
RunnerModel.currentBlockIDはexpandedBlockIDs[player.currentCommandIndex]で、これはTortoise.applyが入力ストリームを index 対応でそのまま記録することに依存している(Kit の
往復テストで固定済み)。
スケールと単位
キャンバスはポイント、机はメートル。描画のバウンディングボックス
(
DrawingBounds.computeは public)を、検出した平面上の一定サイズ(60cm 角程度)に収める写像を 1 箇所に置く。PNG 書き出しの
exportFrameSizeと同じ考え方。この写像は UI に依存しないので Kit 側に置いてテストする。
ターゲット構成 — 増やさない
App ターゲット 1 本のまま
#if os(visionOS)で Scene ツリーだけ差し替える。別ターゲットにすると
Support/Info.plistの UTType 宣言が二重になり、署名も増える。TortoiseBlocksAppは既に#if os(iOS)でLaunchSceneを出し分けている。バンドル ID が同じなら universal purchase も自然に効く。
やること
Phase 0 の結論が出るまで Phase 1 以降の見積もりは意味を持たない。
Phase 0 — 実機スパイク(捨てる前提)
PlaneDetectionProviderで平面を取る既存の 2D 描画をテクスチャで机に貼る(上の (a))→ 不要。ViewAttachmentComponentで既存のTortoiseCanvasをそのまま置けるPhase 1 — ビューワーの骨格
WindowGroup+.fileImporter、サンプル同梱(SampleBlocksを流用、追加リソースなし)PlaybackControlsをそのまま再利用)NSWorldSensingUsageDescriptionと、平面が見つからない場合の代替表示(15 秒で諦めて目の前に浮かべる)Phase 2 — ブロックの窓(ここまでで初めて「このアプリ」になる)
Phase 3 — 仕上げ
SVG / PNG 書き出し→ 作ったうえで外した。 安く動いたが、絵を変えられないビューワーが 2 つ目の成果物を書き出すのは、絵を作る場所(iPad / Mac)の仕事。リモコンのボタンも 6 個になっていた3 つは互いに独立しているので着手順は自由。アセット待ちの 🐢 を後回しにするのが動かしやすい。
受け入れ条件
Phase 0:
Phase 1–3:
.tortoiseを開くと、現実の平面の上に絵が現れ、🐢 が動いて描く注意(先に潰しておくもの)
プライバシーポリシーが 1 行だけ嘘になる。(
NSWorldSensingUsageDescriptionはブランチで追加済み。site/privacy.htmlと App Store の質問票はまだ) 平面検出にはNSWorldSensingUsageDescriptionの許諾が要る。カメラ映像がアプリに渡るわけではないが、
site/privacy.htmlは現在「位置情報、連絡先、写真、カメラ、マイクへのアクセス」なしと書いており、App Store のプライバシー質問票も同じ申告になっている。
「端末内で完結し、送信も保存もしない」と書き足せば済む話だが、忘れると審査で
止まる種類のもの。release skill の管轄。
3D の 🐢 アセットが要る。 現状は 23×32px の 2D 画像。USDZ の用意はアイコンと
同じく手作業になるので、Phase 1 は既存スプライトのビルボードで逃げて、
アセット待ちで止まらないようにする。
検証は実機でしかできない。 シミュレータに現実の平面はない。CI はビルドまで、
目視は毎回ヘッドセット。座標写像のように Kit へ落とせるロジックは落としておく。
対象年齢が噛み合わない。 Vision Pro は Apple 自身が 13 歳以上を想定している。
visionOS 版の位置づけは「大人が見せる/教室で見せる」で、site が「保護者と先生に
向けて書く」方針なのとは合う。ストア文言を iOS からそのままコピーしない理由になる。
PR #52 の扱い
この issue は #52 を置き換えるが、#52 の成果のうちプラットフォーム基盤は
そのまま活きる。
活きるもの:
project.pbxprojのSUPPORTED_PLATFORMS/XROS_DEPLOYMENT_TARGET/TARGETED_DEVICE_FAMILY = "2,7"[sdk=macosx*]に限定。これは実機デバッグを塞いでいたバグ修正なので、この issue と切り離して main に入れてよい)
platform :visionos、metadata_check.rbの3840×2160
リストで共有されているので、公開すると iOS/macOS のリストまで Vision Pro 対応を
名乗ってしまう)
捨てるもの:
documentBrowserToolbar(削除済み)、コードペインの白紙化(コードの窓を作るなら Phase 3 で参照する)
引き継ぐ知見:
hoverEffectとdraggableを同じ View に付けると visionOS で segfault する(効果を ButtonStyle 側に 1 段下げると通る)
ToolbarSpacerは visionOS に無いPhase 1 で再現するか確認し、するなら Feedback Assistant へ
Refs #11, #52