# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview ARZ Web Helper is a modular web-based control panel for Arizona RP (SA-MP/MoonLoader game mod). It has two layers: - **Rust core** (`rust_core/`) — an Axum HTTP/WebSocket server compiled as a shared library (`libarz_core.so`). Exposes a C FFI (`rgl_*` functions) for the Lua side. Handles HTTP routing, WebSocket event broadcasting, SQLite persistence, and static file serving. - **Lua framework** (`http_framework/rgl_framework.lua`) — the main MoonLoader script (~1058 lines). Loads the Rust `.so` via LuaJIT FFI, sets up the framework API, loads modules, and runs the game-thread event loop. ## Build Commands ### Rust core (cross-compile for Android aarch64) ```bash cd rust_core cargo build --release --target aarch64-linux-android ``` The output `.so` is deployed to the game device at `lib/libarz_core.so` relative to the MoonLoader working directory. ### Rust tests ```bash cd rust_core cargo test ``` Tests exist in `rust_core/src/db.rs` (7 tests covering batch DB operations). ### Lua modules No build step. Lua files are loaded at runtime by MoonLoader from the `modules/` directory on the device. ## Architecture ### Rust ↔ Lua Bridge The Rust server and Lua game thread communicate through a poll-based bridge (`rust_core/src/bridge.rs`): - **Lua → Rust**: `rgl_push_event()` sends game events (SAMP network events) to connected WebSocket clients via broadcast channel (capacity 256). - **Rust → Lua**: HTTP API requests become Lua code strings queued via `bridge::request_lua_exec()`. Lua's `main()` loop calls `rgl_poll()` each frame, executes the code, and returns results via `rgl_respond()`. API timeout is 2 seconds. - All strings crossing the FFI boundary may be Win-1251 encoded (game uses CP1251). The Rust side converts to UTF-8 via `encoding_rs`. ### Module System Modules live in `http_framework/modules//init.lua` (bundled with framework) or `more_modules//init.lua` (separate, gitignored). Both are loaded identically at runtime. Each module exports: - `M.init(fw)` — required. Receives the framework API table (namespaced per module). - `M.render(ui, state)` — optional. Provides a UI via the abstract widget builder (works both as web JSON and in-game imgui). - `M.unload()` — optional cleanup. The framework wraps each module's DB calls with a `.` key prefix for isolation. Modules register API handlers via `fw.on_api(module, action, handler)` and game event interceptors via `fw.on_event(event_name, handler, priority)`. See `docs/MODULE_GUIDE.md` for the full module authoring reference. ### Dual UI Rendering Modules write UI using an abstract `ui` API (`ui.text()`, `ui.button()`, `ui.tab_bar()`, etc.). Two implementations exist: - `create_ui_builder()` (rgl_framework.lua:129) — records widgets as JSON for the web frontend (`/m//` served via `ui_page.html`). - `create_ui_imgui()` (rgl_framework.lua:267) — wraps `mimgui` (ImGui) for in-game overlay rendering. Modules with `render()` but no `static/` directory get auto-registered as `__render__` modules, which are served the generic `ui_page.html` template. ### HTTP Routes (server.rs) - `GET /` — dashboard listing all registered modules - `GET /admin` — admin panel (module management, DB browser, logs) - `GET /m//` — module UI (static files or auto-rendered via ui_page.html) - `POST /api//` — module API endpoint (routed to Lua via bridge) - `GET /api/modules` — JSON list of loaded modules - `GET /api/commands` — JSON list of registered commands - `GET /ws` — WebSocket upgrade for real-time game events ### Framework API Quick Reference Core `fw.*` methods available to modules: | Method | Purpose | |--------|---------| | `fw.log(level, tag, ...)` | Log message (also broadcasts to WS as `__log`) | | `fw.on_api(mod, action, fn)` | Register HTTP API handler | | `fw.on_event(name, fn, priority)` | Hook SA-MP event (return false to block) | | `fw.command(name, fn)` | Register `/name` chat command | | `fw.db_get(key)` / `fw.db_set(key, val)` | Single key DB ops (auto-prefixed) | | `fw.db_get_many(keys)` / `fw.db_set_many(kv)` | Batch DB ops | | `fw.db_get_prefix(prefix)` | Get all keys with prefix | | `fw.timer_once(ms, fn)` | One-shot timer, returns cancel function | | `fw.chat(text)` | Send chat message/command | | `fw.add_chat_message(text, color)` | Local-only chat message | | `fw.respond_dialog(id, btn, list, input)` | Respond to server dialog | | `fw.unlock_dialog()` | Send /mm + auto-close to unblock dialog | | `fw.json_encode(t)` / `fw.json_decode(s)` | JSON serialization (cjson) | | `fw.to_utf8(s)` / `fw.to_win1251(s)` | Encoding conversion | | `fw.register_module(name, dir)` | Register static file serving | | `fw.push_event(name, json)` | Broadcast custom event to WS | ### UI Widget Types Available in `render(ui, state)`: | Widget | Signature | Returns | |--------|-----------|---------| | `text` | `ui.text(str)` | — | | `text_colored` | `ui.text_colored(r,g,b,a, str)` | — | | `separator` | `ui.separator()` | — | | `spacing` | `ui.spacing()` | — | | `sameline` | `ui.sameline()` | — | | `button` | `ui.button(label)` | `true` if clicked | | `input` | `ui.input(label, value)` | new string value | | `input_int` | `ui.input_int(label, value)` | new int value | | `checkbox` | `ui.checkbox(label, bool)` | new bool | | `slider_int` | `ui.slider_int(label, val, min, max)` | new int | | `slider_float` | `ui.slider_float(label, val, min, max)` | new float | | `combo` | `ui.combo(label, idx, items)` | new index (0-based) | | `progress` | `ui.progress(fraction, label)` | — | | `collapsing` | `ui.collapsing(label)` | true (call `collapsing_end()`) | | `tab_bar` | `ui.tab_bar(id)` | true | | `tab_item` | `ui.tab_item(label)` | true (call `tab_end()`) | ### Key Conventions - Game chat commands: short names without prefix for modules (`/btc`, `/btcc`), `rgl_` prefix for core (`/rgl_admin`). - Dialog IDs are hardcoded SA-MP server dialog identifiers (e.g., 7238, 25244). Handlers return `false` to hide dialog from player. - Event interceptors sorted by priority (lower = earlier). Return `false` to block event propagation. - DB operations support async batching via `rgl_db_submit()`/`rgl_db_poll()` for bulk reads/writes. - Win-1251 Cyrillic matching in dialog text uses hex escapes (e.g. `\xc2\xea\xeb\xfe\xf7` for "Включ"). ### Key Files | File | Lines | Purpose | |------|-------|---------| | `http_framework/rgl_framework.lua` | ~1058 | Main framework: init, module loading, UI builders, event hooks, admin panel | | `rust_core/src/lib.rs` | ~169 | FFI exports (all `rgl_*` functions) | | `rust_core/src/server.rs` | ~306 | Axum HTTP/WS server, routing, static files | | `rust_core/src/bridge.rs` | ~149 | Lua↔Rust request/response bridge + event broadcast | | `rust_core/src/db.rs` | ~397 | SQLite key-value store with async batch API | | `rust_core/src/events.rs` | ~77 | Win-1251↔UTF-8 conversion, SA-MP color parsing | | `rust_core/src/logging.rs` | ~45 | File + WS broadcast logging | | `rust_core/static/ui_page.html` | ~293 | Generic auto-UI renderer for modules with render() | | `rust_core/static/index.html` | ~55 | Dashboard page | ### Known Issues - `.gitignore` is minimal — missing `target/`, `*.so`, `*.log`, `rgl_data.db` - Broadcast channel (capacity 256) silently drops events when full - Fallback JSON encoder only handles flat `{"key":"value"}` — cjson must be present ### Additional Documentation - `docs/MODULE_GUIDE.md` — complete module authoring reference with examples - `docs/LIB_REFERENCE.md` — analysis of available standard libraries (../lib) - `docs/TASKS.md` — prioritized improvement backlog