コンテンツにスキップ

10. バージョンとエディション戦略

10.1 ターゲットはコンパイル時パラメータ

Section titled “10.1 ターゲットはコンパイル時パラメータ”

ターゲットは (edition, version) の 2 軸で、どちらもソースには書きません。知っているのはバックエン ドだけです (コンパイルモデル)。

バージョン文字列は不透明なラベルとして扱います。 Minecraft のバージョンは従来の semver 風 (1.21.4) の場合も、最新リリース以降の日付ベースの場合もあります。Cairn はバージョン文字列を比較 せず、Mojang が付ける単調増加の整数 DataVersion を正準の順序キーにします。これにより since/until、Vmin/Vmax、@requires、semantic_sensitivity の境界が semver → 日付ベースの移行を またいでも壊れません。

バックエンドは「バージョン文字列 ↔ DataVersion」表を持つので、--target には同じバージョンのどちら の綴りを渡してもかまいません。Bedrock も同様に、バージョン文字列を内部の単調キーに解決します。

10.2 言語の契約: recompile であり transcode ではない

Section titled “10.2 言語の契約: recompile であり transcode ではない”

仕様はバージョンやエディションをまたぐ NBT の可搬性を 保証しません。保証するのは「同じソースを あるターゲットにコンパイルした結果」だけです。

ソースが設計図、.nbt はターゲットに固定されたビルド成果物 (コンパイル済みバイナリ相当) です。新し いバージョンや別のエディションで使うには、ソースを再コンパイルします。

DataFixerUpper は前方向のみ、lossy、かつ不完全です (アイテム、看板、絵画、ブロックエンティティで欠落 が頻発します)。救済ツールであり、言語の意味論には入れません。

解けない残りは隠さず明示します。

  • バージョン間の意味変化 (cauldron の分割、アイテムの tag → components)。
  • データテーブルに無いゲーム挙動 (流体、重力、取り付け、レッドストーン)。
  • 見た目の一貫性 (色温度のドリフト)。
  • 物理規則の変更 (1.21 の wind charge が旧来のトラップを壊す)。

幾何的に正しい NBT は出ますが、ゲーム体験は保証されません。

10.3 バックエンド = データテーブル

Section titled “10.3 バックエンド = データテーブル”

バックエンドには 2 つの供給源があり、両者は分離されています。

機械抽出。 ゲームの --reports / レジストリダンプから取ります。構文とドメインの真実です。ブロッ ク/ エンティティ ID、ブロックステートのプロパティとドメイン、アイテム/コンポーネントのスキーマ、 DataVersion、タグ。誰かの記憶ではなくゲーム自体を真実の源にすることで、新バージョンに対する知識のギ ャップを構造的に解消します。

手書きのバージョンタグ付き制約カタログ。 データに無いもの用です。取り付け (額縁はガラスに掛けら れない)、重力と支持 (砂利、吊りランタン)、流体挙動、エンティティ AABB、レッドストーン。新バージョン ごとに 1 回定義すれば、全ユーザが恩恵を受けます。

constraints:
minecraft:item_frame:
type: entity_attachment
since: "1.13"
targets: { solid_full_face: true, glass_pane: false }
error: "item_frame requires a solid attachable face"
minecraft:lantern:
type: support
states:
hanging=true: { requires_above: solid_or_chain }
hanging=false: { requires_below: solid_top }

正準トークンを主キーにし、各トークンがエディション別の対応 (id + state_map) を持ちます。バージョンは inherits + diffs で畳み込み、Java を基底、Bedrock を上書き差分とします。手書きの意味カタログは 差異のある点だけを記録します。

"@oak_stairs":
base: { states: { half: [bottom,top], shape: [straight,inner_left,inner_right,outer_left,outer_right] } }
mappings:
java: { id: minecraft:oak_stairs, base: "1.13" }
bedrock: { id: minecraft:oak_stairs, state_map: { half=top: {upside_down_bit: true} }, dropped_states: [shape] }
sensitivity:
- { edition: bedrock, kind: missing_state, state: shape, reason: "no inner/outer stair shape" }

10.4 Fail-loud と最小バージョン推定

Section titled “10.4 Fail-loud と最小バージョン推定”

未知 ID、ドメイン外の状態、パリティのギャップはハードエラーです。サイレント置換と暗黙の削除は禁止で す。エラーはターゲットで有効な候補の閉集合、最小バージョン、推奨修正を返します。モデルを自身の記憶で はなくレジストリ由来の候補へ引き戻すためです。

ドメイン外の状態はまだ強制されていません。コンパイラが各ブロックのステートの表を持たないため、下の E_STATE_DOMAIN は未実装です。それまでステートリテラル (構文) は書かれたまま 出力され、どれにも代わりに W_STATE_LITERAL_UNCHECKED (Lint) が付きます。

E_UNKNOWN_ID line 12: "minecraft:pale_oak_planks" not in 1.21.4 registry.
Similar valid: minecraft:oak_planks, minecraft:dark_oak_planks, minecraft:cherry_planks
E_VERSION_CAP line 7: minecraft:cherry_planks introduced in 1.20 (target 1.19.4).
Fix: --target >=1.20, or slot decor -> @oak_planks
E_STATE_DOMAIN line 18: wall north=true invalid for 1.21.4. Valid: none, low, tall (changed from boolean in 1.16).
Suggested DSL: wall_segment id=yard_wall connect_north=low
E_PARITY_UNSUPPORTED line 8: text_display is Java-only (since 1.19.4); Bedrock has no display entity.
Suggested: sign side=front text="Inn", or slot+theme fallback, or @edition java guard

未知 ID は「どのレジストリ」に対して未知なのか

Section titled “未知 ID は「どのレジストリ」に対して未知なのか”

ID はエディション全体ではなく、コンパイルが固定した 1 つの (edition, version) のブロック表に対して 照合されます。Bedrock 1.21.0 は石レンガを stonebrick、1.21.40 は stone_bricks と綴るので、エディ ション単位で答えるとどちらもどこでも通り、どちらの誤りも捕まえられません。表はレジストリパックの blocks コンポーネントに入り、§10.3 の inherits + diffs で畳 み込まれます。

したがってこの検査が走るのはバージョンを固定した場所だけです。すなわち cairn compile --target と、 同じ組を固定して同じパスを走らせつつ何も書かない cairn check --edition E --target V の 2 つです。 cairn info と cairn lower は lowering はしますがバージョンを固定しません (info は意図的に範囲 全体を報告します)。作者に代わってバージョンを選ぶことはせず、比較を飛ばします。--target の無い cairn check は block-array lowering 自体を走らせないので、 E_UNKNOWN_ABSTRACT_TOKEN を含む lowering 段の診断はそもそも届きません。

エディションが出荷する すべて のバージョンに照合し、どれにも存在しない ID だけを拒否する方式なら フラグは要りませんが、それは別の問いへの答えです。stone_bricks は Bedrock のどこかには存在するので、 1.21.0 に固定したビルドは何も知らされないままになります。固定こそが、答えを「いま作っているビルドに ついて真」にするものです。

誤った ID の届き方は 2 通りあるので、推奨修正にも 2 つの半分があります。タイポ は同じ表に対する 距離検索が答えます (oak_plank には oak_planks を返します)。リネーム はタイポではありません。 Bedrock は Java の light を light_block_0 … light_block_15 と綴り、8 編集離れています。タイポ 検索を健全に保つ閾値では、この 2 つは決して結び付きません。リネームはレジストリパックの aliases コンポーネントが答え、パックに行がある場合に限ります。行が無ければ、無関係な最近傍ブロックを提示する 代わりに「候補は無い」と明言します。

aliases の 1 行は 綴りのグループ です。1 つのブロックがまとってきた名前を、エディションをまたい で、また 1 つのエディション自身の範囲をまたいで並べたもので、1 つの古い綴りが分裂した複数の ID も含み ます。

{ "spellings": ["light", "light_block", "light_block_0", "light_block_1"] }

行のどこにも、どの綴りがどの (edition, version) のものかは書かれていません。それはすでに blocks コンポーネントがバージョン単位で知っているからで、照合は「グループを取り、固定したターゲットが宣言す るメンバーだけを残す」になります。だからこそ 1 組の行が Java → Bedrock (oak_sign → standing_sign) と Bedrock 1.21.0 → 1.21.40 (stonebrick → stone_bricks) の双方に答えられます。答えは §10.4 が求 める閉じた候補集合であって、そこからの選択ではありません。Bedrock 1.21.60 の @light には 16 段階の 明度すべてを返します。1 つを選ぶことは、この節が禁じる暗黙の置換だからです。

この鍵で表現できないのは、両エディションが宣言していて意味が異なる綴りです (Bedrock の snow は Java の snow_block、Java の snow は Bedrock の snow_layer)。この組には行を与えず、背後ではタイ ポ検索が働き続けます。

同じバージョン単位のスコープは、パック自身のマテリアル対応にも適用されます。エントリは綴りが異なるバー ジョンを名指す overrides を持てます。これが 1 つの @floor.stone.smooth をリネームをまたぐ範囲で 解決させています。since の側はまだ保留です。表が記録するのはあるバージョンがどの ID を 持つか で あって、どのバージョンで導入されたかではないので、上の E_VERSION_CAP の例はレジストリ推定ではなく @requires の下限です。

構成要素は自身の下限を宣言できる

Section titled “構成要素は自身の下限を宣言できる”

def と theme は独立した行として requires version>=X を宣言できます。合成物の最小バージョンは構 成要素の最大値です。

def cottage size=9x7:
requires version>=1.21.4
walls mat_slot=wall height=4

式はエディションスコープを含めて @requires と同じものです。2 つの綴りが違うのは何を述べるかではな く、何を制約するかです。モジュールレベルの @requires は ファイル に対する下限で、こちらは 構成要 素 に対する下限です。したがって place use=cottage はそれを継承し、テンプレートのライブラリは利用側 がその都度言い直さなくても自身の要求を持ち運べます。

ビルドがどの構成要素から継承するか。 place use= が名指す def と、スコープが束縛する theme です。どこからもインスタンス化されない構成要素は何も寄与しません。どの place も名指さない def は ボクセルを 1 つも作らず (すでに W_UNUSED_DEF です)、それを理由にターゲットを拒否することは、作者が ファイルに残しただけのテンプレートを理由に拒否することになります。

theme の下限は theme が束縛された時点で適用されます。 メンバがそこからスロットを読むかどうかは問 いません。theme を束縛することが、その宣言を引き受ける行為だからです。もう一方の案 — 規則が実際に発火 してはじめて下限を課す — は、どのセレクタが一致したか、ピンがどのバリアントを選んだかに下限を依存さ せます。すると同じソースが、エディションとは無関係な理由で Java では 1.21 を要求し Bedrock では何も 要求しない、という事態になります。さらに危険な側に倒れます。過剰に適用された下限はそれを設定した行の 位置とともに報告され、修正は 1 箇所です。一方、適用され損ねた下限は、ファイル自身が排除しているビルド を検証済みにしてしまいます。

theme を束縛するものは 2 つで、どちらもビルドが lowering するスコープです。1 つは place ... theme=NAME の参照 (theme= は place に必須なので、これは同時にその def をインスタン ス化するものでもあります)。もう 1 つはモジュールレベルの自動選択で、配置なしにビルドが lowering する 唯一のスコープである struct に対して読まれます。自動選択は唯一の theme をすべての def スコープに も束縛しますが、下限はそこには追随 しません。どの place も名指さない def は何も作らないので、そ うした def があることを理由に theme の下限を課すと、同じ def を「theme の下限を引き受ける程度には インスタンス化されている」かつ「自身の下限を課される程度にはインスタンス化されていない」と読むことに なります。配置された def は、その配置自身の theme= を通じて theme に到達します。

struct と site はこの行を取りません。どちらも何かからインスタンス化されるものではなく、それ自体が ビルドです。そのため内側に書かれた下限は、それが置かれたファイルそのものを制約することになり、それは @requires がすでに述べていることです。メンバ自身のインデントされた子についても同じです。下限は構成 要素のものであり、walls の行は構成要素ではありません。この 2 つの拒否はメッセージが別です。修正が違 うからで、一方は @requires を、もう一方はインデントを 1 段戻すことを指します。どちらも語だけを理由に 拒否はしません。下限を読まない本体では requires は通常のキーワードのままなので、そう綴られたメンバ行 は struct / site / メンバの下では、この行が存在する前とまったく同じようにパースされます。

def と theme の本体はその裏返しで、語の後に何が続こうとその語を取ります。行は下限であり、下限とし て読めない式は member ではなく E_INVALID_REQUIRES になります。これは何も損ないません。requires は そもそも member キーワードではないので、同じ行は以前から E_UNKNOWN_KEYWORD でした。

E_VERSION_CAP は数値だけでなく、下限を課した構成要素も名指します。テンプレート内部に書かれた下限に よって拒否されたターゲットは、裸のバージョン番号だけでは対処できません。修正すべき場所は、それを継承 した place use= の反対側だからです。

モジュールの下限は積で合成されます。cairn compile --target はそのビルドに適用されるすべての @requires 行に照らされ、いずれか 1 つでも下回れば E_VERSION_CAP です。成果物を用意する前に報告 されるので、拒否されたビルドは構造ファイルもロックも残しません。この順序が肝心です。ロックは検証済み の内容を記録するもので、ソース自身が排除しているターゲットに対して verified: true と言ってはならな いからです。

@intended_targets (§5.3) は検証記録ではなく願望で、その上にある下限は 制約です。ファイルは両立しない形で両方を書けます — @requires version>=1.21 の隣に @intended_targets ["1.20.4"] — し、下限が強制される前はそれは 2 つの不活性な文でした。いまは一方が ビルドを決め、もう一方は決めません。これは最悪の配置です。指示のように読めるヘッダの方が無視されてい るからです。

そこで、ヘッダが名指す各バージョンをターゲットのエディションの表に位置づけ、3 通りのいずれかで答えま す。

そのバージョン 所見
そのエディションのどの --target でも名指せない: パックがブロックデータを同梱していないリリース (Java の 1.19)、または表が位置づけられないラベル (Java の 1.21.40) W_INTENDED_TARGET_UNSUPPORTED
そのエディションがビルドできるバージョンで、ファイルの宣言する下限を下回る ビルドできるものすべて がそうなら E_INTENDED_TARGET_CAP、一部 なら W_INTENDED_TARGET_CAP
そのエディションがビルドでき、すべての下限以上 何もなし

この順序は意図的です。コンパイラがビルドできないバージョンは、下限が何を言おうとまずそれとして報告さ れます。--target 1.19 が存在しないことこそ作者が動く事実であり、その隣に cap を並べれば、ビルドを止 めているのではない下限を編集させることになるからです。2 つの cap コードの分かれ目は種類ではなく及ぶ範 囲です。「対象だと言っているものが 1 つもビルドできない」は、2 つの宣言の矛盾が取りうる最も強い読みで あり、そう意図した作者はいません。一方、片端が下限を越えて伸びているリストは広く述べすぎた願望で、下 限より上に名指されたものは変わらずビルドできます。

「すべて」が数えるのは、そのエディションがビルドできるバージョンだけです。 そのエディションのどの --target にも載らない名前は、リストのどの部分の答えにもなりません。したがって version>=1.21 のもと での @intended_targets ["1.20.4", "1.19"] は 2 つ目ではなく 1 つ目です。Java がビルドできるのは 2 つ のうち 1 つだけで、下限がそれを拒否しているからです。1.19 を数に入れれば、そもそもビルドできなかった バージョンが「何もビルドできないファイル」を「半分だけの問題」として報告させることになります。そして それは unsupported の側で別途報告され、修正すべき場所もそちらにあります。

どの下限が効くかは §10.4 がすでに定める合成の畳み込み — ファイル の @requires 行と、ビルドがインスタンス化するすべての構成要素 — なので、ライブラリの def が、それ を置くファイルの意図を拒否することもあり、所見はその構成要素を名指します。他方のエディションにスコー プされた下限はここでも不活性で、そのエディションの表が位置づけられない下限は何も拒否しません。それは E_REQUIRES_UNORDERABLE であり、修正すべきは意図ではなく requires の行です。

cairn check を通すすべてのコマンド — check / info / lower / compile / synth — が 2 つの cap コードを報告し、それぞれが対象としているエディションの表でヘッダを照らします。--edition が名指す 1 つ、cairn info --editions が挙げるもの、どれも名指さない場合は両方です。どちらかのエディションが 到達した所見は報告されます。矛盾は、後でどう建てるかに関わらずファイルの 2 行の間にあるからです。また 1 つのスパンが持つ cap 所見は 1 つで、2 つのエディションが及ぶ範囲について食い違った場合はエラーを報告 します。一方がリストの一部をまだビルドできると見なしたからといって、もう一方の「1 つもビルドできない」 が弱くなるわけではないからです。

W_INTENDED_TARGET_UNSUPPORTED はスコープにエディションがちょうど 1 つある場合にだけ答えます。Java が ビルドできないバージョンは、作者が意図した Bedrock のターゲットであることが普通にあるからです。 cairn info --editions bedrock はスコープが 1 エディションなので Bedrock の表だけで照らされます。1 つ のエディションに絞ったレポートが、もう一方の答えによって拒否されることはありません。

E_REQUIRES_CONFLICT は 予約 です。宣言された下限がレジストリから 推定された 範囲と矛盾する ことと定義されていますが、推定範囲はまだ導出されません (パックが since / until を持たないため)。 2 つの @requires 行の衝突ではありません。下限は最も厳しいものへ畳まれるので、積が空になることは ありません。version<1.20 のような上限を要する制約は言語が受け付けない形で、E_INVALID_REQUIRES になります。

順序付けは DataVersion で、エディションごとに

Section titled “順序付けは DataVersion で、エディションごとに”

§10.1 は DataVersion を正準の順序キーとしており、 @requires はそれを使います。下限は ターゲットのエディションの バージョン表 (registry-data/{java,bedrock}/data_versions.json) の中に位置づけられ、ターゲット自身の DataVersion と比べられます。

この表はそのエディションの 全リリース を名指します。これはパックが ビルド できるバージョン の集合 — ブロックとマテリアルのデータを同梱している各エディション 3 つ — とは別の集合で、行が どちらであるかを targetable が示します。2 つを分けることで「表の範囲の内側にありながらどの行でも ない」が「そのエディションのリリースではない」を意味するようになります。1.21.1 はパックがビルド できない Java のリリースですが順序付けは問題なくでき、1.21.4 は Bedrock のどのリリースも名指し ません。Bedrock は patch を 10 刻みで採番するからです (1.21.0、1.21.20、1.21.40)。2 版の リリースラベル集合は互いに素です。

それでも下限はどの表にも無いものを名指せるので、位置づけは単純な参照では済みません。答えは 4 つ あり、厳密なのは最初の 1 つだけです。

下限 位置づけ 理由
表の行を名指す (末尾のゼロは無視: 1.21 は Bedrock の 1.21.0) その行の DataVersion 厳密。
表の行のプレリリースを名指す (1.21.4-rc1) その行の DataVersion リリース候補とそのリリースの間には何も出荷されないので、対応ターゲットもその間には存在しない。
全行より下、または全行より上に位置する 全ターゲットが満たす / どのターゲットも満たさない 下限のラベルを先頭行・末尾行の ラベル と比較して得る答えだが、どの行が先頭・末尾かを決めるのは キー である。したがって「表のラベルがテキスト順とキー順で一致する」ときにちょうど成り立つ。レジストリパックのローダがロード時にこれを検査する。下限のラベル自体はドット区切り 10 進でなければ比較対象にならない。
それ以外 — 表の範囲の内側にありながらどの行でもない 位置づけない DataVersion を持たず、与えることもできない。E_REQUIRES_UNORDERABLE。

最後の行は推測ではなく拒否です。Bedrock に対する @requires version>=1.21.4 がまさにそれです。Java のリリースは Bedrock のどのリリースも名指さず、1.21.0 と 1.21.20 の間に位置します。ラベルの 比較はこれを 40 > 4 で満たされたと読み、下限未満のバージョンに対する Bedrock ビルドを認証していま した。下限を強制する仕組みが取り除くために存在するのと同じ欠陥が、1 つ隣のエディションにあったわけ です。

ラベル集合が互いに素なので、この拒否は「駄目だ」以上のことを言えます。このエディションが位置づけ られず 他方 が名指す — その表の行か、行のプレリリースである — ラベルは、他方の採番で書かれた下限 であり、E_REQUIRES_UNORDERABLE はそれを名指してスコープを提案します。他方が名指さないラベルには スコープを提案しません。提案は推測になり、それを名指さないエディションへスコープを付ければ下限はそこ で不活性になり、制約が消えてしまうからです。これにはどちらも位置づけられないラベル — スナップショット — のほか、他方が全行より下または全行より上としか位置づけないラベルも 含まれます。この 2 つの位置づけはリリースではなく比較であり、作者がどちらの採番を意図したかについて 何も語りません。そこへスコープを付ければ、下限はそのエディションの全ターゲットが満たすか、どれも満た さないかのどちらかになります。

下限はエディションを名乗れる

Section titled “下限はエディションを名乗れる”

Java のリリースは 1.20.4 / 1.21 / 1.21.4、Bedrock は 1.21.0 / 1.21.40 / 1.21.60 と進みます。2 つ は別の尺度で、一方の採番で書かれた下限はもう一方では何も意味しません。そこで下限は、自分がどちらの 採番で書かれているかを言えます。

@requires java version>=1.21.4
@requires bedrock version>=1.21.40

スコープ付き の下限は自分のエディションのビルドだけを制約し、もう一方では不活性です。違反では なく不活性なので、上の 2 行は両エディションでビルドできます。スコープ無し の下限はビルドされる ものに対する下限で、他と同じくそのエディションの表で解決されます。これにより @requires version>=1.21 は両エディションが尊重できる下限になり (Java の 1.21、Bedrock の 1.21.0)、一方のリリースだけを 名指して他方には無い下限は、黙って通過するのではなく上記のエラーになります。

cairn info の registry compatibility 行 (§10.5) が読むのはスコープ無しの下限だけです。この行は両エディションに対して報告されうるファイル 1 つにつき 1 行であり、Java の採番で書かれた下限はそのファイルの Bedrock 範囲について何も言わないからです。 エディションごとの答えは buildable targets 行が持ちます。

theme が宣言する下限も同じ検査を受けます。ただし対象は行に書かれた語ではなく、それを継承する 構成要 素 の側です。エディション別の theme バリアント (§10.7) があると、1 つ の theme= 参照に対して 2 つのエディションが別々の theme を束縛しうるので、theme がこの行に寄与する のは両者が同じものを束縛するときだけです。そうでなければ、その下限はエディションスコープを持たないま まエディション固有の事実になってしまいます。def にこの検査は要りません。place use=NAME が名指すの は 1 つの def であって、バリアントの family ではないからです。この行が読まなかったものは理由とともに stderr で名指されるので、バージョンを拒否する buildable targets 行の隣に 0.0 が並んでも、読み手が 推測する必要はありません。

§10.1 が存在すると言うラベルの形は、すべてディレクティブ が受け付けます。semver 風の 1.21.4、プレリリースの 1.21.4-rc1、スナップショットの 24w14a、そし て日付ベースの採番が綴るものです。形の規則は、各要素が数字で始まり英数字だけからなるドット区切りの 列で、任意で - と同じ形のプレリリースタグが続く、というものです。1.a や x はどの体系でもバー ジョンを名指さないので E_INVALID_REQUIRES です。

ラベルを受け付けることは、それを順序付けられると主張することではありません。あるラベルが DataVersion を持つかどうかは表の答えで、エディションごとに問われます。cairn compile --target はビルドを拒否し、cairn info --editions はそのエディションにビルド可能なターゲットが無いことと その理由を報告します。cairn check はエディションをピンしないので問いません。

10.5 「どのバージョン用か?」には 3 つの答えがある

Section titled “10.5 「どのバージョン用か?」には 3 つの答えがある”

単一の「対応バージョン」はありません。cairn info は 3 つの軸を報告します。

  1. 宣言されたレジストリ範囲 [Vmin, Vmax]: ファイルがエディションを名指さずに宣言した下限を 合成したものと、開いた上端。使われているブロックから導出した事実ではなく、ソースとそれが実体化 する構成要素が宣言した内容を読み返したものです — registry compatibility の行を参照。
  2. 意味的に敏感なメンバ: ID は有効なまま、意味・挙動・見た目が変わる箇所。範囲より重要です。 挙動の変化は ID の消滅よりはるかに頻繁なので、レジストリだけから Vmax を決めるのは危険です。制約 カタログは since/until とは別に semantic_sensitivity (境界バージョン + 理由) を持ち、それを またぐコンパイルで警告を出します。例: 1.17 の cauldron 分割、1.16 の壁接続の bool → none/low/tall、1.20.5 のアイテムフォーマット。
  3. 検証済みのロックターゲット (§10.6)。
$ cairn info build.crn --editions java,bedrock
registry compatibility: 1.21.40 .. latest
edition portability: Java: portable: 42 degraded: 0 unsupported: 0 Bedrock: portable: 38 degraded: 3 unsupported: 1
buildable targets: Java: none (1.20.4, 1.21, 1.21.4 all refuse) Bedrock: 1.21.40, 1.21.60 (1.21.0 refuses)
intended targets: 1.21.40, 1.21.60
semantic-sensitive: yard_water(cauldron [email protected]), fence(wall [email protected])

名前が出るバージョンはすべて同梱パックが宣言しているものです。この出力の元のファイルは @requires version>=1.21.40 を持ち、それが Java の全ターゲットと Bedrock 1.21.0 を下限未満にしてい ます。

intended targets はファイル自身の @intended_targets 行をそのまま写したもので、答えではなく宣言で ある唯一の行です。buildable targets の隣に置かれるのは、それが矛盾しうる相手だからです — 判定できる 半分は §10.4 が自動化し、残りは読み手が突き合わせます。何も宣言していな いファイルは、行が消えるのではなく (none declared) になります。

5 行は stdout、各数字の内訳は stderr の note: 行へ出ます。行を読むパイプラインは、cairn info が 完走するかぎり毎回同じ 5 行を見ます。完走しない場合は別です。所見が行を 1 つも計算する前にコマンドを 拒否するので、stdout は行が足りないのではなく空になります。

この行は宣言を読み返すだけで、ソースが使っているブロックを見ることはありません。Vmin は、エディ ションを名指さない下限のうち最も厳しいもの — @requires version>=X のヘッダと、ビルドが実体化する def と theme それぞれの requires version>=X 行 (§10.4) — であり、どれも寄与しなければ 0.0 です。どの 構成要素が実体化されるかを割り出すのはソースに対する 実際の処理ですが、それらがどのブロックを塗るかを読むことはその一部ではありません。

「最も厳しい」はラベル同士の比較なので、すべてがドット区切りの十進数であるかぎりでのみ厳密です。 数として読めないラベルは、読めるラベルすべてより上に並びます。これは意味があるというより固定されて いるというだけで、version>=1.21.4 と version>=24w14a の両方を宣言したファイルはスナップショット のほうを報告します。この順序はビルドを何も決めません。ビルドを決めるゲートはいずれも、各下限を ターゲットエディションの表に個別に照らします (§10.4)。

この行はエディション非依存なので、読むのはスコープ無しの下限だけです。その理由と、外したものを stderr に出す note については §10.4 を参照してください。したがって 0.0 には 2 つの原因があります — 下限を 1 つも宣言していないファイルと、宣言した下限がすべてエディ ションを名指しているファイルです。行はこの 2 つを区別できませんが、stderr の note は区別できます。

Vmax はリテラルの latest です。上端は 導出された 範囲の片割れであり、この行が持つのは宣言の ほうなので、パックが since / until を持つようになったとしても、その答えはここではなく buildable targets に渡ります。

この行は宣言であって、どのバージョンがビルドできるかの答えではありません。 この 2 つは 1 つの ものとして読まれやすく、ソース次第で見分けがつかなくなります。次の例は、サポートされるどのバージョン も満たす下限を宣言し、かつ Java が 1.21.4 で得たブロックを使っています。

$ cat hut.crn
@cairn 2026.06
@requires version>=1.20.4
theme pale:
slot floor -> @pale_moss_block
slot wall -> @cobblestone
struct hut size=5x5
floor mat_slot=floor
walls mat_slot=wall height=3
$ cairn info hut.crn --editions java
registry compatibility: 1.20.4 .. latest
edition portability: Java: portable: 2 degraded: 0 unsupported: 0
buildable targets: Java: 1.21.4 (1.20.4, 1.21 refuse)
intended targets: (none declared)
semantic-sensitive: (none)

1.20.4 .. latest は答えのように読めますが、2 つ下の行がそれを否定します。しかも拒否リストには 1.20.4 自身が並んでいます。宣言された下限が間違っているのではなく、違う問いに答えているのです。 minecraft:pale_moss_block が 1.21.4 で登場したことはパックについての事実であり、この行が読むのは ファイルが宣言した内容だけです。

導出は 2 つ下の buildable targets の行です。 この行はサポートされるすべてのバージョンにソース を照らし、どれならビルドが通るかをエディションごとに報告します。since / until の積が近似しようと していた答えを、推論ではなく問い合わせで得たものです。また、積では取れない形でもあります。答えは エディションごとであり、連続とはかぎらないので、[Vmin, Vmax] の対は見えない隙間まで主張することに なります。

宣言された下限が独立した行であり続けるのは、種類の違う事実だからです。こちらは作者が書いた 入力 で、cairn compile --target が受け入れる範囲とファイルが読み手に約束する内容を縛ります。対して buildable targets は、たまたま出荷されているパックについての 出力 です。E_REQUIRES_CONFLICT (§10.4) はこの 2 つを比較できるようになる日のために予約されて おり — 宣言された下限がレジストリから 推論された 範囲と矛盾する場合 — パックが since / until を持つまで到達不能なままです。

この行はパレットのエントリを数えます。エントリが unsupported になる理由は 2 通りです。

理由 修復
そのエディションにそのブロックが無い。 マテリアルを変えるか、パックの対応付けを変える。
ブロックはあるが、intent が持つステートに対する対応を Cairn が持たない (§10.7)。 まだ無い。対応を追加するのは Cairn 側です。

最初の 1 つは ID の話、2 つ目はステートの話です。degraded になりうるのは 2 番目だけです。存在しな いブロックには失う詳細がないためです。

1 つの数字の裏に 2 通りの修復が隠れるので、数えられた各エントリは理由とともに stderr で名指しされ、 ID のケースは E_UNKNOWN_ID と同じ答え方を、同じ 2 つの半分でします。行がある場合は aliases コン ポーネントが答えるので、このエディションが別名で持っているエントリは行き止まりではなくその名前で報告さ れ (Java での standing_sign は oak_sign)、行が無いときに did you mean が付きます。両者は択一で あって順番ではありません。行はどのブロックかについてのパックの言明であり、その横に距離からの推測を並べ ることは、読み手にどちらを取るかを選ばせることだからです。ここでもエイリアスの問いはエディションに対し て発せられます。サポート対象の いずれか のバージョンが宣言する綴りが答えになり、固定されたビルドなら 自身のバージョンの分だけを残します。--format json では edition_portability[].unsupported_entries として、数の 1 単位につき 1 要素、パレット順で出ます。

どちらの問いもバージョンではなく エディション に対して発せられます。この行が互換範囲全体にわたって 報告するものだからです。範囲の一部でしか有効でない ID (Bedrock が 1.21.40 で stonebrick を stone_bricks にリネームした件) は unsupported にはなりません。実際にビルドされるバージョンがそれ を持つかは、固定されたターゲットが E_UNKNOWN_ID として答える問いです。cairn compile --target、 またはビルドせずに同じ答えを得る cairn check --edition E --target V です (§10.4)。

どのエントリが degraded し、何を失ったか

Section titled “どのエントリが degraded し、何を失ったか”

degraded も、コマンドが名指しできるエントリを数えたもう 1 つの数字で、名指しの仕方も同じです。 unsupported に比べると理由は弱く、degraded する理由は 1 つ、修復も 1 つなので、読み手が修復を選ぶ ことはありません。同じなのは「N 個のうちどれか」のほうです。roof-hip は degraded: 4 と報告し、 それに答えるもう 1 つの場所はビルドの W_INTENT_DEGRADED であり、cairn info はそのビルドの 前 に読まれるためにあります。

数えられた各エントリは stderr で名指しされ、--format json では edition_portability[].degraded_entries として、数の 1 単位につき 1 要素、パレット順で出ます。 1 件は {id, states, dropped} です。

フィールド 持つもの
id パレットエントリのブロック ID。lowering が intern したそのまま。
states エントリの key=value をカンマで連結したもの。states_unmapped の理由と同じ綴りです。
dropped エディションが形を持たない intent ごとに 1 件の {key, value}。key は閉じた集合(shape)で、新しい種類の損失は既存の key の変更ではなく新しい key になります。

カテゴリのタグを付けた 1 つのリストではなく 2 つのリストにし、ID だけでなく states を持たせていま す。degraded はブロックについての事実ではなく ステートの組み合わせ についての事実だからです。1 つ の ID は、詳細を失う組み合わせの数だけこのリストに現れます。roof-hip の 4 件は minecraft:spruce_stairs の 4 通りの綴りで、ID だけを鍵にしたリストは同じ行を 4 回出すことになりま す。

dropped はそれについての文ではなくプロパティと値を持ちます。unsupported の理由と同じ理由です。 これを読む消費側が、どのステートが失われたかを知るために英文を解析する必要はありません。エディション が表現 できる 値は損失ではないので現れません。Bedrock の階段は straight なので、shape=straight はエントリを作らずに落ちます。note と W_INTENT_DEGRADED が印字する文は 1 か所で書かれており、しか もペア全体にではなく key ごとに書かれています。shape が落ちたときの文は階段の話をしているからで す。intent を落とす 2 つ目のブロックファミリは、この文を受け継ぐのではなく自分の key と自分の文を 持ちます。

パックが拒否すべきだったブロックステートは数字ではない

Section titled “パックが拒否すべきだったブロックステートは数字ではない”

ステート変換器にはさらに 2 種類の失敗が届きえます。Java のドメイン外のステート値 (階段の facing=up) と、変換器が読まないステートキーです。どちらもエディションについての答えではありませ ん。検証済みのレジストリパックには作れないはずのブロックステートが、それでも変換器まで届いたという ことであり、それはパックまたは Cairn の欠陥であって、報告対象のビルドの性質ではありません。

したがってこれらは unsupported の 3 番目・4 番目の理由ではありません。cairn info は、そのような エントリを含むパレットを持つエディションについては可搬性の数字を 一切 報告しません。数字自体は計 算できますが、それは通常の可搬性として読まれてしまいます。漏れた facing=up を unsupported: 1 と 数えることは、Bedrock に単にステートが無いだけの角の階段と見分けが付かず、それこそ読み手に引き出させ てはならない結論だからです。コマンドは漏れた各エントリを変換器自身のメッセージとともに stderr で名指 しし、修復先はソースではなくパックまたは Cairn だと述べ、行を出さずに非ゼロで終了します。コマンドを 拒否する他の findings と同じ形です。

これが観測されうるのは今のところステート変換器だけなので、規則もステートの問いについて述べています。 これは他所の検証漏れに unsupported と答えてよいという許可ではありません。検証済みのパックには作れ なかったはずのパレットの上で計算された数字は、何がそれを作ったのであれ可搬性の答えではありません。

カウンタでは言えないことがあります。2 つのエントリがそれぞれ 互いに素な バージョン集合で宣言されて いても、どちらも「エディションは持っている」と答えるので、どのバージョンも両方を宣言していないのに行 はきれいなままです。

buildable targets はバージョン単位の答えです。要求されたエディションごとに、固定 lowering がエラー を出さないサポート対象バージョンを並べ、拒否したバージョンをその横に名指しします。[Vmin, Vmax] の 範囲ではなく集合なのは、バージョン集合が交錯する 2 つの ID が、範囲では埋めてしまう隙間を残すからで す。

導出はサポート対象バージョンごとに 1 回 lowering する方法で、cairn compile --target と同じ検査です。 範囲全体のパレットの ID 集合を積集合するのは 不健全 なので採りません。ターゲットを固定しないとす べてのマテリアルが 既定 の対応を取るため、ターゲットが綴り替えるトークンが誤った ID として比較され ます。

カウンタと同じく、この行は報告するだけで拒否しません。どのサポート対象バージョンでもビルドできないソー スに対しても cairn info は 0 で終了します。拒否するのはビルドの仕事です。拒否した各バージョンの所 見はそのバージョンの下に印字されるので、E_UNKNOWN_ID がそれを出したターゲット抜きで置き去りになる ことはありません。

buildable が空になる原因は 4 つあり、同じ編集で直るわけではありません。@requires の行が 2 つに 答え、voxel を 1 つも生まなかったメンバが 3 つ目に、固定 lowering が名指したものが 4 つ目に答えます。 リストだけではこれらを区別できないので、--format json では reason が隣に付きます。ビルドできる バージョンが 1 つでもあるときこのキーは 付きません。通常のレポートは変わりません。

reason はオブジェクトで、成り立っている原因をすべて報告します。4 つのうち 2 つは エディション についての事実で、どのリリースでも同じ内容になり、リリースを量る前に決まります。これらは reason 自身のフィールドです。残る 2 つはリリースごとに異なるので、その隣のバージョン別リストに入ります。 中身が空のフィールドは出力されません。

フィールド 持つもの 作者が直す場所
unplaceable_floors floor @requires の行。floor がこのエディションのどのリリースも名指していないので、どのバージョンとも突き合わせられず、どれも認証されません。
dropped_scopes スコープキー、walkway は site::SITE::FROM ↔ TO voxel を生まなかったメンバまたは connect 行。部分ビルドは認証されないので、どのバージョンも ID テーブルを見る前に拒否されます。
versions 拒否されたターゲット 自分自身の理由で拒否されたバージョンごとに 1 エントリ。

どちらか一方ではなく両方が並びます。あるファイルが、このエディションが置けない floor を宣言し、 なおかつどのリリースも持ったことのない ID を使っている、ということは起こります。前者だけを報告する 形だと、作者はそれを直してから後者に初めて出会うことになり、1 回の実行につき原因 1 つという往復に なります。この行はまさにその往復をなくすためにあります。

versions は considered の 部分列 であって、対になる配列ではありません。エディション単位の 答え以外に不利な点がないリリースはエントリを持たないので、消費側は位置ではなく version で突き合わ せます。各エントリは version と refusal タグを持ちます。

refusal 持つもの 作者が直す場所
below_floor floors @requires の行。そのバージョン を拒否している floor だけが載ります。複数宣言しているファイルでも、直すのは全部ではなく 1 行だからです。
lowering_refused findings findings が名指すもの。たいていはマテリアルか ID で、バージョンごとに異なります。

この 2 つは排他です。floor より下のバージョンは lowering されないからです。floor はソースとターゲット の間の関係であって、ID テーブルが何であってもそれは変わりません。

floor の 1 件は {declared, line, col, declared_by?} です。作者が書いたままの floor(スコープ込み) と、それが書かれている場所です。declared_by は部分から継承した floor の {keyword, name} で、 keyword は def か theme のいずれかです。ファイル自身の floor では付きません。位置がすでにその行 を指しており、その行はファイルのものだからです。

findings は spec/lint “Machine-readable payload” が finding を表すのと同じ形で、実行がそのバージョン の下に stderr へ印字するのと同じ findings です。その節はこの行によって変わりません。これらはレポートの 中に乗るのであって {"diagnostics": [ ... ]} のドキュメントに乗るのではなく、レポートは拒否ではないか らです。

テキストの行はこれらを何も言いません。buildable targets: Bedrock: none (1.21.0, 1.21.40, 1.21.60 all refuse) は 4 つの原因すべてで真です。どの原因も stderr には出ますが、形は同じではありません。floor の 2 つはその行の位置を持つ note: として、落ちたスコープはスコープ名だけで位置を持たない note: として、 拒否された lowering はバージョンの下にその findings 自体が、それぞれ自分の位置を持って出ます。読めなかっ たのは JSON のほうです。

5 行目の recommended test targets はこの軸に属し、また別の問い (どのバージョンをテストする価値が あるか) に答えます。まだどのコードパスも出力しません。

.crn が持つのはヒントである @intended_targets だけです。verified: true、DataVersion、各種 ハッシュはロックにのみ存在し、ビルド成功時にコンパイラが書きます。手書きすることはありません。

# build.cairn.lock (コンパイラ生成)
lock_schema_version: 1 # この文書自身のスキーマ改訂
source_hash: sha256:...
cairn_version: 2026.06 # Cairn リリースの日付バージョン (CalVer)
target: { edition: java, mc_version: 1.20.4, data_version: 3700 }
inputs: { registry_pack_hash: sha256:..., constraint_catalog_hash: sha256:... }
resolved_ir_hash: sha256:...
verified: true
member_version_sensitivity: [ { id: yard_water, reason: "cauldron split at 1.17" } ]

resolved_ir_hash が再現性の核です。マクロ展開、デフォルト補完、自動アドレス割り当ての後の IR を 固定します。

lock_schema_version を先頭に置くのは、読み手が残りをパースする前に理解できるか判断できるようにする ためです。バージョン 1 は上の形で、キーを省いた文書はバージョン 1 です。より高いバージョンを宣言 する文書は、フィールド名の意味が同じままだと仮定して読むのではなく拒否します。スキーマが宣言していな いキーは、どこに現れても拒否されます。

別のターゲットで再コンパイルすると、検証済みのものとの差が大きく報告されます。

$ cairn compile build.crn --target 1.21.4 --lock build.cairn.lock
W_PREVIOUSLY_VERIFIED_TARGET: verified for 1.20.4/DataVersion 3700, now 1.21.4/4189.
W_SEMANTIC_SENSITIVITY: 2 members may resolve differently: yard_water, fence

導出規則はエディション固有です。intent_state は中立、resolved_state はエディション別。 契約 は「同じ intent から、エディションごとに最も近い合法表現へ解決する」ことであり、「同じ結果を保証する」 ことではありません。

intent_state: { primitive: stairs, corner: inner_left, facing: east } # エディション中立
resolved_state:
java: { facing: east, half: bottom, shape: inner_left }
bedrock: { weirdo_direction: 1, upside_down_bit: false } # shape が無く角がつながらない

解決結果の差が見た目や機能の差になるとき、lint が知らせます。

W_INTENT_DEGRADED line 12 id=roof_corner:
shape=inner_left cannot be resolved in Bedrock (stairs have no shape state).
Bedrock stairs render straight; visual gaps at corners.

正準語彙が吸収できるのは ID / ステート / シリアライズの差だけです。概念の不在とゲーム挙動の差は吸 収しません。 吸収できない代表例は次のとおりです。

  • display エンティティ (Bedrock に無い)
  • 階段の shape (Bedrock にはステートが無い)
  • armor_stand のポーズ
  • レッドストーンの伝播
  • アイテムコンポーネント ↔ Bedrock のアイテム NBT
  • light ブロックの内部挙動

意味層での @edition 条件分岐は禁止です。代替が必要なときは、この階層を上から順に使います。

  1. クローズドな意味プリミティブ (中立) を使う。表現できなければ E_PARITY_UNSUPPORTED で fail-loud。
  2. スロット + エディション別テーマ でフォールバックする (floating_text スロットを Java では text_display、Bedrock では光る看板に解決)。
  3. エスケープハッチ層でのみ @edition でガードする。生の ID や NBT は本質的にエディション固有です。
@requires bedrock version>=1.21.40 # 下の Bedrock 分岐は平坦化後の ID で書かれている
hologram id=shop_sign text="Weapon" mat_slot=floating_text # 意味層は常に中立
theme shop_java: slot floating_text -> text_display scale=2.0
theme shop_bedrock: slot floating_text -> sign glowing=true # Bedrock フォールバック
@edition java { raw_block mat=minecraft:light[level=15] at=4,3,2 }
@edition bedrock { raw_block mat=minecraft:light_block_15 at=4,3,2 }

@edition が固定するのはエディションであってバージョンではない

Section titled “@edition が固定するのはエディションであってバージョンではない”

ガードが決めるのはどちらの分岐を建てるかだけです。その中の ID は、コンパイルが固定した 1 つの (edition, version) に対して照合されます (§10.4)。したがっ て、エディションが自身のサポート範囲の中でブロックを綴り直している場合、分岐を名指すのは @edition bedrock で、綴りを決めるのはファイルの下限です。

Bedrock の light ブロックがまさにその例で、上のスニペットが下限を持つ理由でもあります。1.21.0 はこの ブロックを light_block と綴り、明るさは block_light_level ステートとして横に持ちます。1.21.40 は その明るさを ID の中に繰り上げたので、1.21.40 と 1.21.60 は同じブロックを light_block_0 … light_block_15 と綴り、light_block という ID はもう持ちません。これらの綴りをひとまとめにしてい る aliases の行 (§10.4) が答えるのは診断であってソースでは ありません。その答えは固定されたターゲットが宣言している閉じた集合です (Bedrock 1.21.60 に対する light は 16 段階すべてが返ります)。16 個の集合は綴りではなく、そこから 1 つを選ぶことこそ同節が禁 じるサイレント置換なので、選ぶのは作者の仕事のままです。分岐を 2 つの形のどちらかに向けて書くとは、ま さにそれをやることです。どちらの形なのかを述べるのが下限です。下限はスコープ付きなので Java のビルド では不活性で (§10.4)、そちらはもう一方の分岐が担います。

下限を省いても間違いにはならず、うるさく落ちます。Bedrock 1.21.0 に対する light_block_15 は E_UNKNOWN_ID です。検査はバージョン単位だからです。下限が足すのは別の拒否ではなく宣言です。分岐が どのバージョンに向けたものかという半分を、ファイルの他の制約と同じ場所に書き、他のヘッダがそれに照ら される半分でもあります。1.21.40 の下限を宣言したファイルの @intended_targets が 1.21.0 を名指せば、 ターゲットを選ぶ前に cairn check の時点で E_INTENDED_TARGET_CAP になります (§10.4)。下限の無い同じファイルは、ターゲットを選ぶまで何も言いません。 両方の綴りを賄わなければならないビルドは 2 つのビルドです。@edition と組にできるバージョン条件分岐 はありません。

バリアントを選ぶのはビルドであってソースではない

Section titled “バリアントを選ぶのはビルドであってソースではない”

theme NAME_java と theme NAME_bedrock は論理テーマ NAME の 2 つのバリアントを宣言します。 --edition のピンはそのエディションのバリアントを束縛し、無ければ接尾辞の無い NAME にフォールバッ クし、そこで止まります。もう一方の エディションのバリアントを束縛すると、そのスロット値がこのエデ ィションの出力に流れ込みます。§10.4 が禁じるサイレント置換で す。どちらも無ければ、要求された体積を空気で建てるのではなく E_THEME_VARIANT_MISSING でコンパイル を止めます。

place ... theme=NAME は 論理 テーマを名指し、まさにこの規則に従います。1 つの site が、ビルド が必要とするバリアントで同じ def を配置できます。そこでバリアントを名指しても (theme=shop_bedrock) 解決はします。ピンが選んだバリアントを束縛し、W_THEME_VARIANT_REBOUND が代わりに束縛されたものを告 げます。ただし意味層が担うべきなのは中立な綴りです。

--edition のピンが無ければ、作者が名指したバリアントを選び直すことはありません。宣言された名前はそ のまま束縛されます。接尾辞 無し で書かれた名前は、モジュールレベルの選択と同じピン無しの順序で解決 されます。モジュールが宣言していない接尾辞 付き の名前は E_UNRESOLVED_THEME_REF です。

非対称で、意図的にそうしています。

  • ダウングレード (新バージョンの NBT → 旧バージョンのワールド) はハードエラーです。未知の コンポーネントはクラッシュや破損を招きます。
  • アップグレード (旧バージョンの NBT → 新バージョンのワールド) は大きな警告 + DataVersion スタン プで、DFU 依存です。明示的な --allow-cross-version を要します。

すべてのビルドがエディション可搬である必要はありません。コンパイラの仕事は、何が可搬性を壊すかを述べ ることです。

10.8 Cairn 自身の面の互換性ティア

Section titled “10.8 Cairn 自身の面の互換性ティア”

上の (edition, version) 軸が扱うのは Cairn が 出力するもの です。直交する軸は、Cairn が自身の進 化について約束することです。.crn 構文、ロックファイル、CLI フラグ、Rust API。CalVer にはそれを読み 取れる「メジャー」軸が無いので、約束は 互換性ティア に明記します。Stable な面は W_DEPRECATED で 1 リリース分の猶予があり、Evolving な面は任意の月次マイナーで変わり、Internal は何も約束しません。