本文へ移動
cccskills
無料GitHub で公開

krkrz-webui

吉里吉里Z (krkrz) の -replweb HTTP+SSE サーバ (WebServer クラス) にブラウザ UI を載せて、本体アプリの操作/編集/観測パネルをブラウザ側に組み込む方法論。ゲーム本体は 3D 表示やゲーム内 UI に専念させ、編集ツール・インスペクター・ダッシュボード・REPL コンソールをブラウザ (別ウィンドウ/別PC) から使う構成を作るときに読む。プラグインや TJS がサーバへエンドポイントを追加公開する手順 (WebServer.register / serveStatic / broadcast)、ハンドラ呼び出し規約 (req %[method,path,query,body,bytes] → 文字列/octet/整数/辞書)、状態同期パターン (fetch POST + SSE /sub push + throttle + 差分/tick 配信)、既存 REPL コンソール (/events + /cmd) の UI 埋め込み、ブラウザのアプリモード起動 (Chromium --app / -webui)、json.dll 連携、TJS2 由来のハマりどころ (ローカル関数クロージャ不在→クラス化 / ブロックコメントのネスト誤爆 / startup 例外の致命性 / ハンドラは必ずメインスレッド実行) を網羅。エンジン側 WebServer クラスそのものの仕様は krkrz core doc/REPL.md、REPL/Agent 駆動は skill krkrz-repl、Elements ネイティブ UI は skill elements、TJS2 言語は skill tjs2、本体 API は skill krkrz を参照。

インストール方法を見る

含まれるファイル(1)

  • SKILL.md20.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

パスの基点: 本文の相対パスは engine ルート基準 (krkrz_dev では src/core/ を前置。下記参照)。作業ディレクトリが krkrz_dev 以外 (krkrz_android / krkrz_ios などの外枠や案件フォルダ) のときは ${KRKRZ_BASE}/krkrz_dev/ を前置して読む (echo $KRKRZ_BASE で実パスを確認。マシンごとに値が違うので絶対パスは書き込まない)。

krkrz replweb ベース ブラウザ UI 組み込み方法論

krkrz エンジンの -replweb HTTP+SSE サーバ (KRKRZ_REPL_WEB ビルド) に乗せて、 本体アプリの操作・編集・観測 UI をブラウザ側に組み込むための方法論。 「ゲーム本体 = 表示 + ゲーム内 UI、ブラウザ = 編集ツール + インスペクター + REPL」 という分離を作れる。Elements ネイティブ UI (skill elements) の制約 (レイアウト/ 入力/コピペ/フォント) から解放され、UI 反復にエンジン再ビルドが要らないのが利点。

エンジン側 WebServer クラスそのものの仕様は krkrz core doc/REPL.md の 「ブラウザ REPL / Web サーバ」節が SSOT。本スキルはその上に UI を構築する側の 設計パターンをまとめる。

本文のエンジンパスは engine ルート相対 (doc/..., common/..., data/...)。 本スキルが置かれている umbrella (krkrz_dev) では src/core/ を前置して読む (例: src/core/doc/REPL.md)。engine 単独チェックアウトならそのまま。

いつ使うか / 使わないか

  • 使う: VRM/3D エディタ、ステージ/シーン編集、パラメータ調整パネル、ライブ インスペクター/ダッシュボード、リモート (別PC) 編集、REPL を UI に同居させたい。
  • 使わない: ゲーム内で完結する常駐 UI・モーダルダイアログ (→ skill elements)。 ゲームの一部として配布する対話 UI は Elements、開発/編集ツールはブラウザ、が目安。

前提

  • engine が KRKRZ_REPL_WEB ビルドであること (WebServer クラスが登録される)。
  • サーバ起動は WebServer.start([port]) をスクリプトから呼ぶのが基本 (既定 8899)。 -replweb オプションは不要 = 利用者に毎回付けさせなくてよい (アプリの startup で if (!WebServer.active) WebServer.start(8899);)。-replweb[=port] を明示した場合は 本体が先に起動するので、WebServer.start は二重起動を避けて no-op になる。 0.0.0.0:<port> で LAN 越し可 (WebServer.startAt("0.0.0.0", port)。信頼ネットワーク 限定・起動ログに警告)。待受 URL は WebServer.url / System.replWebURL。
  • JSON I/O を使うなら json.dll (Scripts.toJSONString / evalJSON)。下記参照。
  • 検証には krkrz-repl (-replfile チャネル / /cmd) と curl が便利。

4 層アーキテクチャ

層実体役割
① サーバengine common/utils/ReplWebServer.cpp + TJS クラス WebServer (ReplWebIntf.cpp)HTTP+SSE。ルーティング・静的配信・SSE チャネル。ハンドラは常にメインスレッド実行
② ハンドラネイティブ (プラグイン) or TJS/api/… や /app/… を処理。重い/型変換が要る処理はネイティブ、細かい操作は TJS
③ アプリブリッジTJS (アプリ側スクリプト)アプリのモデル層 (状態取得/操作関数) を HTTP ルートへ橋渡し + 静的配信登録 + SSE 配信
④ ブラウザ UIdata/ui/** の静的 HTML/JS単一ページ (ビルドレス vanilla JS で十分)。fetch POST + SSE 購読

最小手順

1. 静的 UI を配信する

// アプリ起動後 (startup.tjs 等) に一度
if (typeof global.WebServer != "undefined") {
    WebServer.serveStatic("/ui/", "ui/");   // data/ui/** を /ui/** で配信 (json.dll 不要)
}

data/ui/index.html を置けば http://127.0.0.1:8899/ui/ で開ける。 静的配信は Storages 経由 (xp3 内でも可)。.. はトラバーサル拒否 (403)。

2. 操作エンドポイントを登録する

// TJS2 はローカル関数のレキシカルクロージャを持たない (下記ハマりどころ)。
// ハンドラはクラスのメンバにして objthis 経由で相互参照させる。
class AppBridge {
    function AppBridge() {}
    function handler(req) {
        // req = %[ method, path, query, body(文字列), bytes(octet: body 非空時) ]
        if (req.path == "/app/state") return Scripts.toJSONString(buildState());
        var arg = (req.body != "") ? Scripts.evalJSON(req.body) : %[];
        if (req.path == "/app/do") { app.doSomething(arg.x); push(); return "{\"ok\":true}"; }
        return %[ "status" => 404, "body" => "{}" ];
    }
    function buildState() { return %[ /* アプリ状態 */ ]; }
    function push() { WebServer.broadcast("state", Scripts.toJSONString(buildState())); }
}
global.bridge = new AppBridge();
WebServer.register("/app/", bridge.handler);   // プレフィックス最長一致

3. ブラウザから叩く (index.html 内)

// 取得
const st = await (await fetch('/app/state')).json();
// 操作
await fetch('/app/do', {method:'POST', body: JSON.stringify({x:1})});
// 変更購読 (サーバ push)
const es = new EventSource('/sub/state');
es.onmessage = e => render(JSON.parse(e.data));

WebServer API 早見表

メンバ用途
WebServer.register(prefix, handler)動的ハンドラ登録 (最長一致・上書き)。サーバ未起動でも保持され起動時から有効
WebServer.unregister(prefix)解除
WebServer.serveStatic(prefix, storageDir)Storages 経由の静的配信。例 ("/ui/","ui/")
WebServer.unserveStatic(prefix)静的マウント解除
WebServer.broadcast(channel, text)/sub/<channel> 購読者へ SSE 配信 (改行可)
WebServer.registerPanel(id, label, path)組み込み UI へタブを 1 枚足す (下記)
WebServer.unregisterPanel(id)パネルを外す
WebServer.start([port]) / startAt(host, port)サーバをスクリプトから起動 (-replweb 不要)。既に稼働中なら no-op。戻り値=稼働中か
WebServer.stop()サーバ停止
WebServer.openBrowser([url [, appMode=true]])url をブラウザで開く。appMode 時 Edge/Chrome を --app で試し不可なら既定ブラウザへ。url 省略で稼働中 URL。SDL 版でもアプリモード可
WebServer.active / WebServer.url稼働中か / 待受 URL

組み込みルート: GET / = 埋め込み UI (Console / Watch のタブ) / GET /events = ログ SSE / POST /cmd = TJS 評価 / GET /sub/<ch> = 汎用 SSE / GET|POST /watch + /sub/watch = 監視式 / POST /pad/exec + GET|POST /pad/file = Pad / GET|POST /state + /sub/state = コントローラ / POST /bye (下記)。 組み込みルートは WebServer.register より先に判定されるので、これらのパスは 自前ハンドラで上書きできない (別名を使う)。

案件パネル (registerPanel) — 組み込み UI にタブを足す

自前 UI を丸ごと立てる前にこれを検討する。 serveStatic だけで別ページを 作ると Console / Watch / Pad とコントローラを失う。 パネルとして足せば同居できる。

WebServer.serveStatic("/tool/", "web/");
WebServer.registerPanel("mytool", "案件ツール", "/tool/tool.html");
  • 中身は iframe。組み込みページへスクリプトを注入する方式ではないので、 組み込み UI の内部 DOM に依存しない (向こうが変わっても壊れない)
  • 同一オリジンなのでパネルから /cmd /watch /pad/exec や自前 register エンドポイントを普通に叩ける
  • 同じ id で上書き。登録/解除は /sub/panels で開いているページへ即反映
  • 中身はタブを初めて開いたときに読む (重いツールでも起動は遅くならない)
  • path は / 始まりのサーバ上パス。不正なら登録されずログに理由が出る (⚠ msys2 の bash から curl で叩くと /tool/... が Windows パスへ 自動変換されて弾かれる。ファイル body か PowerShell を使う)

ブラウザ UI とアプリの寿命をそろえる (両方向)

ブラウザを UI にする構成では片方だけ残るのがいちばん困る。engine 側で 両方向を閉じてある。

本体 → ブラウザが居なくなったら終了 (-replwebidle)

  • 既定 5 秒で有効。-replwebidle=no で無効、=<秒> で変更
  • 一度でも SSE 購読が来てから武装する。だから購読を張らない エージェント駆動 / API 面利用は落ちない。自前 UI は EventSource を 1 本張っておけばそれが «ブラウザが居る» 印になる
  • 複数タブは購読数で扱うので最後の 1 枚まで落ちない
  • ページは pagehide/beforeunload で navigator.sendBeacon('/bye') を投げると 猶予が ~2 秒へ前倒しされる (自前 UI でも同じことをすると閉じが速くなる)。 投げなくても <秒> で畳まれる

ブラウザ → 本体が居なくなったら閉じる

  • /sub/state に {"exiting":true} が来たら即 window.close() (engine が Stop() で流す)
  • SSE が切れて 5 秒復帰しなければ close (クラッシュ / 強制終了)
  • close が効かない通常タブは全面オーバーレイへフォールバック
  • 自前 UI もこの 2 つを実装しておくと «本体だけ終わってページが残る» を防げる

ブラウザの自動オープン (-replwebopen)

既定は「ループバック束縛 かつ コンソール無し (GUI 起動)」のときだけ app モード。 端末から起動すると開かないので、開かせたいときは -replwebopen=app (tab = 通常ウィンドウ / no = 抑止)。TJS からは WebServer.openBrowser。

⚠ engine の app モードは msedge.exe --app=<url> を ユーザの通常プロファイル で起動する (プロファイル指定なし)。検証で --user-data-dir=<使い捨て> を 自分で渡すと Edge のオンボーディング (同期を促すダイアログ) がユーザの画面に 出るので、捨てプロファイルを使うなら --disable-sync --no-first-run --no-default-browser-check を添え、終わったら必ずウィンドウごと片付ける。

監視式 API (/watch)### Pad API (/pad/*) — 複数行スクリプトの実行と保存

  • POST /pad/exec — body をまるごと 1 回実行 (/cmd の 1 行実行と違い、 関数定義やループをそのまま流せる)。{"ok","result","error"}
  • GET /pad/file?path= — ストレージから読む (text/plain)
  • POST /pad/file?path= — ストレージへ書く。既定は 403。 本体を -replwebpad=<dir> で起動したときだけ、その接頭辞の配下へ書ける (⚠ セキュリティ境界ではなく «[保存] のうっかり» を防ぐ柵。/cmd で任意 TJS が 実行できる時点で全権限は開いている)

監視式 API (/watch) — 状態を張り込んで観測する

エンジン組み込み。式のリストを保持して、まとめて評価して返す。自前の インスペクターを書く前に、これで足りないか見ると早い。

ルート説明
GET /watch一覧+現在値 (JSON)。評価しないのでポーリング安全。?eval=1 で評価してから返す
POST /watchform-urlencoded。op=add&expr=… / op=rm&id=… / op=edit&id=…&expr=… / op=clear / op=interval&ms=… / op=eval。成功なら GET と同じ JSON
GET /sub/watch自動更新の push。値が変わったときだけ流れる (定数式を張っても無駄な配信は出ない)
await fetch('/watch', {method:'POST', body:new URLSearchParams({op:'add', expr:'System.getTickCount()'})});
await fetch('/watch', {method:'POST', body:new URLSearchParams({op:'interval', ms:'500'})});
new EventSource('/sub/watch').onmessage = e => render(JSON.parse(e.data));

payload = {"interval":500,"entries":[{"id":1,"expr":"…","value":"…","error":false}]}。 interval は -1=off / 0=毎フレーム / 正値=ms (下限 100ms)。式が例外を投げても value が "(error) …" になるだけで一覧は返る。

ハンドラ呼び出し規約

  • 引数 req = %[ method, path, query, body, bytes ] (bytes は body 非空時のみ octet)。
  • 戻り値の解釈:
    • 文字列 → 200 application/json
    • octet → 200 application/octet-stream
    • 整数 → そのステータスで空ボディ
    • void → 204
    • 辞書 %[status, mime, body(文字列 or octet)] → 明示指定
  • ハンドラ内の TJS 例外 → 500 (本文=メッセージ) + WebServer: handler error: ログ。
  • ハンドラは必ずメインスレッドで呼ばれる (ReplMainQueue タスク)。GL/TJS/エンジン API を自由に触ってよい。長い処理はフレームを止めるので注意 (Elements 版と同じ制約)。

状態同期パターン (実践)

  • client → server: fetch POST (body=JSON)。スライダ等の連続入力は throttle (例 50ms、キー単位で最新値のみ送る)。連続反映系は push() しない (操作元が真実。SSE 逆流で入力が上書きされるのを防ぐ)。
  • server → client: SSE /sub/state。
    • 各操作ルートの直後に push() (辞書一発)。
    • フレームループから ~4Hz で差分検知 push (前回 JSON と一致なら送らない)。 これで REPL 等 UI 外からの変更もブラウザに自動反映される。
  • エディタ DOM の再構築を壊さない: 高頻度で変わる観測データ (fps/ログ等) と 編集状態を分け、編集状態が変わったときだけ DOM を作り直す (観測は別 DOM を差分更新)。 スライダ操作中フラグを持ち、操作中は SSE 由来の再描画を抑止する。

REPL コンソールを UI に同居させる

エンジン組み込みの /events (ログ SSE、バックログ2000行・レベル別 cls 付き) と /cmd (POST body=1行を TJS 評価、応答 body は継続入力中なら "1") をそのまま ブラウザ UI 側で購読・POST するだけ。エンジン改変なしで編集 UI に REPL を統合できる。 履歴 (↑↓) と継続プロンプト切替はブラウザ側で実装する。

ブラウザをアプリモードで開く

Chromium 系の --app=<URL> でタブ/アドレスバー無しウィンドウになる。 本体の WebServer.openBrowser(url) を使うのが基本 (Edge → Chrome を --app で試し、 不可なら既定ブラウザへフォールバック)。エンジンが「URL を開く」(TVPShellExecute= SDL は SDL_OpenURL) と「プログラムを引数付きで実行」(TVPExecuteProgram= デスクトップ Windows は Win32 ShellExecute) を分けて実装しているので、SDL 版でもアプリモードで 開ける (System.shellExecute 直呼びは SDL で --app 引数が捨てられるため不可だった)。

WebServer.openBrowser(("" + WebServer.url) + "ui/");   // url 省略で稼働中サーバ URL

WebServer.start() を先に呼んでいればサーバは同期的に稼働 (WebServer.active=true) なので、 開くのを Timer で待つ必要はない (起動直後にそのまま openBrowser してよい)。 旧エンジン (openBrowser 未実装) 向けにフォールバックを書くなら typeof WebServer.openBrowser != "undefined" で分岐し、無い場合のみ従来の System.shellExecute("msedge.exe", "--app=" + url) 列を使う。

json.dll

/app/ の JSON I/O には json.dll (Scripts.toJSONString(v,indent) / evalJSON(str) / evalJSONStorage(path,utf8) / saveJSON(path,v,utf8,indent)) が要る。 exe と同世代のものを plugin フォルダへ。ビューア/最小起動では未リンクのことがあるので、 ブリッジ側で自力 try { Plugins.link("json.dll"); } catch(e){} を試み、不可なら 静的配信 (/ui/) だけ登録して操作系を無効化するとフォールバックが綺麗。

★ハマりどころ (TJS2/krkrz 由来)

  1. TJS2 にローカル関数のレキシカルクロージャは無い: IIFE 内の関数同士の相互参照は 実行時「メンバが見つかりません」。→ ハンドラ/ヘルパはクラスのメンバにして objthis (インスタンス) 経由で解決する。WebServer.register("/x/", obj.method) で渡すと呼び出し時 objthis がそのインスタンスになる。
  2. TJS2 のブロックコメントはネスト可能: コメント文中の /ui/ + * のような並びが /* と誤認され「コメントが終わらないまま終端に達した」。→ ブリッジ/ハンドラを書く .tjs は行コメント (//) のみにすると安全。
  3. startup スクリプト中の例外は致命: REPL 保護 (TVPReplActive) は TVPCreateREPL() 後に立つため、startup から呼ぶ登録スクリプトのロード失敗は プロセスごと落ちる。登録処理は例外を出さないよう防御的に書く/切り分ける。
  4. ハンドラはメインスレッド実行だが、HTTP スレッドは複数接続を捌く。共有状態は ハンドラ内 (メインスレッド) でのみ触る前提で設計する (engine が SubmitTask で 直列化してくれる)。
  5. 静的配信は json.dll 非依存。UI ページだけ先に出したいときは serveStatic を register より先に無条件で呼ぶ。

検証

  • ハンドラ登録は -replfile チャネル (skill krkrz-repl) か /cmd から eval し、 curl http://127.0.0.1:<port>/<path> で応答確認 (ブラウザ不要で往復テストできる)。
  • SSE は curl -N http://127.0.0.1:<port>/sub/<ch> で購読しつつ別で操作を投げる。
  • 画面反映は Agent.captureScreen(path) → 画像を目視 (skill krkrz-repl)。
  • UI の JS 構文チェックは <script> 部を抜き出して node --check。

リファレンス実装 (krkr_threepp)

VRM 立ち絵/箱庭エディタの完全な実例:

  • engine: common/utils/ReplWebServer.cpp (サーバ) / ReplWebIntf.cpp (TJS クラス) / doc/REPL.md (仕様 SSOT)
  • プラグイン: main.cpp の ThreeWebApi (/api/three/ をネイティブ公開。 リンク時に POST_REGIST から WebServer.register、unlink 時に解除)。UI 向け一括 JSON ダンプ Object3D.treeJson / VRM.expressionListJson / morphListJson (UTF-8→ttstr 変換ヘルパで日本語対応。既定 std::string コンバータは ASCII 限定)
  • アプリ+UI: data/webui.tjs (WebUIBridge, /app/… ブリッジ) + data/ui/index.html (5 モード編集 + サイドバー + REPL コンソール)。設計詳細は threepp/docs/webui.md

関連スキル: krkrz-repl (REPL/Agent 駆動・-replweb 起動) / elements (ネイティブ UI) / krkrz (本体 API: Storages/System/Plugins) / tjs2 (言語仕様) / dev-toolkit:msys2 (シェル)。

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

elements

無料日本語概要

吉里吉里Z 上の Elements ベース汎用ダイアログ/画面 UI (cycfi/elements + elements_modal) の作り方リファレンス。TJS で JSON / Dictionary 定義のダイアログを作る・出す・イベントを受ける・複数画面フロー(navigator)や常駐メニューを組む・入力/フォーカス/モーダルを制御する・Agent で動作検証する、といった場面で使う。基本的なダイアログ画面の作り方(最小例・レイアウト JSON スキーマ・ウィジェット一覧)から、モーダル/非モーダル/独立ウィンドウ/常駐フロー、複数インスタンス/z-order、DrawDevice 登録タイミング等のハマりどころまで網羅。Win32 ネイティブの WIN32Dialog(win32dialog プラグイン)とは別物。TJS2 言語仕様は skill `tjs2`、本体クラス API は `krkrz`、REPL/Agent 駆動は `krkrz-repl` を参照。

wamsoft/krkrz_dev102026年10月9日 更新

krkrz

無料日本語概要

吉里吉里Z (kirikiri Z) 本体クラス API のリファレンス。TJS2 で吉里吉里Z 上のスクリプトを書く、レビューする、デバッグするときに使う。Layer / Window / Bitmap / System / Storages / Font / Plugins / Timer / Debug / AsyncTrigger / Scripts / BinaryStream / Matrix32 / Matrix44 / Rect / ImageFunction のコア API、サウンド系 (WaveSoundBuffer / SoundBuffer / VideoOverlay)、DrawDevice (BasicDrawDevice / SDLDrawDevice / OGLDrawDevice / NullDrawDevice)、OpenGL 描画系 (Canvas / Texture / ShaderProgram / Offscreen / VertexBinder / VertexBuffer)、および主要プラグイン提供クラス (HttpRequest / GdiPlus.* / WIN32Dialog / CSVParser / LineParser / Process / Pad / MenuItem / Unzip / Zip / PSD / SimpleHTTPServer) を網羅。**呼び出されたら必ず「共通パターン」と「クロスカッティング概念」を確認し、必要な詳細クラスは doc/reference/*.md を Read しに行くこと。** TJS2 言語そのものや組み込みクラス (Array / Dictionary / Math 等) は別 skill (`tjs2`) を参照。エンジン内部構造 (C++ 実装、レンダリングパイプライン詳細) は対象外。

wamsoft/krkrz_dev102026年10月9日 更新

krkrz-repl

無料日本語概要

吉里吉里Z (krkrz) の SDL3 / WINVER ビルドを REPL 経由でエージェントから駆動するためのリファレンス。krkrz を起動して TJS スクリプトを評価・検証・デバッグする、startup.tjs を介さず明示的に処理を開始する、入力イベント (キー/マウス) を注入する、画面をキャプチャして目視確認する、Elements ダイアログを観測・操作する、例外やダイアログ表示をコンソールで観測する、コアデモ全シーンを自動巡回してキャプチャで表示確認する (-demotest / -demotestcap)、といった場面で使う。**外部エージェントは console(CONIN$) に打てないので -replfile ファイルチャネルが本命**。起動フラグ (-repl / -replfile / -nostartup / -loglevel / -display / -ignoremouse)、ファイルチャネルのプロトコル、Agent API (入力注入 / captureScreen / dialogs / dialogClick)、ドットコマンド (.cap/.dlg/.click/.mem 等)、REPL 駆動時の挙動変更 (例外で即終了しない / inform と例外ダイアログがコンソールに出る) を網羅。TJS2 言語仕様そのものは skill `tjs2`、本体クラス API は skill `krkrz` を参照。

wamsoft/krkrz_dev102026年10月9日 更新

tjs2

無料日本語概要

TJS2 (吉里吉里Z 内蔵スクリプト言語) の言語仕様と組み込みクラスのリファレンス。.tjs ファイル / *.ks (KAG) 内の埋め込みスクリプト / TJS2 コード断片 を扱う、書く、レビューする、デバッグするときに使う。JavaScript / TypeScript に似ているが文法と意味論が違うので、JS の感覚で書くと壊れる場合が多い。組み込みクラス (Array / Dictionary / Date / Math / RegExp / Exception) の API もここに集約。**呼び出されたら必ず「JS との主な違い」セクションを最初に確認し、その上で必要な詳細リファレンスを Read で取りに行くこと。** 吉里吉里Z 本体のクラス API (Window / Layer / System / Storages / Bitmap 等) や engine 内部はこのスキルの対象外。

wamsoft/krkrz_dev102026年10月9日 更新

wamsoft のスキルをすべて見る

このスキルの問題を報告する