{"id":51013189,"url":"https://github.com/neguse/lub","last_synced_at":"2026-06-21T06:03:11.276Z","repository":{"id":359993950,"uuid":"1234515331","full_name":"neguse/lub","owner":"neguse","description":"Lua-driven live coding 3D game engine.","archived":false,"fork":false,"pushed_at":"2026-06-18T16:54:40.000Z","size":50495,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-18T18:27:37.218Z","etag":null,"topics":["gamedev","hot-reload","live-coding","lua","sdl","slang","sokol"],"latest_commit_sha":null,"homepage":"https://lub.neguse.net/","language":"C","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/neguse.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"docs/roadmap.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-10T09:34:15.000Z","updated_at":"2026-06-18T16:54:45.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/neguse/lub","commit_stats":null,"previous_names":["neguse/lub"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/neguse/lub","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neguse%2Flub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neguse%2Flub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neguse%2Flub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neguse%2Flub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/neguse","download_url":"https://codeload.github.com/neguse/lub/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/neguse%2Flub/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34596047,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gamedev","hot-reload","live-coding","lua","sdl","slang","sokol"],"created_at":"2026-06-21T06:03:10.570Z","updated_at":"2026-06-21T06:03:11.270Z","avatar_url":"https://github.com/neguse.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lub\n\n`lub` は、細部までこだわったゲーム体験を作るためのコード中心のゲーム開発環境。\nゲームの動作を止めずにコード変更を即座に反映し、試行錯誤の速度を上げることを\nコアな価値に置く。\n\nruntime は C/C++ と既存ライブラリで組み、Lua を通して API を呼び出す。\n上位の script layer は Haxe で書き、Lua に transpile して hot reload する想定。\n特定のアセット形式や GUI editor に依存せず、ゲームを構成する状態、描画、\n入力、物理、音、debug 情報をコードから制御できる環境を目指す。\n\n現時点の実装は SDL3 + Slang + Lua 5.5 を基盤にし、GPU backend として\n**sokol_gfx (Vulkan/WebGPU)** と **SDL3 GPU API** の 2 系統を持つ。\n対応プラットフォームは Linux x86_64、Windows x86_64、WebAssembly/WebGPU。\n\n## ドキュメント\n\n- [docs/design.md](docs/design.md): lub の why / to-be / 設計原則。\n- [docs/roadmap.md](docs/roadmap.md): API を固めるための実装順序。\n- [docs/profile.md](docs/profile.md): Release 計測と汎用 CPU profiler。\n\n## ビルド\n\n依存:\n- CMake 3.20+\n- C11 / C++17 対応コンパイラ (GCC / Clang / MSVC)\n- Vulkan SDK / loader\n  - Linux — Arch: `vulkan-icd-loader`、Debian/Ubuntu: `libvulkan-dev`\n  - Windows — LunarG Vulkan SDK (`winget install KhronosGroup.VulkanSDK`)\n\nSlang prebuilt (`slang.dll` / `libslang.so` 等) は configure 時に\n`third_party/slang/lib/` に無ければ GitHub release から自動取得する\n(`third_party/slang/{lib,bin}/` は gitignore 対象)。\n\nLinux:\n\n```sh\ncmake -S . -B build\ncmake --build build -j\n```\n\nWindows (PowerShell, MSVC + Ninja):\n\n```powershell\n$env:VULKAN_SDK = \"C:\\VulkanSDK\\1.4.341.1\"  # winget でインストールされた SDK\n\u0026 'C:\\Program Files\\Microsoft Visual Studio\\18\\Professional\\VC\\Auxiliary\\Build\\vcvars64.bat'\ncmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release\ncmake --build build -j\n```\n\nWindows Release build は手順を固定するため、通常は script 経由で行う:\n\n```powershell\npwsh -NoProfile -ExecutionPolicy Bypass -File .\\scripts\\build-release.ps1\n```\n\nLinux Release build も同じく script 経由:\n\n```sh\nbash scripts/build-release.sh\n```\n\n詳細は [docs/release-build.md](docs/release-build.md) を参照。\n\nCMake の POST_BUILD で `SDL3.dll` と Slang ランタイム DLL 群が `lub.exe`\nの横にコピーされるので、追加の PATH 設定なしで実行できる。\n\n## コードフォーマット\n\n各フォーマッタの **デフォルト設定** で整形する。既存スタイルに寄せる\nプロジェクト固有の設定ファイル (`.clang-format` / `hxformat.json` /\n`.prettierrc`) は意図的に置かず、ツール標準のスタイルに従う。\n\n- C/C++ — `clang-format` (LLVM default)\n- Haxe — `haxelib formatter` (要 `haxelib install formatter`)\n- Web TS — `prettier` (`web/` で `npm install` 後に利用可)\n\n```sh\nscripts/format.sh            # 全ソースを整形\nscripts/format.sh --check    # 整形が必要か確認のみ (CI 向け / 非ゼロ終了で失敗)\n```\n\n## 実行\n\n```sh\n./build/lub samples/01_triangle/01_triangle.hxml\n```\n\n(Windows は `.\\build\\lub.exe samples\\01_triangle\\01_triangle.hxml` 形式)\n\nサンプルは `samples/\u003cname\u003e/` に 1 つずつ自己完結する形で置く\n(`\u003cClassName\u003e.hx` + `\u003cname\u003e.hxml` + `data/`)。\n\n### Haxe sample の実行\n\nHaxe で書いた sample を `.hxml` を entry にして直接実行できる。`.hx` 編集 → 保存で hot reload される。\n\n依存:\n- **Haxe 5.0.0-preview.1**(`haxe --version` で確認)。web playground(client-only wasm\n  コンパイル)と native で版を揃える。`scripts/install-haxe5.sh` でローカル導入できる\n  (system の haxe を壊さない)。\n- 1 回だけ extern を haxelib に登録: `haxelib dev lub \u003crepo\u003e/haxe-lib/lub`\n\n```sh\n# system の haxe が 5.0.0-preview.1 ならそのまま:\n./build/lub samples/01_triangle/01_triangle.hxml\n\n# 別バイナリ(例: scripts/install-haxe5.sh で入れた ~/haxe5)を使う場合:\nexport LUB_HAXE=\"$HOME/haxe5/haxe\"   # native player が spawn する haxe。\n                                     # HAXE_STD_PATH は隣の std/ から自動補完される。\n./build/lub samples/01_triangle/01_triangle.hxml\n```\n\n`samples/01_triangle/Triangle01.hx` を編集して保存すると、走っている game に即反映される。\n\n#### sample の書き方\n\n`-cp samples/\u003cname\u003e / -lib lub / -main \u003cClassName\u003e` の 3 行 hxml + Haxe class 1 個が最小構成。\n\n```haxe\nimport lub.Lub;\nimport lub.Gfx;\n\nclass Triangle01 {\n  public static function main() {}\n  public static function onInit() { Lub.config({ backend: \"sokol\" }); }\n  public static function onFrame() {\n    Gfx.beginPass({ target: Gfx.mainTex, clear_color: lua.Table.fromArray([0.1, 0.1, 0.2, 1.0]) });\n    Gfx.endPass();\n  }\n}\n```\n\nextern は責務別に分割:\n- `lub.Lub` — `config()`\n- `lub.Gfx` — 描画 / GPU / 定数\n- `lub.Input` — `keyDown()`\n- `lub.Io` — `samples/lub_io.lua` の cached loader (`loadText`/`loadFloats`/`loadGltf` 等)\n- `lub.Sys` — 低 level primitive (普段不要、Haxe stdlib `Sys` を import するときは衝突に注意)\n- `lubx.Png` — PNG load/write helper (`Bytes` backed)\n\n#### 注意点\n\n- **配列リテラル**: `Gfx.beginPass({ clear_color: [...] })` のように Haxe array をそのまま渡すと 0-indexed の Lua table になり lub C 側 (1-indexed 期待) と噛み合わない。`lua.Table.fromArray([0.1, 0.1, 0.2, 1.0])` で明示変換する。named field の anonymous structure (`{ target: x }`) は対象外。\n- **環境変数読み出し**: `Sys.getEnv(...)` は使えない (Haxe stdlib が `luv` を require するため)。代わりに `lua.Os.getenv(...)` を使う。\n\nLinux ヘッドレス (Mesa lavapipe = CPU Vulkan):\n\n```sh\n# 事前: sudo pacman -S vulkan-swrast (Arch) / sudo apt install mesa-vulkan-drivers (Debian)\nscripts/run-headless.sh samples/01_triangle/01_triangle.hxml\n```\n\n`scripts/run-headless.sh` は `VK_ICD_FILENAMES` で lavapipe ICD を強制し、\n`DISPLAY` / `WAYLAND_DISPLAY` が無ければ自動で `xvfb-run` でラップする。\nこれにより CI / SSH / コンテナ環境でも native sample を走らせられる\n(Mesa lavapipe / AMD radv 双方で動作)。\nWindows 用のヘッドレス wrapper は無く、実 GPU で動かす前提。\n\n### Sprite benchmark (Release)\n\n`rsushi` 風のスプライト数ベンチは、Release build と実行を毎回組み立てず\n固定スクリプト経由で走らせる:\n\n```powershell\npwsh -NoProfile -ExecutionPolicy Bypass -File .\\scripts\\run-sprites-bench.ps1\n```\n\n```sh\nbash scripts/run-sprites-bench.sh\n```\n\nscore の見方や `-NoBuild` / backend 切替は [docs/sprites-bench.md](docs/sprites-bench.md) を参照。\n\nスクリーンショット capture (PNG 出力):\n\n```sh\n# 30 フレーム描画後にキャプチャして即終了\nscripts/run-headless.sh samples/01_triangle/01_triangle.hxml --capture out.png --capture-frame 30\n```\n\nLua/Haxe 側で任意の render target を保存する場合は readback handle を作り、\n`id` 付きで request してから次の call 以降で結果を drain する:\n\n```haxe\nvar rb = Gfx.readback();\nvar r = rb.readTexture(tex, frame == 30 ? 30 : null);\nif (r.status == \"ready\" \u0026\u0026 r.id == 30) {\n  lubx.Png.write(path, r.bytes, r.width, r.height, r.stride);\n}\n```\n\nreadback queue depth は既定 8。必要な場合だけ起動時 config で変更できる\n(Lua: `config({ readback_depth = N })`, Haxe: `Lub.config({ readback_depth: N })`,\n1..32)。\n\n### Golden image diff (回帰テスト)\n\n```sh\nscripts/run-golden.sh             # 全 sample × 両 backend を tests/golden と cmp\nscripts/run-golden.sh --update    # golden 画像を再生成 (描画意図的変更時)\nscripts/run-golden.sh --sample 01_triangle --backend sokol\n```\n\nlavapipe + xvfb 環境では capture が確定的なので `cmp -s` で完全一致判定する。\n実 GPU でのドリフトは想定範囲外 (tolerance 比較は別途)。\n\n## Backend 切替\n\nlub は内部に 2 つの GPU backend を持つ:\n\n- `sokol` (default) — sokol_gfx (Vulkan)\n- `sdlgpu` — SDL3 GPU API (現在 Vulkan で実装、将来 Metal / D3D12 にも展開可能)\n\n切替は Lua の `on_init` 内で `config({ backend = \"sdlgpu\" })` を呼ぶ。\nサンプルでは `arg[1]` または環境変数 `LUB_BACKEND` を見るパターン:\n\n```lua\nfunction on_init()\n    config({ backend = os.getenv(\"LUB_BACKEND\") or \"sokol\" })\nend\n```\n\n```sh\n# default = sokol\n./build/lub samples/01_triangle/01_triangle.hxml\n\n# SDL3 GPU 経路\nLUB_BACKEND=sdlgpu ./build/lub samples/01_triangle/01_triangle.hxml\n\n# headless でも同じ\nLUB_BACKEND=sdlgpu scripts/run-headless.sh ./build/lub samples/01_triangle/01_triangle.hxml\n```\n\nどちらの backend でも同一 Lua API で sample が動く。render target の readback は\n`Gfx.readback()` で作った handle を使う。\n\n## Live edit (file watching)\n\nサンプルは `samples/\u003cname\u003e/data/` 配下の外部ファイルから shader / 頂点データ / テクスチャを\n読み込む。起動中にファイルを編集して保存すると次フレームから反映される。\n\n仕組み:\n\n- 各サンプル冒頭で `lub_io` / `lubx_png` を `require` し、`load_text` /\n  `load_floats` / `Png.load` を経由してリソースを取得する。\n- helper は `path → {mtime, content_hash}` のキャッシュを持ち、毎フレームの\n  `stat()` 1 回だけで「変化なし」を判定する。mtime 違い時のみ再読み込みして\n  FNV-1a 64 ハッシュを取り、それを `version` として `use_*` に渡す。\n- C 側は `version` 違いで in-place update (buffer / texture) または recompile\n  (shader) を実施。shader recompile 時は旧 shader を参照する pipeline cache\n  entry を sweep してリークを防ぐ。\n- shader compile error 時は旧 shader を維持してログを出すのみで、クラッシュせず\n  エディタで修正→保存すれば復帰する (初回 compile 失敗だけは loud に止める)。\n\n例: `samples/01_triangle/data/01_triangle.fs.slang` の出力色をエディタで書き換えて保存すると、\n起動中の `samples/01_triangle/01_triangle.hxml` の三角形の色が即座に変わる。\nPNG を別画像で上書きすればテクスチャも、`*.verts.lua` を編集すれば頂点も同様。\n\n## WASM playground (web)\n\nブラウザ上で動く Vite + CodeMirror ベースの playground を `web/` 配下に同梱。\nsokol-gfx の WGPU backend を target に WASM へクロスコンパイルしたバイナリを iframe\nで読み込み、左ペインのエディタで `.slang` / `.lua` を編集すると 300ms debounce で右ペインの\nプレイヤーに同期される (`samples/\u003cname\u003e/data/*` の mtime/hash hot-reload 経路を再利用)。\nshader compile は [slang-wasm](https://github.com/shader-slang/slang/releases) を vendor。\n\n### Build\n\n```sh\n# 1. WASM バイナリを生成 (Linux/macOS — emsdk が必要)\nsource ~/emsdk/emsdk_env.sh             # emcc / emcmake を PATH に\nemcmake cmake -S . -B build/wasm        # WGPU + emdawnwebgpu port が configure される\ncmake --build build/wasm -j             # lub.{js,wasm,data} が生成\n\n# 2. JS 側の依存と slang-wasm を取得\ncd web\nnpm install                             # postinstall で web/scripts/fetch-slang-wasm.sh が\n                                        # web/public/slang/ に slang-wasm.{js,wasm} を取得\nnpm run dev                             # http://localhost:5173/ で dev server 起動\nnpm run verify                          # 別端末: playwright + swiftshader で headless 検証\nnpm run build                           # web/dist/ に production bundle 生成\n```\n\n### アーキテクチャ\n\n```\n            parent (index.html / main.ts)              iframe (player.html / player.ts)\n            ┌────────────────────────────┐             ┌──────────────────────────────────┐\n            │ CodeMirror editor          │   setFiles  │ slang-bridge.ts                  │\n            │   path -\u003e content table    │  ────────▶  │   window.slangCompile() を export │\n            │ sample dropdown / restart  │  syncFiles  │ WebGPU device 取得 → preinit     │\n            │ debounce 300ms             │  ────────▶  │ lub.js (Emscripten module)     │\n            └────────────────────────────┘  ◀─player──│   ↑ EM_ASYNC_JS bridge            │\n                                            Ready/log │   ↑ FS.writeFile で MEMFS overlay │\n                                                       │ sokol_gfx (WGPU) — canvas へ描画 │\n                                                       └──────────────────────────────────┘\n```\n\npostMessage プロトコル:\n\n- `parent → iframe`: `setFiles {files, entry}` (初回ブート時 1 回), `syncFiles {files}` (編集毎)\n- `iframe → parent`: `playerReady` (ハンドシェイク), `log {level, msg}` (console relay)\n\nshader compile は C 側 (`src/shader.cpp`) の `EM_ASYNC_JS` shim から\n`window.slangCompile(src, entry, stage)` を呼び、`{wgsl, reflectJson}` を `'\\x01'`\n区切りで pack して戻す。エラーは `'\\x02' + msg` 形式で Slang diagnostic として\nerr_buf に届く。\n\nMEMFS sync: iframe 側で Emscripten の data file package (`lub.data`) をマウント\nした直後に `FS.writeFile` でエディタ内容を上書きする (`player.ts` の `postPreload`\nhook)。実行中の `syncFiles` も同じ `FS.writeFile` 経路で、C 側は次フレームの\n`stat()` で mtime 違いを検知して reload する (native と同じ hot-reload コード)。\n\n### サンプル対応状況 (web)\n\nweb playground の対象 sample は `web/` 側の sample list と verify script で管理する。\n\n### Browser requirements\n\n- WebGPU が利用可能なブラウザ:\n  - **Chrome / Edge** (primary、137+) — 既定で WebGPU 有効。\n  - **Firefox Nightly** — `dom.webgpu.enabled` を `about:config` で有効化。\n- ローカル開発: Vite dev server が emdawnwebgpu に必要な CORS/MIME 設定を済ませる。\n- production bundle (`npm run build`) は `web/dist/` 配下、`/wasm/`,\n  `/slang/` への絶対パス前提なので site root に置く。\n\n### Headless verification\n\n`npm run verify` は playwright + chromium (swiftshader Vulkan) で:\n\n1. sample 01 の初期描画 (orange triangle on dark blue clear) を pixel bucket で確認\n2. fragment shader を編集 → green になる\n3. lua の clear_color を編集 → 背景が red になる\n4. verts を縮小編集 → green pixel 数が減る\n5. 登録済み sample を順に切替 → 各サンプルの非黒描画を確認\n\nスクリーンショットは `/tmp/lub-verify/` に出力される。CI 利用時は dev server を\n別ジョブで立ち上げてから `LUB_URL=http://...` を指定すること。\n\n### Live edit caveats\n\n- shader に syntax error がある場合: 既存の shader を維持して Slang diagnostic を\n  iframe log に流すのみ (next save で復帰)。初回 compile 失敗のみ load を止める。\n- 300ms debounce: 入力後 300ms 静止してから `syncFiles` を送る。連打中は更新されない。\n- サンプル切替時に dirty な編集があると `confirm()` で警告する。\n\n### Known limitations\n\n- **`--capture` の swapchain capture は native sokol のみ**。任意 render target の\n  readback は `Gfx.readback()` を使う。\n- **sdlgpu backend は web 非対応**。WGPU backend の sokol のみ。\n\n## ライセンス\n\n- lub 本体(C ランタイム / web playground / samples / `haxe-lib/lub`)は **MIT**(`LICENSE`)。\n- web playground がブラウザ内で使う **Haxe コンパイラ wasm は GPL-2.0-or-later**(改変版、\n  ビルド用パッチは `spike/patches/`)。ツールとしての同梱=集約で、lub 本体には伝播しない。\n- バンドル/リンクする第三者依存(SDL3 / sokol / Lua / Slang / Haxe std 等)は\n  `THIRD_PARTY_LICENSES.md` を参照。spike の内訳は `spike/LICENSE`。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneguse%2Flub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fneguse%2Flub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fneguse%2Flub/lists"}