ファイルからマインクラフトのワールドへの変換

2026/07/21 5:54

ファイルからマインクラフトのワールドへの変換

RSS: https://news.ycombinator.com/rss

要約

Japanese Translation:

この記事は、任意のファイルを入力バイトを一意のブロックタイプにマッピングすることで閲覧可能な Minecraft ワールドに変換するツール「Sulfur」を紹介し、地形を安全なデータコンテナとして効果的に利用する。暗号化プロセスでは、「SULF」というマジック文字列と 12 バイトのサイズフィールドを持つカスタムヘッダーが生成される。この構造は、初期に

.mca
ヘッダーの最初の 8 バイトをペイロードサイズと誤って読み込み、約 2.3 エксаバイトの誤った計算を行ったというミスを補うために追加された。元の実装では
BASE_Y = 0
が使用されており、「Flat Slab」エラーが発生してワールドの高さが 16 ブロック (~4 MiB/リージョン) に制限されていた。全容量 (~96 MiB/リージョン) を達成するために、現在はチャンク列あたり 16x16 の床を埋め尽くし、Y -64 から Y 320 の 24 セクションまで構築した後に 32x32 のチャンクグリッドを巡回する。バージョン 1.21.11 で
mcdata_rs
を用いて構築されたルックアップテーブル (PALETTE) は、破損や視覚的エラーに傾倒しやすいブロックを除外する。除外リスト (
NAUGHTY_LIST
) は、砂、石、金槌、ドラゴン卵、足場、雪、耕作地、チェスト、ベッド、ドア、氷などの問題のある単一ブロックの表現を取り除く。マルチブロックエンティティについては、単一ブロックとして配置された際に不正確または不可視になるため、その名前をこのリストに追加することで特に処理された。最終出力は標準的な Minecraft リージョンファイルであり、ワールドセーブを共有するだけでゲーム配信の中にアーカイブや秘密メッセージを隠すことができる。復号化はこのプロセスを逆に行い、カスタムヘッダーを読み込んで元のファイルサイズを回復し、「block_to_byte」を用いてペイロードブロックをバイトに変換し、再構築されたストリームをディスクに書き出す。

本文

ファイルをマインクラフトの 3D ワールドに変換するプロジェクト

かつて「あらゆるファイルをマインクラフトの世界に保存したい」という着想を抱いたことはありませんか? そのアイデアを実現し、3D 幾何学・バイト配列・マインクラフトデータに関する深い探求を行ってきました。

プロジェクトの背景と目的

このプロジェクトの核心はシンプルです:

  • 「あらゆるファイルを、マインクラフトのブロックとして表示したい」

なぜ取り組むのか?

  • **「やってみる価値がないわけじゃない」**という単純な好奇心から始まりました。
  • テキストファイルさえ**2.3 エクサバイト(EB)**もの容量になる可能性があるため、スケールへの驚きも大きかったです(笑)。

1. パレットの生成

任意の

0〜255
のバイトを、マインクラフトのブロックに一意に割り当てる必要があります。

処理フロー

  1. データの取得:バージョン
    1.21.11
    のブロックデータを取得(
    mcdata_rs
    を使用)。
  2. フィルタリング:以下の条件で除外しました。
    • 状態変化(
      age
      ,
      level
      ,
      part
      など)を持つブロック(例:作物、液面など)。 理由:状態が変わるとデコーダーがバイトを正しく読み取れず、ファイルが壊れるため
    • 後述する「汚らしいリスト」に指定された名前を含むブロック。
  3. 固定配列化:残ったブロックから先頭の
    256
    ブロックを取り出し、Rust のコードとして出力。

コード例:パレット生成器

use mcdata_rs::mc_data;
use std::{fs, path::Path};

// 除外リスト(状態変化やマルチブロックなど)
const NAUGHTY_LIST: &[&str] = &[
    "sand", "gravel", "anvil", "dragon_egg", "scaffolding", 
    "dripstone", "snow", "farmland", "chest", "_bed", 
    "door", "ice", "grass_block",
];

fn main() {
    // データ取得
    let data_1_21_11 = mc_data("1.21.11").expect("ダウンロード失敗");
    
    // 条件を満たすブロックをフィルタリング
    let palette = data_1_21_11
        .blocks_array
        .iter()
        // 境界ボックスが "block" のものだけ
        .filter(|b| b.bounding_box.eq("block"))
        // 状態を持たないブロックだけ(age, level, part を除外)
        .filter(|b| {
            !b.states.iter().any(|s| matches!(s.name.as_str(), "age" | "level" | "part"))
        })
        // NAUGHTY_LIST に含まれる名前を除外
        .filter(|b| !NAUGHTY_LIST.iter().any(|g| b.name.contains(g)))
        .collect::<Vec<_>>();

    // 先頭 256 ブロックの名前抽出と配列生成
    let names: Vec<&str> = palette.iter().take(256).map(|b| b.name.as_str()).collect();

    let mut out = String::new();
    out.push_str("pub const PALETTE: [&str; 256] = [\n");
    for name in &names {
        out.push_str(&format!("    \"minecraft:{}\",\n", name));
    }
    out.push_str("];\n");

    // ファイル出力
    let out_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("src/palette.rs");
    fs::write(out_path, out).expect("書き込み失敗");
}

バイト ↔ ブロック変換関数

生成された

PALETTE
を使用して、変換関数を定義します。

  • バイト → ブロック: 単純な配列インデックスアクセス(高速)。
  • ブロック → バイト: パレットでの検索位置を計算(逆参照)。
pub fn byte_to_block(byte: u8) -> &'static str {
    PALETTE[byte as usize]
}

pub fn block_to_byte(block: &str) -> Result<u8, SulfurError> {
    PALETTE
        .iter()
        .position(|p| *p == block)
        .map(|i| u8::try_from(i).expect("パレットは常に 256 エントリ"))
        .ok_or(SulfurError::BlockNotInPalette(block.to_string()))
}

2. 座標計算の実装 (
cube_coords
)

バイトのインデックスを、マインクラフトの 3D ワールド座標

(x, y, z)
に変換します。

世界構造と配置戦略

  • セクション:
    16×16×16
    ブロック(4,096 ブロック)のキューブ。
  • 垂直方向:
    Y=-64
    320
    の間で、合計 24 セクション積み上げられる。
  • 水平方向:
    32×32
    チャンクグリッド。
  • 計算順序:
    1. まず
      16×16
      の床面を埋める。
    2. その上に高さを上げる(1 セクション完遂)。
    3. 次のチャンクへ移動する前に、その列の下端から上端まで埋める。

コード例:座標計算ロジック

pub fn cube_coords(byte_location: usize) -> silverfish::Coords {
    const SECTION_SIZE: usize = 16;
    const BLOCKS_PER_SECTION: usize = SECTION_SIZE * SECTION_SIZE * SECTION_SIZE; // 4096
    const MAX_CHUNKS: usize = 32;
    const SECTIONS_PER_COLUMN: usize = 24;
    const MIN_Y: i32 = -64;

    let inside_section = byte_location % BLOCKS_PER_SECTION;
    let section_number = byte_location / BLOCKS_PER_SECTION;

    // セクション内での座標計算 (x, y, z)
    let x_inside = inside_section % SECTION_SIZE;
    let y_inside = inside_section / (SECTION_SIZE * SECTION_SIZE);
    let z_inside = (inside_section / SECTION_SIZE) % SECTION_SIZE;

    // レイヤーとチャンク位置の計算
    let layer = section_number / SECTIONS_PER_COLUMN;
    let section_x = layer % MAX_CHUNKS;
    let section_y = section_number % SECTIONS_PER_COLUMN;
    let section_z = (layer / MAX_CHUNKS) % MAX_CHUNKS;

    // 絶対座標へ変換
    let x = u32::try_from(section_x * SECTION_SIZE + x_inside).expect("オーバーフロー");
    let y = MIN_Y + i32::try_from(section_y * SECTION_SIZE + y_inside).expect("オーバーフロー");
    let z = u32::try_from(section_z * SECTION_SIZE + z_inside).expect("オーバーフロー");

    (x, y, z).into()
}

座標遷移のテスト例

バイト位置結果的な動作
0
(0, -64, 0)
:世界最底辺から開始
15
(15, -64, 0)
:最初の床面行終了
16
(0, -64, 1)
:奥へ移動(Z 方向ラップ)
255
(15, -64, 15)
:床面が満杯になる瞬間
256
(0, -63, 0)
:1 ブロック高くなる
4,095
(15, -49, 15)
:最初のセクション(4,096 ブロック)満杯
4,096
(0, -48, 0)
:次のセクションへ、同じ列で高くなる
4,096 × 24
(16, -64, 0)
:1 チャンク分横移動(列完了)
4,096 × 24 × 32
(0, -64, 16)
:Z 方向でラップ(次の行へ)

容量計算:

32(チャンク) × 32(チャンク) × 24(セクション) × 4,096(ブロック) ≈ 1 リージョンあたり 96 MiB


3. エンコーディングとデコーディング

エンコーディング(ファイル → マインクラフト)

  1. ファイルを読み込み、リージョンを初期化。
  2. ヘッダー書き込み(詳細後述)。
  3. ファイルの各バイトをブロックに変換し、
    cube_coords
    で計算した座標に設置。
  4. .mca
    形式として書き出す。
pub fn file_to_region(
    source_file: impl AsRef<Path>,
    region_file: impl AsRef<Path>,
) -> Result<(), SulfurError> {
    let input_file = std::fs::read(source_file.as_ref())?;
    
    // ヘッダーサイズチェック(容量オーバー防止)
    if HEADER_SIZE + input_file.len() > REGION_CAPACITY {
        return Err(SulfurError::EncodedPayloadTooLarge);
    }

    let mut region = Region::default();
    region.set_config(Config::new(true, true, Config::DEFAULT_WORLD_HEIGHT))?;

    // ヘッダーのバイトをブロックとして配置
    for (loc, byte) in Header::new(input_file.len()).to_bytes().iter().enumerate() {
        region.set_block(cube_coords(loc), byte_to_block(*byte))?;
    }

    // ファイル本体のバイトを配置
    let header_len = HEADER_SIZE as usize;
    for (offset, byte) in input_file.iter().enumerate() {
        region.set_block(
            cube_coords(header_len + offset), 
            byte_to_block(*byte), 
        )?;
    }

    region.write(&mut std::fs::File::create(region_file)?)?;
    Ok(())
}

デコーディング(マインクラフト → ファイル)

  1. リージョンを読み込み、ヘッダーブロックからファイルサイズを取得。
  2. ヘッダーブロックをバイト列として復号。
  3. ペイロード領域(ファイル本体相当)のブロックをバイトに変換して出力。
pub fn region_to_file(
    region_file: impl AsRef<Path>,
    output_file: impl AsRef<Path>,
) -> Result<(), SulfurError> {
    let region = Region::from_region(&mut std::fs::File::open(region_file)?, (0, 0))?;

    // ヘッダー復号
    let header_coords = (0..HEADER_SIZE).map(cube_coords).collect();
    let raw_header_bytes = header_coords
        .iter()
        .map(|c| {
            let block_name = region.get_block(c)?.name.to_str();
            block_to_byte(block_name)
        })
        .collect::<Result<Vec<u8>, _>>()?;

    // ファイルサイズ解析とペイロード領域取得
    let header_info = Header::from_bytes(&raw_header_bytes)?;
    let file_size = usize::try_from(header_info.file_size)?;
    
    // ペイロード用座標範囲計算
    let start = HEADER_SIZE;
    let end = HEADER_SIZE + file_size;
    let payload_coords = (start..end).map(cube_coords).collect();

    // バイト列へ書き出し
    let mut writer = std::io::BufWriter::new(std::fs::File::create(output_file)?);
    for coord in &payload_coords {
        let block_name = region.get_block(coord)?.name.to_str();
        writer.write_all(&[block_to_byte(block_name)?])?;
    }

    writer.flush()?;
    Ok(())
}

4. 開発中のミステークスと解決策

❌ ミストーク #1:平坦なスラブ問題

初版実装では、Y 座標が適切に増えず、全ブロックが

Y=0
付近の「高さ 16 ブロックしかない幅広のスラブ」のように配置されていました。

  • 影響: 本来の垂直容量(96 MiB)を使い切れず、世界が扁平化してしまった。
  • 解決:
    cube_coords
    のロジック見直しにより、セクション間を正しく積み上げるよう修正。

❌ ミストーク #2:キャパシティオーバーフロー

ファイルの最初の 8 バイトを読み取りそのままサイズと解釈すると、マインクラフトのフォーマットヘッダーとして誤解析されてしまいました。

  • 現象:
    2,338,328,528,344,327,162
    という巨大な値が得られ、メモリエラーに陥る。
  • 解決: データファイルであることを示す専用ヘッダーを導入。
    const MAGIC: [u8; 4] = *b"SULF";
    pub const HEADER_SIZE: usize = 12; // magic(4) + size_le(8)
    
    pub struct Header {
        pub file_size: u64,
    }
    

❌ ミストーク #3:目に見えないブロック

_bed
,
double_chest
などは「マルチブロック」として機能するため、単独で配置すると見た目がおかしくなります。

  • 対策:
    NAUGHTY_LIST
    に追加し、パレット生成段階で除外しました。

まとめと将来展望

現在の成果

  • Jeb 氏(元 Mojang)の提案する「アノイルファイル形式」のようなアイデアを実現できました。
  • 「マインクラフト映画」などの動画データをエンコード・復号可能です。(※ダウンサンプリングのため画質は粗くなります)

今後の拡張計画

  • 暗号化: クリーパーや TNT のブロックパターンでデータを保護する仕組みの追加。
  • 大容量対応: 複数のリージョンにまたがる巨大なワールド出力機能の実装。

コードの閲覧・貢献はこちらから
もし改善点やアイデアをご存知であれば、お気軽にお試しください!

🔗 sulfur リポジトリ (Codeberg)
(是非スターを押しておいてくださいね :))

同じ日のほかのニュース

一覧に戻る →

2026/07/23 23:24

筆記は脳にとっても有益です

## Japanese Translation: 筆記は、抽象的なアイデアを複雑な身体的動きと統合させることで、タイピングよりも脳をより深く関わらせます。著者は、*Baroque Cycle* のために大量のページを書くなど、25 年間の日常的な筆記の経験に基づいてこの主張を支持しています。「書き手痙攣」という一般的な恐怖とは対照的に、著者は数十年の実践を通じてこれを一度も経験したことがありませんが、文字が illegible になるや大文字を忘れるなどの初期の苦労は新しい書き手によくあると認められています。筆記機構では適度な摩擦(「歯車」)に依存しており、草書はこの摩擦を活用することで、個々の文字を書くことよりも疲れにくくしています。「インク災害」という一般的な恐怖は、 Fountain Pen との固有の欠陥ではなく、不適切な材料や条件(例えば大気圧の変化など)から生じた神話であることが多く、適切な紙とペンとの組み合わせにより左利きであっても滲みを防ぐことができます。教育者は AI の懸念により学生を長手筆記試験に戻すようになり、学習者が高価な機器への投資をする前に、綿成分の紙や Pilot G-2 ペンのような利用可能な選択肢を試験することが重要となっています。タイピングや AI だけに依存するだけでは、運動技能を統合する身体的評価に備えられない可能性があります。

2026/07/24 4:26

Show HN: Echo – オープンウェイトモデルで費用を 1/3 に抑えつつファブルレベルの成果を実現

## Japanese Translation: 本記事では、OpenRouter、Fireworks、JusCode、Pellmell.ai、TracerML(Echo)、SakanaAI Fugu、Magnitude.dev などの現代の AI ルーティングシステムが、Fable といったプレミアムまたは専用ソリューションと比較して著しく低いコストで高品質な大規模言語モデル(LLM)の出力を可能にすることを論じています。ベンチマークデータによれば、ルーターは HumanEval+、SWE-bench Verified、BigCodeBench を含む 7 つのベンチマークファミリーにおいてトップクラスモデルと同等のパフォーマンスを発揮しており、従来手法のおよそ 1/3 の価格で達成されています。生成に基づいたアプローチ(例:Fusion)のように多数の返信を生成して合成する過程で高いレイテンシーとコストを招く一方で、ルーティングアーキテクチャは速度を維持しつつ結果を改善します。Dogpile.com、AskJeeves、Alta Vista、Lycos、DeepSeek R2、GitHub Copilot(GHCP)、OpenAI の「auto」モードといった製品も、異なる戦略を示しています。OpenAI の「auto」モードは単純なクエリに対するインフラコスト削減のためモデルサイズを選択し、Pellmell はルーターを通じて最適の返信を即時にストリーミングするとともに、必要に応じて絵文字反応を使って合成を行います。エコシステムは歴史的な検索エンジンから GPT-5/4o/o1、Qwen 3.7 Max、Anthropic モデル、GPT Sol、そしてさまざまなオープンウェイトの中国モデルを使用する現代ツールまでをカバーしています。重要な技術的な区別として、現在の LLM の専門家混合物(MoE)におけるルーティング決定はトークン単位で行われるのに対し、タスク固有またはアンサンブルルーティングとはアーキテクチャ的に異なります。プライバシーポリシーは更新され、Echo が顧客のプロンプト、ファイル、チャット、出力をモデルの学習やファインチューニングに使用しないことが明確に記載されています。また、Echo は今後は無料での初期クレジットを提供し、サインアップ時にクレジットカードの登録が不要となっています。専門家は、月 200 ドルといった優遇されたサブスクリプションプランが一時的な損失誘発策であり、エンタープライズは最終的にトークンあたり標準的な API 価格に直面する可能性があると警告しています。「1/3 のコストで Fable 相当の結果」といった主張に対する懐疑については、ルーターが高品質モデル(Fable など)を必要に応じてのみ使用することで費用を節約するためであることを明確化することで対処します。これらの進展は、業界がよりアクセス可能なインフラストラクチャーへと移行するにつれて、企業が品質、速度、価格を慎重にバランスさせることを迫っています。

2026/07/24 6:05

Namecheap が単に依頼されたからといって、私のアカウントを未確認の第三者に引き渡した

## Japanese Translation: CVC Capital パートナーズによる 2025 年 9 月の買収およびそれ以降の経営陣の変更を経て、Namecheap は深刻なユーザー信頼危機に直面しており、その要因は重大なセキュリティ上の見落としと運用上の不安定さにある。具体的な不満事項には、正当な理由なく WHOIS データが準拠しているにもかかわらずアカウントがロックされること、Link などのサードパーティ系決済プロセッサーへの強制的な移行(これらは侵襲的な SMS 認証を要求する)が含まれる。さらに、攻撃者に対しソーシャルエンジニアリングを用いて完全なドメイン制御権を付与したという致命的なサポートインシデントが発生しており、これは無料で利用可能なプライバシーツールが提供できたはずの保護を回避するという事案である。これらの問題は、私募資金出資(プライベートエクイティ)による所有下でのコスト削減が本質的なセキュリティ慣行を犠牲にすることで生じる「enshittification」の傾向を象徴している。その結果、ユーザーは Cloudflare、Porkbun、Gandi といった低価格かつより安全な代替手段へ急激に移りつつあり、これは従来の高マージン型のドメインレジスターからの市場シフトではなく、堅牢なセキュリティを優先するレジスターへの転換を示している。 ## Text to translate: Following its September 2025 acquisition by CVC Capital Partners and subsequent leadership changes, Namecheap is facing a critical crisis of user trust due to severe security lapses and operational instability. Specific grievances include accounts being locked without valid reason despite compliant WHOIS data, forced migration to third-party payment processors like Link that demand invasive SMS verification, and catastrophic support incidents where agents granted attackers full domain control through social engineering—bypassing protections that free privacy tools could have provided. These issues exemplify the "enshittification" trend where cost-cutting under private equity ownership sacrifices essential security practices. Consequently, users are rapidly migrating to cheaper, more secure alternatives like Cloudflare, Porkbun, and Gandi, signaling a market shift away from traditional high-markup registrars toward those prioritizing robust security.