@stormcat-works/stormmin
v0.7.1
Published
StormMin: Rust/WASM Lua size optimizer for browser and Node, plus a native-binary CLI with WASM fallback and a demo-UI server
Readme
@stormcat-works/stormmin
StormMin は Stormworks: Build and Rescue の vehicle Lua(マイコンの Lua スクリプト)向けサイズ最適化コンパイラです。プログラム全体を AST・レキシカルバインディング・副作用解析にもとづいて変換し、意味を保存したままゲームの 8192 文字制限に収めやすい Lua を生成します。単なる空白除去・変数リネームのミニファイアではなく、複数の最適化パスと候補探索を組み合わせた最適化コンパイラです。
インストール不要の Web 版はこちら → https://www.makkii.jp/tools/stormworks/stormmin/
β版につき、出力コードにコンパイラ起因のバグが含まれる可能性があります。最適化後のコードは必ずゲーム内で動作確認してください。
このパッケージについて
コンパイラは公開ライブラリStorm Lua EngineのRust実装を使用します。Storm Minは専用CLI/Webと非公開の回帰検証を所有します。このパッケージにはWASM・nativeバイナリ、JavaScriptのloader/Worker/UI、型定義を同梱し、Rustソースや非公開コーパスは含めません。同梱物は次のとおりです。
- CLI —
stormminコマンド。同梱 native バイナリ(下表)を実行し、対応外プラットフォームでは単一ファイル/JSONプロジェクトのcompileをNode + WASMで実行します(--entry/--modules-dir、lintはnative専用) - ブラウザ用ライブラリ —
import { compile }で coordinator Web Worker が WASM を遅延ロードし、メインスレッドを塞ぎません - Node 用ライブラリ — 同じ import 指定子が Node では in-process WASM 実行に解決されます(出力はブラウザ版と byte 単位で同一)
- デモ UI —
stormmin serveで Web 版と同じ UI をローカル静的配信
| 実行環境 | 要件 |
| --------------- | ------------------------------------------------------------------------------------------- |
| Node | 20 以上 |
| native バイナリ | linux-x64 / win32-x64 / darwin-arm64 / darwin-x64 |
| ブラウザ | module Worker が使える現行ブラウザ。SharedArrayBuffer / cross-origin isolation は不要 |
CLI
npm i -g @stormcat-works/stormmin
stormmin input.lua -o output.lua
# インストールせずに1回だけ
npx @stormcat-works/stormmin input.lua -o output.luastormmin [compile] [OPTIONS] [FILE]
stormmin compile --entry NAME --modules-dir DIR [OPTIONS]
stormmin compile --project project.json [OPTIONS]
stormmin lint --entry NAME --modules-dir DIR [--format text|json]
FILE 省略時は標準入力(明示する場合は '-'。単一ファイルモードのみ)
compile オプション:
-o, --output FILE 標準出力の代わりに FILE へ書き出す
--options JSON コンパイル/compileProject オプションを JSON で指定
--options-file FILE 同上を JSON ファイルで指定
--json 結果全体を JSON で出力(code の代わりに)
--entry NAME プロジェクトモード: entry モジュールキー
--modules-dir DIR プロジェクトモード: DIR を再帰走査して *.lua をモジュールとして読み込む
--project FILE プロジェクトモード: LuaProject 全体を JSON ファイルで指定
--no-minify プロジェクトモード: リンクのみ(可読出力 + Source Map)
--map FILE 最適化後または非短縮のSource Map v3を別ファイルへ書き出す
-h, --help ヘルプ
-V, --version バージョン
lint オプション:
--entry NAME / --modules-dir DIR / --project FILE (compile と同じ)
--options JSON / --options-file FILE AnalyzeOptions(例: disabledRules)
--format text|json 出力形式(既定 text)
Subcommands:
serve [--port N] 同梱デモ UI をローカル静的配信例:
# 目標 8192 文字の高速モードで最適化し、結果メタデータも見る
stormmin input.lua --options '{"targetSize":8192}' --json
# property をハードコードして最適化
stormmin input.lua --options '{"property":{"mode":"hardcode","numbers":{"Gain":1.5}}}'
# 複数モジュールをリンク + minify
stormmin compile --entry main --modules-dir src/ -o out.lua
# リンクのみ(可読出力)+ Source Map
stormmin compile --entry main --modules-dir src/ --no-minify --map out.lua.map
# 編集中リント(CI・エディタ統合向けに JSON 出力)
stormmin lint --entry main --modules-dir src/ --format json--modules-dir は相対パス foo/bar.lua をモジュールキー foo.bar へ変換します(区切りは / のみ)。この対応関係に合わないファイル名(ベース名にドットを含む等)やキー衝突はサイレントスキップせず invalid-module-key 診断として報告します。
ライブラリ API
import { compile } from '@stormcat-works/stormmin';
const result = await compile(source, { mode: 'smallest' });
if (!result.ok) throw new Error(result.error);
console.log(result.code, result.size);公開 API は次の6つだけです。パス単位の細かい API は公開していません。
compile(source, options?) -> Promise<CompileResult>— 単一ソースの最適化本体compileProject(project, options?) -> Promise<ProjectCompileResult>— 複数モジュール(require)+ambient(sim.*等)をリンクし、minifyオプション既定trueでそのまま最適化まで行うanalyze(project, options?) -> Promise<AnalyzeResult>— 解析専用(リンク時と同じ parity 規則 + lint)。構文エラーでも例外を投げずdiagnosticsに積む。編集中リント向けscanProperties(source) -> Promise<PropertyScanResult>— ソース中のproperty.get*読み取りを列挙(hardcode 設定の下準備用)passMetadata() -> Promise<PassMetadataEntry[]>— 表示用パス記録名とIDの対応(全67パスの一覧とは異なる)terminate()— ブラウザのWorkerを破棄。Nodeではno-op
AI Handoff(Web)
Web版の「AIに渡す」は、入力Luaと利用可能な公式ツールをMarkdownまたはJSONへまとめます。設定/Property値と直近のコンパイル結果は個別に含めるか選択できます。docs.makkii.jpの検索MCP https://docs.makkii.jp/mcp、Storm Min CLI/npm API、Storm Lua EngineのSource Map検証APIを明示し、外部AI/Agentが仕様を推測せず機械検証できるようにします。
Storm Min自体には公開HTTP compile APIはありません。HandoffのJSONは kind: "stormmin-ai-handoff" / schemaVersion: 1 です。Source Map本体は自動埋め込みしません。
最適化後Source Map(Engine v0.3.0対応)
Storm Min 0.7.0以降で利用できる機能です。await compile(source, { sourceMap: true, sourceName: "controller.lua" })のcodeとmapを一組で保持します。compileProject(project, { sourceMap: true })にも対応します。CLIはstormmin input.lua -o output.lua --map output.lua.mapです。--map指定時は生成を有効化し、--jsonと併用しても指定ファイルへの保存を省略しません。
mapは標準Source Map v3にEngineのx_storm拡張を含むJSON文字列です。通常のreaderで元位置を参照でき、詳細の検証・解釈はEngineのcompiler SDKが所有します。非短縮project mapは従来どおり返します。mapをLuaに埋め込まず、8192文字制限へ加算しません。
原文全文と固定化された設定を含みます。共有先を確認し、生成Luaを編集した後に古いmapを使用しないでください。 Webでは生成を明示的に有効にし、.luaと.mapを同じbasenameで保存できます。
CompileOptions
| オプション | 型 / 値 | 既定 | 説明 |
| ------------------ | -------------------------- | --------------------- | ---------------------------------------------------------------------------------------- |
| sourceMap | boolean | false | 最適化後のmapを生成 |
| sourceName | string | input.lua | 単一ソースの表示名(空文字/NUL不可)。CLIは入力basenameまたはstdin.luaを使用 |
| mode | 'smallest' | 'safe' | smallest | smallest は積極的に最小化。safe はホスト環境の数値演算差などに対して保守的な変換のみ |
| property | 下記 | { mode: 'runtime' } | property の扱い |
| numericMode | 'tolerant' | 'exact' | tolerant | tolerant は許容誤差内の数値近似を許可。exact は近似・単位リスケール等を無効化 |
| numericTolerance | { abs, rel } | 1e-6 / 1e-6 | tolerant 時の絶対・相対許容誤差 |
| targetSize | number | なし | 指定すると satisficing search を有効化(後述) |
| searchMode | 'exhaustive' | 'fast' | exhaustive | fast は軽量サイズ推定によるビーム探索 |
| searchBeamWidth | 1〜16 | 4 | fast 時のビーム幅 |
| zeroCostNewlines | boolean | true | 改行を文字数 0 として扱う(Stormworks の制限カウント仕様に合わせる) |
| passToggles | Record<string, boolean> | {} | パス個別 ON/OFF。バグ回避用の一時的な逃げ道で、通常運用の調整つまみではない |
property:
{ mode: 'runtime' }—property.getNumber()/getBool()/getText()呼び出しを維持(既定){ mode: 'hardcode', numbers: {...}, bools: {...}, texts: {...} }— 指定した property 読み取りだけを literal 化。ローカル変数でpropertyが shadow されている箇所は対象外
targetSize(satisficing search)
targetSize を指定すると、「最短を探し続ける」代わりに「制限を満たすコードをできるだけ早く得る」探索に切り替わります。目標以下の意味保存候補が得られた時点で停止するため大幅に高速です。
const result = await compile(source, { targetSize: 8192 });
console.log(result.size, result.search.targetMet, result.search.stage);- 探索は決定的なチェックポイント列(軽量候補 → コスト考慮候補 → full search フォールバック)で、実行時間の計測値を順位付けに使わないため結果は毎回同じです
- 目標未達でも最良の結果を返し、
search.targetMetがfalseになります targetSize未指定時は従来どおり最短を探します
CompileResult(主要フィールド)
| フィールド | 説明 |
| ----------------------------------------- | --------------------------------------------------------------------------------------------- |
| ok | 成功なら true。false のとき error に理由 |
| code | 最適化後の Lua ソース |
| map | sourceMap:trueで生成した、codeと対になるSource Map JSON文字列 |
| original / size / saved | 入力文字数 / 出力文字数 / 削減文字数 |
| elapsedMs | 所要時間 |
| passes | 適用パスの記録(name / saved / elapsedMs など) |
| diagnostics | 診断一覧(Diagnostic[]。下記参照) |
| search | 探索統計。targetSize 指定時は targetMet / stoppedEarly / checkpoints / stage を含む |
| propertyMode / propertyReadsHardcoded | property の扱いと literal 化した読み取り数 |
Diagnostic(compile / compileProject / analyze 共通)
interface Diagnostic {
code: string; // 安定な kebab-case 識別子。廃止はあっても意味は変わらない
severity: 'error' | 'warning' | 'info';
message: string; // 常に英語固定。UI ローカライズは code をキーにアプリ側で行う
module?: string; // プロジェクトAPIのみ: 関連モジュールキー
range?: { line: number; col: number; endLine?: number; endCol?: number }; // 1-based
}プロジェクトAPI(compileProject / analyze)
複数モジュール(Lua の require)と sim.* のような ambient 名前空間を扱う窓口です。コアはファイルパスを知りません。LuaProject.modules はモジュールキー(. 区切りの Lua 識別子。例: lib.util)→ ソース文字列の map です。
import { compileProject, analyze } from '@stormcat-works/stormmin';
const project = {
entry: 'main',
modules: {
main: 'local util = require("lib.util")\nreturn util.f()\n',
'lib.util': 'local M = {}\nfunction M.f() return 1 end\nreturn M\n',
},
};
const linted = await analyze(project); // { ok, diagnostics }
const result = await compileProject(project, { minify: false }); // 可読リンク結果 + Source Map v3compileProject(project, options?)optionsはCompileOptionsを全て含んだ上でminify?: boolean(既定true)を追加したもの- エラー時(require/ambient の規則違反・構文エラー等)は
ok:falseとdiagnosticsのみを返す(code/mapは返さない) minify:falseのときだけ Source Map v3(JSON 文字列)をmapに返すusedModules(リンクされたモジュールキー、実行順)・injectedAmbient(注入された ambient メンバー)も返す
analyze(project, options?)options.disabledRulesで診断コードを個別に抑制できる(severity: 'error'は抑制不可)- 構文エラーでも
ok:trueのままdiagnosticsにsyntax-errorとして返す(編集中の呼び出し前提。他モジュールの解析は継続する)
require("key")は各モジュールのトップレベル直下の文としてのみ認識されます(式の一部・条件分岐内・動的文字列は全てエラー診断)。詳細な入力スキーマ・require/ambient の制限・診断コード一覧はコアリポジトリの設計ドキュメントを参照してください。
Stormworks 固有制限の検出
Stormworks サンドボックスは標準 Lua の一部しか持たず、input./output. は onTick コールバックの中でしか使えません。analyze() は warning、compileProject() は error(ok:false)としてこれらを検出します(「リンター警告は出るが実行はできる、Minify だけは失敗する」という体験)。
sw-unavailable-global:pcall/setmetatable/os/io/coroutine/debug等、標準 Lua ビルトインとして既知だが Stormworks に存在しないグローバルへの参照(自前で同名グローバルを定義していれば対象外)。error/assertは実機での可否が未確認のため対象外(要確認)。input-outside-ontick/output-outside-ontick:onTickに代入される関数リテラル(function onTick() ... end/onTick = function() ... end)の本体(ネストした内側関数を含む)以外でのinput./output.参照。関数呼び出し経由の間接参照は検出しません。
意味保存の保証と決定性
- 同一入力・同一オプションからは byte 単位で同一の出力を生成します
screen.setColor/screen.draw*の観測可能な呼び出し順序を保存します- 式の評価回数・評価順・エラー発生位置を変える変換は、安全性を証明できる条件下でしか行いません
- 変数の同値判定は名前文字列ではなく解決済みバインディング単位で行います。
math/screen/input/output/propertyと同名のローカル変数が定義されているコードでも誤変換しません - pack/unpack・ビット演算・テーブル index・numeric-for の境界値に使われる数値は、
tolerantモードでも近似しません
性能の目安
入力 20,195 文字の実車両スクリプト、各5回計測の中央値:
| 条件 | Native CLI | ブラウザ (Chrome + WASM) |
| ------------------------------------- | ---------: | -----------------------: |
| 最短探索(出力 5,248 文字) | 約 2.2 秒 | 約 2.7 秒 |
| targetSize: 8192(出力 8,169 文字) | 約 0.6 秒 | 約 0.9 秒 |
既知の制限
- β版です。最適化は意味保存を目標に設計されていますが、全ての Lua スクリプトでの動作は未保証です。出力は必ずゲーム内で動作確認してください
- 対象は Stormworks vehicle Lua(
onTick/onDrawとマイコン API)です。汎用 Lua ミニファイアとしての利用は想定していません
ライセンス
- パッケージ本体: MIT License
- ソースコードは非公開ですが、パッケージに同梱された成果物(WASM / native バイナリ / JS ローダー)は MIT ライセンスで利用・再配布できます。
- 同梱の Rust モジュールおよび native バイナリで使用しているオープンソースライブラリのライセンス一覧と全文は、パッケージ内の
THIRD_PARTY_LICENSES.txtに記載されています(MIT, Apache-2.0, Unicode-3.0 等)。
リンク
- Web 版(インストール不要・最新版): https://www.makkii.jp/tools/stormworks/stormmin/
