From 14f36edb93a3fbbb888fd32f266c6e43f46ed764 Mon Sep 17 00:00:00 2001 From: Jamie Wells Date: Wed, 14 Oct 2026 01:10:38 +0000 Subject: [PATCH 1/2] Document shared scripting APIs in every gameplay language --- content/user/compatibility.md | 22 +- content/user/scripting.md | 6 +- content/user/troubleshooting.md | 2 +- docs/Scripting/API/audio.md | 22 +- docs/Scripting/API/camera-actions.md | 2 +- docs/Scripting/API/clock.md | 2 +- docs/Scripting/README.md | 2 + docs/Scripting/apiLanguages.md | 225 ++++++++++ .../gameplayActions/language-bindings.md | 9 +- docs/Scripting/sceneObjects.md | 13 +- lib/api-docs.ts | 41 +- lib/api-example-data.json | 400 +++++++++++++----- lib/api-reference-markdown.ts | 44 +- lib/api-reference.ts | 18 +- lib/docs-catalog.ts | 3 +- lib/engine-api-baseline.json | 133 +++++- scripts/check-engine-drift.mjs | 2 +- tests/api-reference.test.mjs | 17 +- tests/docs-integrity.test.mjs | 2 +- 19 files changed, 768 insertions(+), 197 deletions(-) create mode 100644 docs/Scripting/apiLanguages.md diff --git a/content/user/compatibility.md b/content/user/compatibility.md index 8902fc1..0ff77f8 100644 --- a/content/user/compatibility.md +++ b/content/user/compatibility.md @@ -41,21 +41,21 @@ This reference was checked against engine commit `017db99b7025eeda96acf219676641 | --- | --- | --- | --- | | Lua 5.4 (.lua) | Bundled embedded Lua | Implemented; immediate core host writes, new instance ID returned | Recording, hot mic and reusable loading implemented | | Luau (.luau) | Engine-provided external host | Implemented; SDK command/query boundary | Recording, hot mic and reusable loading implemented | -| JavaScript (.js) | Bundled QuickJS | Implemented; deferred core writes, synchronous queries | Capture and reusable loading unsupported | -| Python (.py) | Python toolchain | Implemented via SDK | Capture and reusable loading unsupported | -| C (.c) | C17 compiler | Implemented via SDK; explicit callback/storage conventions | Capture and reusable loading unsupported | -| C++ (.cpp) | C++ toolchain | Implemented via typed SDK | Capture and reusable loading unsupported | -| C# (.cs) | .NET SDK | Implemented via SDK | Capture and reusable loading unsupported | -| Java (.java) | java and javac | Implemented via SDK | Capture and reusable loading unsupported | -| PHP (.php in ui/) | PHP CLI | Implemented via SDK | Capture and reusable loading unsupported | -| HTML/inline JS (.html in ui/) | QuickJS extraction, no browser | Implemented JavaScript behavior surface; CSS-only is a no-op | Capture and reusable loading unsupported | +| JavaScript (.js) | Bundled QuickJS | Implemented; deferred core writes, synchronous queries | Recording, hot mic and reusable loading implemented | +| Python (.py) | Python toolchain | Implemented via SDK | Recording, hot mic and reusable loading implemented | +| C (.c) | C17 compiler | Implemented via SDK; explicit callback/storage conventions | Recording, hot mic and reusable loading implemented | +| C++ (.cpp) | C++ toolchain | Implemented via typed SDK | Recording, hot mic and reusable loading implemented | +| C# (.cs) | .NET SDK | Implemented via SDK | Recording, hot mic and reusable loading implemented | +| Java (.java) | java and javac | Implemented via SDK | Recording, hot mic and reusable loading implemented | +| PHP (.php in ui/) | PHP CLI | Implemented via SDK | Recording, hot mic and reusable loading implemented | +| HTML/inline JS (.html in ui/) | QuickJS extraction, no browser | Implemented JavaScript behavior surface; CSS-only is a no-op | Recording, hot mic and reusable loading implemented | ### Method-specific differences -- `rustic.find_entity` and object-path `GetAttribute` convenience calls are embedded Lua-specific in the published reference. Prefer each language’s scene lookup guide; general scene lookup support does not imply identical helper names. -- `Clock.state` is exposed in embedded Lua/JavaScript, Luau and Python; other SDKs provide setter controls. C#/PHP additionally expose a scale getter. See [Clock](/docs/api/clock). +- Every gameplay binding exposes live rustic.find_entity and object attribute reads. Use rustic.get_object_attribute(source, name), or the path proxy getter. Native spellings differ. +- Clock.state is available in every gameplay language; native return types follow their SDK. - Audio device enumeration/selection exists in all ten script types but native devices require Windows. Non-Windows enumeration returns empty lists; device selection raises an error. Microphone capture requires Windows permissions and uses the system default input, independently of selected input-device metadata. -- Lua/Luau game-output recording can run headlessly; microphone recording and hot mic require Windows. Recording captures audio, not video. Read and unload capture handles before owner cleanup; see [Audio recording](/docs/api/audio/start-recording). +- Game-output recording in every gameplay language can run headlessly; microphone recording and hot mic require Windows. Recording captures audio, not video. Read and unload capture handles before owner cleanup; see [Audio recording](/docs/api/audio/start-recording). - Collision callback names are not evidence of event delivery: no collision/touch callbacks are emitted by the primitive simulator. ## Platform compatibility diff --git a/content/user/scripting.md b/content/user/scripting.md index 03c902e..860f12d 100644 --- a/content/user/scripting.md +++ b/content/user/scripting.md @@ -33,9 +33,9 @@ Lua and JavaScript also accept compatible snake-case spellings; canonical names | Runtime / operation | Execution behavior | | --- | --- | | Embedded Lua 5.4 core writes | Host methods update the Play world immediately, including translation, attributes and properties. `instance.add/clone` return the new stable ID immediately. | -| Embedded JavaScript / Web core writes | Translation, property/attribute writes, enabled state, camera selection, logging and instance creation are collected and applied in order after a successful callback. Reads use the callback snapshot; creation returns no new entity ID. | +| Embedded JavaScript / Web core writes | Translation, property/attribute writes, enabled state, camera selection, logging and instance creation are collected and applied in order after a successful callback. Owner getters use the callback snapshot; a synchronous query first flushes earlier writes. Creation returns no new entity ID. | | External SDKs: Python, C, C++, C#, Luau, Java, PHP | Core commands normally commit with the callback response. A synchronous query flushes earlier commands in order before evaluating the query. Simple snapshot getters still use lifecycle state, so flushing does not refresh that snapshot. | -| Synchronous gameplay queries | Evaluate when called. Examples include physics queries/impulses, environment updates and audio controls. Embedded JavaScript queries do not automatically flush earlier queued core writes. | +| Synchronous gameplay queries | Evaluate when called. Examples include physics queries/impulses, environment updates and audio controls. Embedded JavaScript and external SDK queries flush earlier queued core writes before evaluation. Owner snapshot getters do not refresh their snapshot. | | Scheduled actions / events | Actions are registered and advanced by the shared scene clock. Events/completion callbacks are delivered through the scheduler, not by a direct call into another script’s VM. | These distinctions describe the current adapters, not a transaction guarantee. In particular a Lua write already applied before a later error is not automatically rolled back. Keep order-sensitive operations explicit and avoid assuming a later getter sees a deferred write. API 1.1 is the gameplay API capability version; it is unrelated to Docs v0.1. @@ -58,3 +58,5 @@ Inspect Console diagnostics and click source locations to open the external edit Lua/QuickJS are isolated embedded runtimes. External languages use persistent child sessions and engine-supplied SDKs; do not print protocol data or invent a network service. There are source/response bounds and callback timeouts. Per-instance runtimes/processes, synchronous queries and frequent logs have costs; this documentation makes no benchmark claims. Use engine-scheduled actions for time-based work and avoid per-frame log flooding. See [Runtime architecture](/docs/open-source/engine/scripting-architecture) and [Sandbox and Play safety](/docs/guides/gameplay-programming/sandbox-and-play-safety). Optional AI/MCP workflows live in [Advanced agent integration](/docs/guides/ai-agents); they are not prerequisites for making a game. + +See [APIs in every language](/docs/guides/api-languages) for the shared API families, loaded audio, capture handles and live object reads. diff --git a/content/user/troubleshooting.md b/content/user/troubleshooting.md index 45f3d0f..3e43a19 100644 --- a/content/user/troubleshooting.md +++ b/content/user/troubleshooting.md @@ -28,7 +28,7 @@ Read-only projects may have another active editor holding the lock or a future u ## Audio and platform differences -Native playback/devices and microphone input require Windows in this implementation. Game-output recording works headlessly in Lua/Luau; hot mic does not. Denied microphone access, absent devices, stale device IDs and unsupported formats can raise script errors. The selected input device does not redirect Lua/Luau capture from the system default. See [Audio](/docs/api/audio) and [Compatibility](/docs/reference/compatibility). +Native playback/devices and microphone input require Windows in this implementation. Game-output recording works headlessly in every gameplay language; hot mic does not. Denied microphone access, absent devices, stale device IDs and unsupported formats can raise script errors. The selected input device does not redirect capture from the system default. See [Audio](/docs/api/audio) and [Compatibility](/docs/reference/compatibility). ## Reporting a problem diff --git a/docs/Scripting/API/audio.md b/docs/Scripting/API/audio.md index 63c703e..e12df24 100644 --- a/docs/Scripting/API/audio.md +++ b/docs/Scripting/API/audio.md @@ -20,7 +20,7 @@ The signatures and complete example below use JavaScript. Attach a JavaScript Ob | OGG Opus | `.ogg`, `.oga`, `.opus` | Mono or stereo Opus (mapping family 0) | Put the file inside your project's `assets/` folder. Extensions are case -insensitive. The same formats work with loaded Lua/Luau audio handles and +insensitive. The same formats work with loaded audio handles in every gameplay language and `Audio.play` / `Audio.playAt` in every supported script language. For example, `rustic.game.assets.music["theme.mp3"]:load()` or `Audio.play("assets/music/theme.flac")` uses the usual audio controls. @@ -38,7 +38,7 @@ bytes and 200 million decoded samples per clip. Opus decodes at 48 kHz and respects header gain, encoder delay, and end padding; other formats preserve the decoded sample rate. Lossy decoder peaks are clamped to the mixer's [-1, 1] range. -## Load and control audio objects (Lua and Luau) +## Load and control audio objects Create `assets/sounds/` in your project and place `crash.wav` there (WAV, MP3, AAC, FLAC, and OGG are supported). Create a Lua or Luau Object Component script, attach it to an enabled scene object, and press **Play**. Use `Start` to load audio before playing: @@ -87,7 +87,10 @@ with no active playback are harmless. error. Missing, unsupported, or invalid files fail during `load()`. Check the Console, attachment, file name and extension. Paths must stay inside `assets/`. -The object methods and `Audio.load` are available in Lua 5.4 and Luau. Existing +Audio.load and loaded playback/control/unload are available in every gameplay language. +Asset path proxies retain their Lua/Luau syntax and are also available in JavaScript; +other languages use Audio.load(source). See [APIs in every language](/docs/guides/api-languages) +for native signatures and examples. Existing `Audio.play(source, options?)` and `Audio.playAt` in all languages still decode and play immediately; their voice controls continue to work. @@ -105,7 +108,7 @@ list and report an unavailable-backend error when selecting a device. - `Audio.set_output_device(id)` switches this scene's mixed audio to that output. - `Audio.set_input_device(id)` validates and retains the scene's selected input device. This call does not start capture or route microphone sound to playback. - Lua/Luau recording and hot mic capture currently use the system default input + Recording and hot mic capture currently use the system default input independently of this selection; see the recording section below. - Pass `null` (Lua/Luau `nil`, Python `None`, C `NULL`) to either setter to select the current system default. Java requires an explicit `null` argument; other @@ -156,7 +159,7 @@ plugging/unplugging hardware. Empty lists are valid on hosts without devices. - `Audio.get_output_devices() / get_input_devices()` - `Audio.set_output_device(id = null) / set_input_device(id = null)` -- `Audio.load(source)` (Lua/Luau; returns a reusable audio handle) +- `Audio.load(source)` (every gameplay language; returns a reusable audio handle) - `Audio.play(source, options?)` - `Audio.playAt(source, position, options?)` - `voice.pause() / resume() / stop()` @@ -200,11 +203,14 @@ globalThis.behavior = { - Check script attachment, enabled state and the Console when an operation fails. Asynchronous errors also appear in operation state. Callback exceptions disable the owner and clean up its actions. - Owner destruction, disable, successful reload or Stop cleans up owned actions and subscriptions. Failed reload retains the previous behavior's operations. See [Operation handles](/docs/api/operation-handles) for controls and lifetime details. -## Record audio and read a hot microphone (Lua and Luau) +## Record audio and read a hot microphone Create a Lua or Luau Object Component script in `scripts/`, attach it to an **enabled** scene object, and press **Play**. Start capture inside `Start` or -another callback. Capture methods use camel case and colon syntax: +another callback. The example below uses Lua/Luau colon syntax. All gameplay languages expose +Game.startRecording, stopRecording, startHotMic, stopHotMic and capture +handle read/stop/unload; see [APIs in every language](/docs/guides/api-languages) +for copyable native bindings: ```lua local rec @@ -299,5 +305,5 @@ return { and OS microphone access. Default F32, I16 and U16 device formats are supported. Missing devices, permission failures, unsupported formats, startup timeouts and device disconnection raise errors. Game mixer recording works headlessly too. -- These convenience bindings are available in Lua 5.4 and Luau. Hot mic supplies +- These services are available in every gameplay language. Hot mic supplies local PCM; automatic network transport and speaker monitoring are not provided. diff --git a/docs/Scripting/API/camera-actions.md b/docs/Scripting/API/camera-actions.md index 3d2b1b6..6ab7e3a 100644 --- a/docs/Scripting/API/camera-actions.md +++ b/docs/Scripting/API/camera-actions.md @@ -44,7 +44,7 @@ globalThis.behavior = { - Create/select an active perspective camera before Play. It reaches the given position, identity rotation and 45-degree FOV in two seconds. - Lua/Luau/JavaScript additionally allow moveTo(position, duration, easing) and zoom(fov, duration, easing) for the current camera. Explicit targets work across all bindings. -- C has no Camera.current helper: resolve an explicit camera ID. Other native bindings expose current. +- Camera.current is available in every gameplay language, including C; it returns the active camera ID. - LookAt samples when starting; follow re-reads its target only during the finite duration. Orbit uses local XZ. Shake restores its initial position. - Check script attachment, enabled state and the Console when an operation fails. Asynchronous errors also appear in operation state. Callback exceptions disable the owner and clean up its actions. - Owner destruction, disable, successful reload or Stop cleans up owned actions and subscriptions. Failed reload retains the previous behavior's operations. See [Operation handles](/docs/api/operation-handles) for controls and lifetime details. diff --git a/docs/Scripting/API/clock.md b/docs/Scripting/API/clock.md index 46bc113..8c1d414 100644 --- a/docs/Scripting/API/clock.md +++ b/docs/Scripting/API/clock.md @@ -17,7 +17,7 @@ The Calls section uses JavaScript signatures. The function examples below includ ## Parameters -Scale is finite non-negative; 1 is normal rate and 0.5 is half speed. JavaScript state returns the current scale and paused flag. Native getter/state availability varies; C/C++/Java timeScale requires a value. +Scale is finite non-negative; 1 is normal rate and 0.5 is half speed. Clock.state() returns the current scale and paused flag in every gameplay language. C returns RusticClockState; C++ returns RusticValue; C# returns JsonElement; Java returns a map. C sets time scale with Clock.timeScale(value) and reads it with Clock.state().scale. ## Return and timing diff --git a/docs/Scripting/README.md b/docs/Scripting/README.md index 3b0276a..cb8cbf6 100644 --- a/docs/Scripting/README.md +++ b/docs/Scripting/README.md @@ -69,3 +69,5 @@ object using `rustic.game..` in native language syntax. See [Entity foundations](API/entity-foundations.md) for live tags, groups, hierarchy, lifetime, component and world queries in the shared scripting runtime. + +See [APIs in every language](/docs/guides/api-languages) for the shared API families, reusable audio, capture, and live object reads. diff --git a/docs/Scripting/apiLanguages.md b/docs/Scripting/apiLanguages.md new file mode 100644 index 0000000..64eb924 --- /dev/null +++ b/docs/Scripting/apiLanguages.md @@ -0,0 +1,225 @@ +# APIs in every supported language + +Lua 5.4, Luau, JavaScript, Python, C, C++, C#, Java, PHP, and HTML inline JavaScript +use the same engine services. CSS supplies styling and executes no API calls. + +## Create and attach a script + +1. Open a project and create an **Object Component Script** from + **Programming > New Script**, choosing your language. +2. Select an enabled scene object and use **+ Add Component** to attach the script. +3. External languages need their toolchain on PATH: Python 3, a C17 or C++20 + compiler, .NET SDK 10+, Java with javac, or PHP CLI. Lua, JavaScript and the + isolated Luau host ship with Rustic. +4. Generate the programming workspace from the editor before using an external + editor. Include/import the engine-owned SDK shown in your + [language guide](/docs/guides/scripting). PHP and HTML assets belong under ui/. +5. Put tone.wav in assets/, then run the examples below from **Start**. + Press **Play**. Playback is audible on Windows; other platforms currently mix + without a native output device. + +## Shared API families + +| Service | Available operations | +| --- | --- | +| Lifecycle and owner state | Construction, startup, enable/disable, fixed/frame updates, destroy/stop, translation, properties, built-in attributes, input, logging | +| Scene and object services | Find/list, live entity lookup, read/edit other objects, instance add/clone, current camera selection, environment/sky settings | +| Entity foundations | Tags, groups, hierarchy, destruction/lifetime, components, radius/tag queries | +| Gameplay actions | Tween/value, easing, interpolation, movement, paths, timers, sequence/timeline, effects, camera actions | +| Animation | Load/register clips, play/blend, speed/loop/pause/stop, markers, keyframes, IK and procedural actions | +| Events and time | Signals, connections, operation handles, scene clock read/pause/resume/time scale | +| Audio | Play/spatial play, reusable loaded clips, voice controls, volume/pitch/fades, device selection | +| Capture | Record game mix or microphone, hot mic, stop/read/unload script-owned PCM handles | + +All these families are available in every gameplay language. Names and signatures +follow each language's SDK; native bindings use typed options and handles. +HTML shares the JavaScript sandbox. This does not add browser APIs or DOM access. + +## Load audio and record the mix + +These are callback-body examples, not standalone programs. Use the callback +registration from your language guide. They play the same clip, pause/resume it, +stop and replay it, then release it. Recording starts before playback and stops +afterward; a read returns the PCM mixed between callbacks, so stopping immediately +within Start can produce an empty packet. Retain the handles across callbacks and +stop/read in Update or a Timer callback when recording a longer session. +The immediate stop/unload calls below run before a mixed frame, so these short +snippets produce no audible output. Omit those controls until a later callback to +hear playback and collect a nonempty recording. + +### Lua 5.4 / Luau + +```lua +local rec = Game.startRecording() +local clip = Audio.load('assets/tone.wav') +clip:play({loop=true}) +clip:pause(); clip:resume(); clip:stop(); clip:play() +clip:unload() +Game.stopRecording(rec) +local packet = rec:read() +rec:unload() +``` + +### JavaScript / HTML inline JavaScript + +```javascript +const rec = Game.startRecording(); +const clip = Audio.load('assets/tone.wav'); +clip.play({loop:true}); +clip.pause(); clip.resume(); clip.stop(); clip.play(); +clip.unload(); +Game.stopRecording(rec); +const packet = rec.read(); +rec.unload(); +``` + +### Python + +```python +# from rustic import Audio, Game +rec = Game.startRecording() +clip = Audio.load('assets/tone.wav') +clip.play({'loop':True}) +clip.pause(); clip.resume(); clip.stop(); clip.play() +clip.unload() +Game.stopRecording(rec) +packet = rec.read() +rec.unload() +``` + +### C + +```c +/* #include "rustic.h" */ +RusticCaptureHandle rec = Game.startRecording(NULL, NULL); +RusticLoadedAudio clip = Audio.load("assets/tone.wav"); +RusticAudioOptions options = {.volume=1, .pitch=1, .loop=true}; +LoadedAudio.play(clip, &options); +LoadedAudio.pause(clip); LoadedAudio.resume(clip); LoadedAudio.stop(clip); +LoadedAudio.play(clip, NULL); LoadedAudio.unload(&clip); +Game.stopRecording(&rec); +RusticCaptureData packet = Capture.read(rec); +Capture.unload(rec); +``` + +### C++ + +```cpp +// #include "rustic.hpp" +auto rec = Game.startRecording(); +auto clip = Audio::load("assets/tone.wav"); +clip.play(1,1,true); +clip.pause(); clip.resume(); clip.stop(); clip.play(); +clip.unload(); +Game.stopRecording(&rec); +auto packet = rec.read(); +rec.unload(); +``` + +### C# + +```csharp +// using static Rustic; +var rec = Game.startRecording(); +var clip = Audio.load("assets/tone.wav"); +clip.play(loop:true); +clip.pause(); clip.resume(); clip.stop(); clip.play(); +clip.unload(); +Game.stopRecording(rec); +var packet = rec.read(); +rec.unload(); +``` + +### Java + +```java +// Inside a behavior extending Rustic. +var rec = Game.startRecording(); +var clip = Audio.load("assets/tone.wav"); +clip.play(1,1,true); +clip.pause(); clip.resume(); clip.stop(); clip.play(); +clip.unload(); +Game.stopRecording(rec); +var packet = (java.util.Map)rec.read(); +rec.unload(); +``` + +### PHP + +```php +// Inside a callback after require_once 'rustic.php'. +global $Game; +$rec = $Game->startRecording(); +$clip = Audio::load('assets/tone.wav'); +$clip->play(['loop'=>true]); +$clip->pause(); $clip->resume(); $clip->stop(); $clip->play(); +$clip->unload(); +$Game->stopRecording($rec); +$packet = $rec->read(); +$rec->unload(); +``` + +Audio.load decodes once. play reuses retained PCM even if the source file is +removed afterward. Stop retains PCM; unload frees it and stops its playback. +Unload twice is harmless. Lua/Luau, JavaScript, Python, C++, C#, Java and PHP can +call clip.load() (Lua uses a colon) to load the same object again after unload. +C calls Audio.load(source) to obtain a new handle. C++ copies share loaded state. +A voice returned by loaded playback supports the same fades and controls as an +Audio.play voice. + +## Microphone capture + +Use Game.startHotMic() instead of startRecording() to obtain a microphone +handle, then use its read, stop, and unload methods. C uses +Game.startHotMic(), Capture.read(mic), Capture.stop(mic) and +Capture.unload(mic). Stop all owned hot mics with Game.stopHotMic(); C passes +NULL, C++ omits the pointer, and Java passes null or omits the argument. + +To record microphone PCM, use Game.startRecording(null, {source:'microphone'}) +in JavaScript, Game.startRecording(None, {'source':'microphone'}) in Python, +Game.startRecording(nil, {source='microphone'}) in Lua/Luau, or +$Game->startRecording(null, ['source'=>'microphone']) in PHP. C, C++, C# and Java +pass "microphone" as the second argument. The existing rustic.game entry point +also exposes the capture methods in each SDK. + +Capture packets contain samples, sampleRate, channels, active, and +droppedSamples. Samples are interleaved floating-point PCM. Reads advance the +cursor; repeated reads do not repeat samples. C uses packet.count for the number +of samples; its sample storage remains valid until the next script callback. +Recording and microphone capture retain the existing platform limits: +Windows supplies microphone input; other platforms report an unavailable-backend +error. Device selection does not currently change capture's system-default input. +See the [Audio API](/docs/api/audio) for file paths, limits and cleanup. + +## Read another object's current attributes + +Use rustic.get_object_attribute(source, name) in every language, with native +member syntax ($rustic-> in PHP). source accepts a stable entity ID or +rustic.game.Scene.Root.Child. C returns RusticValue; C++ returns RusticValue; +C#/Java return ordinary scalar or vector values. JavaScript/Python/Lua return +their ordinary values. Path proxies also expose GetAttribute and get_attribute; +C uses rustic.game.GetAttribute(source, name). + +Edits queue in JavaScript and external adapters. A live query flushes earlier +commands before reading, so an edit followed by get_object_attribute in the same +callback observes the new value. Owner snapshot getters such as get_translation +and get_attribute retain their callback snapshot in those adapters. Lua applies +writes immediately. rustic.find_entity(source) performs live lookup; scene +Find/List retain their existing snapshot semantics in external adapters. List returns\none entity ID per matched object, supports a subtree path and defaults to Game.scene. + +## Diagnose failures + +- Missing external SDK/toolchain: regenerate the workspace and check the Console's + toolchain diagnostic. Do not write your own protocol loop. +- Missing clip: check the exact assets/ path and supported encoding. Playing after + unload requires load first. A C handle must be replaced by Audio.load. +- Silent output or microphone unavailable: check the platform, Windows device + permissions and the Console. A stopped recording still permits reading. +- Missing/stale object: use a stable entity ID or verify the scene/hierarchy path; + a destroyed object cannot supply attributes. +- Capture/action lifetime: handles belong to the script and clean up on disable, + destruction, successful reload or Stop. + +Input remains limited to held WASD, arrows and Shift in embedded Play. Collision +and touch callbacks are not dispatched. These are shared runtime limits across +languages. diff --git a/docs/Scripting/gameplayActions/language-bindings.md b/docs/Scripting/gameplayActions/language-bindings.md index 04b82b7..8fa8f8c 100644 --- a/docs/Scripting/gameplayActions/language-bindings.md +++ b/docs/Scripting/gameplayActions/language-bindings.md @@ -31,8 +31,7 @@ Programming Workspace after upgrading. Python imports the modules it uses, for e PHP and HTML entries remain restricted to `ui/`; CSS alone executes no actions. -`Interpolation` aliases `Smooth` except in C# and Java, where you use `Smooth`. -`Clock.state()` is available in Lua, Luau, JavaScript, Python and HTML inline -JavaScript; C#, C++, C, Java and PHP have no paused-state getter. C# and PHP -can read the scale with `Clock.timeScale()` / `Clock::timeScale()`. C has no -`Camera.current()` getter; use `Game.scene.Find` with the authored camera path. +Interpolation aliases Smooth in every SDK. Clock.state() returns scale and paused +in every gameplay language. C uses RusticClockState and sets time scale with +Clock.timeScale(value); C++ returns RusticValue, C# returns JsonElement, and Java +returns a map. \ No newline at end of file diff --git a/docs/Scripting/sceneObjects.md b/docs/Scripting/sceneObjects.md index 6350f6c..975eb58 100644 --- a/docs/Scripting/sceneObjects.md +++ b/docs/Scripting/sceneObjects.md @@ -95,11 +95,12 @@ All gameplay languages use engine-owned APIs. The engine handles command transpo use the native calls above without serializing or printing requests/responses. CSS alone cannot call functions; use inline JavaScript in an HTML UI asset. -Embedded Lua applies edits immediately and supports -`:GetAttribute("Position")` on the same path. JavaScript and external languages -queue edits in issue order and apply them after the callback; they do not expose -path-based getters. Do not use a queued edit followed by an owner getter to read -another object's new value. +Embedded Lua applies edits immediately. Every gameplay language can read another +object using rustic.get_object_attribute(source, name) or its path proxy's +GetAttribute method (C uses rustic.game.GetAttribute(source, name)). +JavaScript and external languages queue edits in issue order. A live query flushes +those edits before reading; owner getters still read the callback snapshot. +See [APIs in every language](/docs/guides/api-languages) for native signatures. ## Supported attributes @@ -114,7 +115,7 @@ another object's new value. | `Anchored` | boolean | Prevents physics movement; unchecked primitives fall under gravity | | `Parent` | stable entity ID or null | Reparents the target; changes future path | -`EditAttribute` and `edit_attribute` are equivalent. The direct Lua and JavaScript +`EditAttribute` and `edit_attribute` are equivalent. The shared helper `rustic.edit_object_attribute(source, name, value)` also accepts the full path string. Material and Enabled are not supported attributes; `set_enabled` still affects the script owner. diff --git a/lib/api-docs.ts b/lib/api-docs.ts index 9b64538..c6df12d 100644 --- a/lib/api-docs.ts +++ b/lib/api-docs.ts @@ -85,8 +85,8 @@ const docs: Record = { attributes: { title: "Built-in attribute API", summary: "Read or edit engine-owned attributes on the behavior entity or a named scene object.", when: "Use attributes for built-in entity state. Prefer `set_translation` for Position and `set_enabled` for Enabled when those dedicated calls better express intent.", - calls: ["rustic.get_attribute(name)", "rustic.edit_attribute(name, value)", "rustic.game.Scene.Root.Child.EditAttribute(name, value)", "rustic.edit_object_attribute(source, name, value) — embedded Lua / JavaScript", "rustic.game.Scene.Root.Child:GetAttribute(name) — embedded Lua only"], - parameters: [p("name", "string", true, "Name, Position, Size, Color (RGB), CanTouch, CanCollide, Anchored, or Parent."), p("value", "attribute-specific", true, "Value matching the attribute type.")], + calls: ["rustic.get_attribute(name)", "rustic.edit_attribute(name, value)", "rustic.game.Scene.Root.Child.EditAttribute(name, value)", "rustic.edit_object_attribute(source, name, value)", "rustic.game.Scene.Root.Child:GetAttribute(name)", "rustic.get_object_attribute(source, name)"], + parameters: [p("source", "string", true, "Stable entity ID or full rustic.game.Scene.Root.Child path for object helpers."), p("name", "string", true, "Name, Position, Size, Color (RGB), CanTouch, CanCollide, Anchored, or Parent."), p("value", "attribute-specific", true, "Value matching the attribute type.")], returns: "Get returns the current value. Edit validates a typed mutation; [write timing depends on the runtime](/docs/guides/scripting#when-mutations-take-effect).", examples: { "Lua 5.4": "local name = rustic.get_attribute('Name')\nrustic.edit_attribute('Color', {1, .5, 0})", @@ -123,7 +123,7 @@ const docs: Record = { logging: { title: "Logging API", summary: "Send a bounded diagnostic message to the live game console.", when: "Log lifecycle transitions and recoverable errors. Avoid per-frame logging outside short debugging sessions because bounded queues can drop excessive output.", - calls: ["rustic.log(level, message)", "print(...) — Lua and JavaScript info", "warn(...) — Lua and JavaScript warning"], + calls: ["rustic.log(level, message)", "print(...) — native bindings use rustic.print(message)", "warn(...) — native bindings use rustic.warn(message)"], parameters: [p("level", "string", true, "debug, info, warn, or error."), p("message", "string", true, "Human-readable diagnostic text.")], returns: "No value. The message is queued for the bounded console sink.", examples: simpleMutationExamples("rustic.log('info', 'player spawned')", "rustic.log(\"info\", \"player spawned\");"), @@ -157,9 +157,9 @@ const docs: Record = { notes: ["Unknown settings and invalid values raise errors without mutating the environment. Exposure is limited to [-20,20]; colors/intensities/distances must be finite and non-negative; sun direction must be finite and nonzero.", "setSkyTexture changes only the image; set enabled=true separately. The renderer checks the image on the next frame. The panorama does not generate image-based lighting or reflections.", "Changes affect the running scene and do not save to the editor or appear in Apply Runtime Changes. Restart Play to restore authored settings.", protocolNote], }, scene: { - title: "Scene lookup API", summary: "Resolve stable scene paths to entity IDs and list paths in the play snapshot.", + title: "Scene lookup API", summary: "Resolve stable scene paths to entity IDs and list entity IDs in the play snapshot.", when: "Resolve references during `Start` and cache the stable ID when possible. List paths for discovery and tooling, not every frame.", - calls: ["Game.scene.Find(path)", "Game.scene.List()", "rustic.find_entity(name_or_id) — embedded Lua only"], parameters: [p("path", "string", true, "Stable scene path, name, or entity ID accepted by the adapter.")], + calls: ["Game.scene.Find(path)", "Game.scene.List()", "rustic.find_entity(name_or_id)"], parameters: [p("path", "string", true, "Stable scene path, name, or entity ID accepted by the adapter.")], returns: "Find returns an entity ID or no value. List returns entity IDs.", examples: { "Lua 5.4": "local id=Game.scene.Find('Room.Player')\nlocal found,err=rustic.find_entity('Room.Player')\nlocal paths=Game.scene.List()", JavaScript: "const id=Game.scene.Find('Room.Player');\nconst paths=Game.scene.List();", Python: "entity_id=Game.scene.Find('Room.Player')\npaths=Game.scene.List()", "C++": "auto id=Game.scene.Find(\"Room.Player\");\nauto paths=Game.scene.List();", "C#": "string? id=Game.scene.Find(\"Room.Player\");\nvar paths=Game.scene.List();", Luau: "local id = Game.scene.Find('Room.Player')\nlocal ids = Game.scene.List()", C: "const char *id = Game.scene.Find(\"Room.Player\");\nRusticList ids = Game.scene.List(\"Game.scene\");", Java: "String id = Game.scene.Find(\"Room.Player\");\nvar ids = Game.scene.List();", PHP: "$id = $Game->scene->Find(\"Room.Player\");\n$ids = $Game->scene->List();", "HTML / inline JS": "const id=Game.scene.Find('Room.Player');\nconst paths=Game.scene.List();", @@ -265,3 +265,34 @@ function callbacks() { const examples: Record = {"Lua 5.4": lua, JavaScript: js, Python: python, "C++": cpp, "C#": csharp, Luau: lua, C: c, Java: java, PHP: php, "HTML / inline JS": js}; return `# Lifecycle callbacks\n\nCallbacks are entry points invoked by Rustic. Define only those a behavior needs.\n\n## When each callback runs\n\n| Callback | When to use it | Variables |\n| --- | --- | --- |\n| OnCreate | One-time construction before startup. | None |\n| Start | Resolve references and initialize gameplay state. | None |\n| OnEnable | Resume when the owner becomes enabled. | None |\n| FixedUpdate | Physics and simulation. | dt: fixed-step seconds |\n| Update | Input and presentation. | dt: frame seconds |\n| OnDisable | Pause when the owner becomes disabled. | None |\n| OnDestroy | Release instance resources. | None |\n| OnStop | Final play-session cleanup. | None |\n\n## Examples in every language\n\nThese registration examples include every callback. Replace each body with the logic you need. Follow your language guide to generate the workspace, install its toolchain, attach the script and press Play. External dispatchers receive dt; it is meaningful for update and fixed_update. Physics runs after FixedUpdate, but collision and touch callbacks are not dispatched. HTML examples go inside an inline script in a ui/ HTML asset.\n\n${languages.map((language) => `### ${language}\n\n\`\`\`${codeLanguage(language)}\n${examples[language]}\n\`\`\``).join("\n\n")}`; } + +for (const language of ["Python", "C", "C++", "C#", "Java"]) docs.logging.examples[language] += + language === "Python" ? '\nrustic.print("spawned")\nrustic.warn("low health")' : '\nrustic.print("spawned");\nrustic.warn("low health");'; +docs.logging.examples.PHP += '\n$rustic->print("spawned");\n$rustic->warn("low health");'; +docs.logging.examples.Luau += '\nprint("spawned")\nwarn("low health")'; + +const liveReads: Record = { + "Lua 5.4": "local position = rustic.get_object_attribute(rustic.entity_id(), 'Position')", + Luau: "local position = rustic.get_object_attribute(rustic.entity_id(), 'Position')", + JavaScript: "const position = rustic.get_object_attribute(rustic.entity_id(), 'Position');", + "HTML / inline JS": "const position = rustic.get_object_attribute(rustic.entity_id(), 'Position');", + Python: "position = rustic.get_object_attribute(rustic.entity_id(), 'Position')", + C: 'RusticValue position = rustic.get_object_attribute(rustic.entity_id(), "Position");', + "C++": 'auto position = rustic.get_object_attribute(rustic.entity_id(), "Position");', + "C#": 'var position = rustic.get_object_attribute(rustic.entity_id(), "Position");', + Java: 'var position = rustic.get_object_attribute(rustic.entity_id(), "Position");', + PHP: '$position = $rustic->get_object_attribute($rustic->entity_id(), "Position");', +}; +for (const language of languages) { + docs.attributes.examples[language] += "\n" + liveReads[language]; + const edit = docs.attributes.examples[language].split("\n").find((line) => line.includes("edit_attribute("))!; + docs.attributes.examples[language] += "\n" + edit.replace("edit_attribute(", "edit_object_attribute(" + (language === "PHP" ? "$rustic->entity_id(), " : "rustic.entity_id(), ")); + const lookup = language === "PHP" ? '$found = $rustic->find_entity($rustic->entity_id());' + : language === "Python" ? "found = rustic.find_entity(rustic.entity_id())" + : language === "Lua 5.4" || language === "Luau" ? "local foundById = rustic.find_entity(rustic.entity_id())" + : language === "C" ? "const char *found = rustic.find_entity(rustic.entity_id());" + : language === "C++" ? "auto found = rustic.find_entity(rustic.entity_id());" + : language === "JavaScript" || language === "HTML / inline JS" ? "const found = rustic.find_entity(rustic.entity_id());" + : "var found = rustic.find_entity(rustic.entity_id());"; + docs.scene.examples[language] += "\n" + lookup; +} diff --git a/lib/api-example-data.json b/lib/api-example-data.json index eb8946a..a51625b 100644 --- a/lib/api-example-data.json +++ b/lib/api-example-data.json @@ -510,17 +510,17 @@ }, { "title": "Clock.state", - "note": "Clock.state is available in embedded Lua/JavaScript, Luau and Python. Other SDKs expose setters only (C# and PHP also allow timeScale() to read scale).", + "note": "Every gameplay binding exposes Clock.state; C returns RusticClockState, C++ returns RusticValue, C# returns JsonElement, and Java returns a map.", "examples": { "Lua 5.4": "local state = Clock.state()", "JavaScript": "const state = Clock.state();", "Python": "state = Clock.state()", - "C++": "// This SDK has no Clock.state getter; set the clock with Clock::timeScale(1).", - "C#": "var scale = Clock.timeScale();\n// C# exposes the scale getter but no paused-state snapshot.", + "C++": "auto state = Clock::state();", + "C#": "var state = Clock.state();", "Luau": "local state = Clock.state()", - "C": "// This SDK has no Clock.state getter; set the clock with Clock.timeScale(1).", - "Java": "// This SDK has no Clock.state getter; set the clock with Clock.timeScale(1).", - "PHP": "$scale = Clock::timeScale();\n// PHP exposes the scale getter but no paused-state snapshot.", + "C": "RusticClockState state = Clock.state();", + "Java": "var state = Clock.state();", + "PHP": "$state = Clock::state();", "HTML / inline JS": "const state = Clock.state();" } } @@ -1068,7 +1068,7 @@ "C++": "auto camera = Camera::current();", "C#": "var camera = Camera.current();", "Luau": "local camera = Camera.current()", - "C": "const char *camera = Game.scene.Find(\"Game.scene.Camera\");\n// C has no Camera.current getter; resolve the authored camera explicitly.", + "C": "const char *camera = Camera.current();", "Java": "var camera = Camera.current();", "PHP": "$camera = Camera::current();", "HTML / inline JS": "const camera = Camera.current();" @@ -1528,130 +1528,338 @@ }, { "title": "Audio.load", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", "examples": { - "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\nlocal voice = audio:play()", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");", - "Luau": "local audio = Audio.load(\"assets/music.ogg\")\nlocal voice = audio:play()", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");" + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");", + "Python": "audio = Audio.load(\"assets/music.ogg\")", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");", + "C#": "var audio = Audio.load(\"assets/music.ogg\");", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");", + "Java": "var audio = Audio.load(\"assets/music.ogg\");", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");" } }, { "title": "audio.load", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", "examples": { - "Lua 5.4": "local audio = rustic.game.assets[\"music.ogg\"]:load()\naudio:load() -- Already loaded: returns the same handle.", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");", - "Luau": "local audio = rustic.game.assets[\"music.ogg\"]:load()\naudio:load() -- Already loaded: returns the same handle.", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");" + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:load()", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");\naudio.load();", + "Python": "audio = Audio.load(\"assets/music.ogg\")\naudio.load()", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");\naudio.load();", + "C#": "var audio = Audio.load(\"assets/music.ogg\");\naudio.load();", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:load()", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");\nLoadedAudio.unload(&audio);\naudio = Audio.load(\"assets/music.ogg\");", + "Java": "var audio = Audio.load(\"assets/music.ogg\");\naudio.load();", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");\n$audio->load();", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");\naudio.load();" } }, { "title": "audio.play", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", "examples": { - "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\nlocal voice = audio:play({loop=true, volume=0.5})", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");", - "Luau": "local audio = Audio.load(\"assets/music.ogg\")\nlocal voice = audio:play({loop=true, volume=0.5})", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");" + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");\naudio.play();", + "Python": "audio = Audio.load(\"assets/music.ogg\")\naudio.play()", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");\naudio.play();", + "C#": "var audio = Audio.load(\"assets/music.ogg\");\naudio.play();", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");\nLoadedAudio.play(audio, NULL);", + "Java": "var audio = Audio.load(\"assets/music.ogg\");\naudio.play();", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");\n$audio->play();", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");\naudio.play();" } }, { "title": "audio.pause", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", "examples": { - "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:pause()", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.pause();", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")\nvoice.pause()", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");\nvoice.pause();", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");\nvoice.pause();", - "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:pause()", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);\nAudio.pause(voice);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);\nvoice.pause();", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");\n$voice->pause();", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.pause();" + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:pause()", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");\naudio.pause();", + "Python": "audio = Audio.load(\"assets/music.ogg\")\naudio.pause()", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");\naudio.pause();", + "C#": "var audio = Audio.load(\"assets/music.ogg\");\naudio.pause();", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:pause()", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");\nLoadedAudio.pause(audio);", + "Java": "var audio = Audio.load(\"assets/music.ogg\");\naudio.pause();", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");\n$audio->pause();", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");\naudio.pause();" } }, { "title": "audio.resume", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", "examples": { - "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:pause()\naudio:resume()", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.resume();", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")\nvoice.resume()", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");\nvoice.resume();", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");\nvoice.resume();", - "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:pause()\naudio:resume()", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);\nAudio.resume(voice);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);\nvoice.resume();", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");\n$voice->resume();", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.resume();" + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:resume()", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");\naudio.resume();", + "Python": "audio = Audio.load(\"assets/music.ogg\")\naudio.resume()", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");\naudio.resume();", + "C#": "var audio = Audio.load(\"assets/music.ogg\");\naudio.resume();", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:resume()", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");\nLoadedAudio.resume(audio);", + "Java": "var audio = Audio.load(\"assets/music.ogg\");\naudio.resume();", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");\n$audio->resume();", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");\naudio.resume();" } }, { "title": "audio.stop", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", "examples": { - "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:stop()", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")\nvoice.stop()", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");\nvoice.stop();", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();", - "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:stop()", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);\nAudio.stop(voice);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);\nvoice.stop();", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");\n$voice->stop();", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();" + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:stop()", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");\naudio.stop();", + "Python": "audio = Audio.load(\"assets/music.ogg\")\naudio.stop()", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");\naudio.stop();", + "C#": "var audio = Audio.load(\"assets/music.ogg\");\naudio.stop();", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:stop()", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");\nLoadedAudio.stop(audio);", + "Java": "var audio = Audio.load(\"assets/music.ogg\");\naudio.stop();", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");\n$audio->stop();", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");\naudio.stop();" } }, { "title": "audio.Unload", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", "examples": { - "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:Unload() -- Stops playback and frees the decoded clip.", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")\nvoice.stop()", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");\nvoice.stop();", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();", - "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:Unload() -- Stops playback and frees the decoded clip.", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);\nAudio.stop(voice);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);\nvoice.stop();", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");\n$voice->stop();", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();" + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:Unload()", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");\naudio.Unload();", + "Python": "audio = Audio.load(\"assets/music.ogg\")\naudio.Unload()", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");\naudio.Unload();", + "C#": "var audio = Audio.load(\"assets/music.ogg\");\naudio.Unload();", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:Unload()", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");\nLoadedAudio.unload(&audio);", + "Java": "var audio = Audio.load(\"assets/music.ogg\");\naudio.Unload();", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");\n$audio->Unload();", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");\naudio.Unload();" } }, { "title": "audio.unload", - "note": "Loaded audio handles and asset-proxy methods are available only in Lua 5.4 and Luau. Supply assets/music.ogg before Play. Other languages use immediate Audio.play and voice controls, shown below; stopping a voice does not create a reusable loaded handle.", - "examples": { - "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:unload() -- Stops playback and frees the decoded clip.", - "JavaScript": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();", - "Python": "# Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvoice = Audio.play(\"assets/music.ogg\")\nvoice.stop()", - "C++": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nauto voice = Audio::play(\"assets/music.ogg\");\nvoice.stop();", - "C#": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();", - "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:play()\naudio:unload() -- Stops playback and frees the decoded clip.", - "C": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nRusticAudioVoice voice = Audio.play(\"assets/music.ogg\", NULL);\nAudio.stop(voice);", - "Java": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nvar voice = Audio.play(\"assets/music.ogg\", 1, 1, false);\nvoice.stop();", - "PHP": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\n$voice = Audio::play(\"assets/music.ogg\");\n$voice->stop();", - "HTML / inline JS": "// Loaded audio handles are Lua/Luau only; this SDK uses immediate playback.\nconst voice = Audio.play(\"assets/music.ogg\");\nvoice.stop();" + "note": "Reusable loaded audio works in every gameplay language. Supply assets/music.ogg before Play. Stop retains decoded PCM; unload releases it. C uses LoadedAudio service functions and obtains a new handle from Audio.load to reload.", + "examples": { + "Lua 5.4": "local audio = Audio.load(\"assets/music.ogg\")\naudio:unload()", + "JavaScript": "const audio = Audio.load(\"assets/music.ogg\");\naudio.unload();", + "Python": "audio = Audio.load(\"assets/music.ogg\")\naudio.unload()", + "C++": "auto audio = Audio::load(\"assets/music.ogg\");\naudio.unload();", + "C#": "var audio = Audio.load(\"assets/music.ogg\");\naudio.unload();", + "Luau": "local audio = Audio.load(\"assets/music.ogg\")\naudio:unload()", + "C": "RusticLoadedAudio audio = Audio.load(\"assets/music.ogg\");\nLoadedAudio.unload(&audio);", + "Java": "var audio = Audio.load(\"assets/music.ogg\");\naudio.unload();", + "PHP": "$audio = Audio::load(\"assets/music.ogg\");\n$audio->unload();", + "HTML / inline JS": "const audio = Audio.load(\"assets/music.ogg\");\naudio.unload();" + } + }, + { + "title": "Game.startRecording", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()", + "JavaScript": "const capture = Game.startRecording();", + "Python": "capture = Game.startRecording()", + "C++": "auto capture = Game.startRecording();", + "C#": "var capture = Game.startRecording();", + "Luau": "local capture = Game.startRecording()", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);", + "Java": "var capture = Game.startRecording();", + "PHP": "global $Game;\n$capture = $Game->startRecording();", + "HTML / inline JS": "const capture = Game.startRecording();" + } + }, + { + "title": "Game.stopRecording", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()\nGame.stopRecording(capture)", + "JavaScript": "const capture = Game.startRecording();\nGame.stopRecording(capture);", + "Python": "capture = Game.startRecording()\nGame.stopRecording(capture)", + "C++": "auto capture = Game.startRecording();\nGame.stopRecording(&capture);", + "C#": "var capture = Game.startRecording();\nGame.stopRecording(capture);", + "Luau": "local capture = Game.startRecording()\nGame.stopRecording(capture)", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);\nGame.stopRecording(&capture);", + "Java": "var capture = Game.startRecording();\nGame.stopRecording(capture);", + "PHP": "global $Game;\n$capture = $Game->startRecording();\n$Game->stopRecording($capture);", + "HTML / inline JS": "const capture = Game.startRecording();\nGame.stopRecording(capture);" + } + }, + { + "title": "Game.startHotMic", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startHotMic()", + "JavaScript": "const capture = Game.startHotMic();", + "Python": "capture = Game.startHotMic()", + "C++": "auto capture = Game.startHotMic();", + "C#": "var capture = Game.startHotMic();", + "Luau": "local capture = Game.startHotMic()", + "C": "RusticCaptureHandle capture = Game.startHotMic();", + "Java": "var capture = Game.startHotMic();", + "PHP": "global $Game;\n$capture = $Game->startHotMic();", + "HTML / inline JS": "const capture = Game.startHotMic();" + } + }, + { + "title": "Game.stopHotMic", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "JavaScript": "const capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "Python": "capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "C++": "auto capture = Game.startHotMic();\nGame.stopHotMic(&capture);", + "C#": "var capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "Luau": "local capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "C": "RusticCaptureHandle capture = Game.startHotMic();\nGame.stopHotMic(&capture);", + "Java": "var capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "PHP": "global $Game;\n$capture = $Game->startHotMic();\n$Game->stopHotMic($capture);", + "HTML / inline JS": "const capture = Game.startHotMic();\nGame.stopHotMic(capture);" + } + }, + { + "title": "rustic.game:startRecording", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()", + "JavaScript": "const capture = Game.startRecording();", + "Python": "capture = Game.startRecording()", + "C++": "auto capture = Game.startRecording();", + "C#": "var capture = Game.startRecording();", + "Luau": "local capture = Game.startRecording()", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);", + "Java": "var capture = Game.startRecording();", + "PHP": "global $Game;\n$capture = $Game->startRecording();", + "HTML / inline JS": "const capture = Game.startRecording();" + } + }, + { + "title": "rustic.game:stopRecording", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()\nGame.stopRecording(capture)", + "JavaScript": "const capture = Game.startRecording();\nGame.stopRecording(capture);", + "Python": "capture = Game.startRecording()\nGame.stopRecording(capture)", + "C++": "auto capture = Game.startRecording();\nGame.stopRecording(&capture);", + "C#": "var capture = Game.startRecording();\nGame.stopRecording(capture);", + "Luau": "local capture = Game.startRecording()\nGame.stopRecording(capture)", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);\nGame.stopRecording(&capture);", + "Java": "var capture = Game.startRecording();\nGame.stopRecording(capture);", + "PHP": "global $Game;\n$capture = $Game->startRecording();\n$Game->stopRecording($capture);", + "HTML / inline JS": "const capture = Game.startRecording();\nGame.stopRecording(capture);" + } + }, + { + "title": "rustic.game:startHotMic", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startHotMic()", + "JavaScript": "const capture = Game.startHotMic();", + "Python": "capture = Game.startHotMic()", + "C++": "auto capture = Game.startHotMic();", + "C#": "var capture = Game.startHotMic();", + "Luau": "local capture = Game.startHotMic()", + "C": "RusticCaptureHandle capture = Game.startHotMic();", + "Java": "var capture = Game.startHotMic();", + "PHP": "global $Game;\n$capture = $Game->startHotMic();", + "HTML / inline JS": "const capture = Game.startHotMic();" + } + }, + { + "title": "rustic.game:stopHotMic", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "JavaScript": "const capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "Python": "capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "C++": "auto capture = Game.startHotMic();\nGame.stopHotMic(&capture);", + "C#": "var capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "Luau": "local capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "C": "RusticCaptureHandle capture = Game.startHotMic();\nGame.stopHotMic(&capture);", + "Java": "var capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "PHP": "global $Game;\n$capture = $Game->startHotMic();\n$Game->stopHotMic($capture);", + "HTML / inline JS": "const capture = Game.startHotMic();\nGame.stopHotMic(capture);" + } + }, + { + "title": "rec:stopRecording", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()\nGame.stopRecording(capture)", + "JavaScript": "const capture = Game.startRecording();\nGame.stopRecording(capture);", + "Python": "capture = Game.startRecording()\nGame.stopRecording(capture)", + "C++": "auto capture = Game.startRecording();\nGame.stopRecording(&capture);", + "C#": "var capture = Game.startRecording();\nGame.stopRecording(capture);", + "Luau": "local capture = Game.startRecording()\nGame.stopRecording(capture)", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);\nGame.stopRecording(&capture);", + "Java": "var capture = Game.startRecording();\nGame.stopRecording(capture);", + "PHP": "global $Game;\n$capture = $Game->startRecording();\n$Game->stopRecording($capture);", + "HTML / inline JS": "const capture = Game.startRecording();\nGame.stopRecording(capture);" + } + }, + { + "title": "mic:stopHotMic", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "JavaScript": "const capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "Python": "capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "C++": "auto capture = Game.startHotMic();\nGame.stopHotMic(&capture);", + "C#": "var capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "Luau": "local capture = Game.startHotMic()\nGame.stopHotMic(capture)", + "C": "RusticCaptureHandle capture = Game.startHotMic();\nGame.stopHotMic(&capture);", + "Java": "var capture = Game.startHotMic();\nGame.stopHotMic(capture);", + "PHP": "global $Game;\n$capture = $Game->startHotMic();\n$Game->stopHotMic($capture);", + "HTML / inline JS": "const capture = Game.startHotMic();\nGame.stopHotMic(capture);" + } + }, + { + "title": "capture:read", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()\nlocal packet = capture:read()", + "JavaScript": "const capture = Game.startRecording();\nconst packet = capture.read();", + "Python": "capture = Game.startRecording()\npacket = capture.read()", + "C++": "auto capture = Game.startRecording();\nauto packet = capture.read();", + "C#": "var capture = Game.startRecording();\nvar packet = capture.read();", + "Luau": "local capture = Game.startRecording()\nlocal packet = capture:read()", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);\nRusticCaptureData packet = Capture.read(capture);", + "Java": "var capture = Game.startRecording();\nvar packet = capture.read();", + "PHP": "global $Game;\n$capture = $Game->startRecording();\n$packet = $capture->read();", + "HTML / inline JS": "const capture = Game.startRecording();\nconst packet = capture.read();" + } + }, + { + "title": "capture:stop", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()\ncapture:stop()", + "JavaScript": "const capture = Game.startRecording();\ncapture.stop();", + "Python": "capture = Game.startRecording()\ncapture.stop()", + "C++": "auto capture = Game.startRecording();\ncapture.stop();", + "C#": "var capture = Game.startRecording();\ncapture.stop();", + "Luau": "local capture = Game.startRecording()\ncapture:stop()", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);\nCapture.stop(capture);", + "Java": "var capture = Game.startRecording();\ncapture.stop();", + "PHP": "global $Game;\n$capture = $Game->startRecording();\n$capture->stop();", + "HTML / inline JS": "const capture = Game.startRecording();\ncapture.stop();" + } + }, + { + "title": "capture:unload", + "note": "Run inside Start or another callback. Retain the handle across callbacks to record a longer session. Hot microphone input requires Windows; capture samples are interleaved PCM. Native SDKs show their equivalent method syntax. Lua also retains the rustic.game and handle-specific stop aliases. See APIs in every language for setup and platform limits.", + "examples": { + "Lua 5.4": "local capture = Game.startRecording()\ncapture:unload()", + "JavaScript": "const capture = Game.startRecording();\ncapture.unload();", + "Python": "capture = Game.startRecording()\ncapture.unload()", + "C++": "auto capture = Game.startRecording();\ncapture.unload();", + "C#": "var capture = Game.startRecording();\ncapture.unload();", + "Luau": "local capture = Game.startRecording()\ncapture:unload()", + "C": "RusticCaptureHandle capture = Game.startRecording(NULL, NULL);\nCapture.unload(capture);", + "Java": "var capture = Game.startRecording();\ncapture.unload();", + "PHP": "global $Game;\n$capture = $Game->startRecording();\n$capture->unload();", + "HTML / inline JS": "const capture = Game.startRecording();\ncapture.unload();" } } ], diff --git a/lib/api-reference-markdown.ts b/lib/api-reference-markdown.ts index a285073..0d4fa9b 100644 --- a/lib/api-reference-markdown.ts +++ b/lib/api-reference-markdown.ts @@ -1,6 +1,6 @@ import { buildCallExamples } from "./api-examples"; import type { ApiCall, ApiCategory } from "./api-reference"; -// Capture uses Lua/Luau handles rather than the JavaScript playback facade. +// Capture uses the same engine-owned service across every gameplay binding. type CaptureReference = { parameters: string; returns: string; @@ -16,7 +16,7 @@ const audioCaptureReference: Record = { }, "stop-recording": { parameters: "| Parameter | Meaning |\n| --- | --- |\n| recording | Optional recording handle owned by this script. Omit it to stop all of this script's recordings. |", - returns: "Stops recording and finalizes any WAV file. Returns the supplied handle, or nil when omitted. Retained PCM remains readable until unload or owner cleanup. Repeated stops are safe.", + returns: "Stops recording and finalizes any WAV file. Lua/Luau return the supplied handle or nil; other bindings return no value. Retained PCM remains readable until unload or owner cleanup. Repeated stops are safe.", example: 'local rec\nreturn {\n Start = function() rec = rustic.game:startRecording() end,\n OnStop = function()\n rustic.game:stopRecording(rec)\n local packet = rec:read()\n print(#packet.samples)\n rec:unload()\n end\n}', }, "start-hot-mic": { @@ -26,7 +26,7 @@ const audioCaptureReference: Record = { }, "stop-hot-mic": { parameters: "| Parameter | Meaning |\n| --- | --- |\n| mic | Optional hot mic handle owned by this script. Omit it to stop all of this script's hot microphones. |", - returns: "Closes the input device and discards unread hot mic input. Returns the supplied handle, or nil when omitted. Repeated stops are safe.", + returns: "Closes the input device and discards unread hot mic input. Lua/Luau return the supplied handle or nil; other bindings return no value. Repeated stops are safe.", example: 'local mic\nreturn {\n Start = function() mic = rustic.game:startHotMic() end,\n OnStop = function() rustic.game:stopHotMic(mic); mic:unload() end\n}', }, "rec-stop-recording": { @@ -46,7 +46,7 @@ const audioCaptureReference: Record = { }, "capture-stop": { parameters: noParameters, - returns: "Stops this recording or hot mic, returning the same handle. This is an alias for stopRecording() on recordings and stopHotMic() on hot microphones.", + returns: "Stops this recording or hot mic. Lua/Luau return the same handle; other bindings return no value. This is an alias for stopRecording() on recordings and stopHotMic() on hot microphones.", example: 'local capture\nreturn {\n Start = function() capture = rustic.game:startRecording() end,\n OnStop = function() capture:stop(); capture:unload() end\n}', }, "capture-unload": { @@ -56,8 +56,8 @@ const audioCaptureReference: Record = { }, }; -const captureSetup = "Available in Lua 5.4 and Luau. Create an Object Component script in `scripts/`, attach it to an enabled scene object, and press **Play**. Start capture in Start or another callback. Use colon syntax. Microphone input requires Windows and OS microphone access. Recording captures audio, not video."; -const captureConstraints = "- Capture handles belong to their script. Destroying the owner, ending Play or successful reload closes devices, finalizes files and releases handles. Read retained data in OnStop before cleanup.\n- Each script may retain at most four capture handles; call `:unload()` after use. Recordings retain at most 64 MiB of PCM, including recordings written to files (about 175 seconds of stereo game audio). Exceeding the limit stops capture and read/stop report an error.\n- Hot mic queues at most two seconds of input. Read regularly, using multiple reads if needed. `droppedSamples` reports lost input. Microphone samples are finite floats in `[-1, 1]`; channel count and rate come from the device.\n- Default F32, I16 and U16 microphone formats are supported. Missing devices, denied access, unsupported formats, startup timeouts and disconnection raise errors. Catch expected failures with `pcall`.\n- A recording destination must be a new `.wav` file in an existing writable directory. Game mixer recording also works headlessly.\n\nSee [the recording and hot mic guide](/docs/guides/gameplay-actions/audio#record-audio-and-read-a-hot-microphone-lua-and-luau) for full setup and examples."; +const captureSetup = "Available in every supported gameplay language. Create an Object Component script in `scripts/`, attach it to an enabled scene object, and press **Play**. Start capture in Start or another callback. Native SDKs use their own member syntax; Lua uses colons. Microphone input requires Windows and OS microphone access. Recording captures audio, not video."; +const captureConstraints = "- Capture handles belong to their script. Destroying the owner, ending Play or successful reload closes devices, finalizes files and releases handles. Read retained data in OnStop before cleanup.\n- Each script may retain at most four capture handles; call `:unload()` after use. Recordings retain at most 64 MiB of PCM, including recordings written to files (about 175 seconds of stereo game audio). Exceeding the limit stops capture and read/stop report an error.\n- Hot mic queues at most two seconds of input. Read regularly, using multiple reads if needed. `droppedSamples` reports lost input. Microphone samples are finite floats in `[-1, 1]`; channel count and rate come from the device.\n- Default F32, I16 and U16 microphone formats are supported. Missing devices, denied access, unsupported formats, startup timeouts and disconnection raise errors. Catch expected failures with `pcall`.\n- A recording destination must be a new `.wav` file in an existing writable directory. Game mixer recording also works headlessly.\n\nSee [the recording and hot mic guide](/docs/guides/api-languages) for full setup and examples."; export function buildApiCategoryMarkdown(category: ApiCategory, title: string, description: string, shared = "") { @@ -90,7 +90,9 @@ const audioParameters: Record = { function audioReturn(method: string) { if (["get-output-devices", "get-input-devices"].includes(method)) return "Returns a list of device records containing string id and name fields. Empty lists are valid on hosts without devices. Enumeration currently requires Windows; headless platforms return an empty list."; if (method === "set-output-device") return "Returns no value. Switches this scene's mixer output to the selected Windows device. Mixer voices retain their playback cursors; queued output samples are discarded, so a brief interruption is possible. A failed switch preserves the previous output."; - if (method === "set-input-device") return "Returns no value. Validates and retains this scene's selected Windows input device. This call does not start microphone capture or route sound to playback. Lua/Luau recording and hot mic capture currently use the system default input independently of this selection."; + if (method === "set-input-device") return "Returns no value. Validates and retains this scene's selected Windows input device. This call does not start microphone capture or route sound to playback. Recording and hot mic capture currently use the system default input independently of this selection."; + if (method === "load") return "Decodes and retains the clip without starting playback; returns a reusable loaded audio handle."; + if (method.startsWith("audio-")) return "Operates on a reusable loaded clip. load returns the same handle; play returns a voice; stop/pause/resume/unload return no value. C reload obtains a new Audio.load handle. Stop retains PCM; unload frees it."; if (["play", "play-at"].includes(method)) return "Returns a voice object with an ID and playback controls."; if (["fade-in", "fade-out", "crossfade"].includes(method)) return "Returns an operation handle. The shared scheduler advances the fade using scene-clock time."; return "Updates the voice playback directly without creating an operation handle."; @@ -114,22 +116,15 @@ function parametersFor(call: ApiCall, shared: string) { } // Core snippets include prerequisite reads and writes, so keep each complete -// language block. Filter helpers that only exist in embedded runtimes. -function coreCallExamples(shared: string, name: string): string { +// language block for every supported binding. +function coreCallExamples(shared: string): string { const examples = section(shared, "Language examples"); - const parts = examples.split(/(?=^### )/m); - const supported = name === "rustic.find_entity" ? ["Lua 5.4"] - : ["print", "warn"].includes(name) ? ["Lua 5.4", "JavaScript", "HTML / inline JS"] : null; - if (!supported) return examples; - return `Available in ${supported.join(", ")}.\n\n${parts.filter((part, index) => index === 0 || supported.includes(part.split("\n")[0].slice(4))).join("")}`; + return examples; } -function compatibilityFor(category: ApiCategory, call: ApiCall, capture: boolean) { +function compatibilityFor(category: ApiCategory, call: ApiCall) { const name = call.signature.split("(")[0]; - const languages = name === "Clock.state" ? "Lua 5.4, Luau, JavaScript, Python and HTML/inline JavaScript. Other SDK examples show available alternatives, not a Clock.state implementation" - : capture ? "Lua 5.4 and Luau" - : name === "rustic.find_entity" ? "Lua 5.4" - : ["print", "warn"].includes(name) ? "Lua 5.4, JavaScript and HTML/inline JavaScript" + const languages = ["print", "warn"].includes(name) ? "Lua 5.4, Luau, JavaScript and HTML/inline JavaScript; other languages use rustic.print/rustic.warn" : "C, C++, C#, Python, JavaScript, Lua 5.4, Luau, Java, PHP and HTML/inline JavaScript (binding spellings and return shapes differ)"; const platforms = category.slug === "api/audio" ? "Native devices and microphone input require Windows. Game-output recording and the mixer can run headlessly. Other operating systems do not supply the Windows native device backend." @@ -158,17 +153,18 @@ export function buildApiCallMarkdown(category: ApiCategory, call: ApiCall, title } const method = call.slug.split("/").pop()!; const audio = category.slug === "api/audio"; - const capture = audio ? audioCaptureReference[method] : undefined; + const capture = audio ? audioCaptureReference[method.replace(/^game-/, "")] : undefined; if (capture) { return [ `# ${call.title}`, `[${title}](/docs/${category.slug})`, - compatibilityFor(category, call, Boolean(capture)), + compatibilityFor(category, call), `## Signature\n\n\`${call.signature}\``, `## Setup and usage\n\n${captureSetup}`, `## Parameters\n\n${capture.parameters}`, `## Return and timing\n\n${capture.returns}`, - `## Language examples\n\n${["Lua 5.4", "Luau"].map((language) => `### ${language}\n\n\`\`\`lua\n${capture.example}\n\`\`\``).join("\n\n")}`, + `## Language examples\n\n${buildCallExamples("audio", call.signature.split("(")[0])}`, + `## Complete Lua lifecycle example\n\n\`\`\`lua\n${capture.example}\n\`\`\``, `## Capture limits and troubleshooting\n\n${captureConstraints}`, relatedFor(category, call), ].join("\n\n"); @@ -185,7 +181,7 @@ export function buildApiCallMarkdown(category: ApiCategory, call: ApiCall, title const name = call.signature.split("(")[0]; const topic = category.slug.slice(4); const example = category.source.startsWith("api:") - ? coreCallExamples(shared, name) + ? coreCallExamples(shared) : buildCallExamples(topic, name); const constraints = section(shared, "Behavior and limitations") || section(shared, "Constraints and behavior"); // Example-specific outcomes belong alongside the example that demonstrates them. @@ -194,7 +190,7 @@ export function buildApiCallMarkdown(category: ApiCategory, call: ApiCall, title return [ `# ${call.title}`, `[${title}](/docs/${category.slug})`, - compatibilityFor(category, call, Boolean(capture)), + compatibilityFor(category, call), `## Signature\n\n\`${call.signature}\``, setup && `## Setup and usage\n\n${setup}`, `## Parameters\n\n${parameters || "See the category conventions and the language-specific examples below; no additional parameter contract is asserted here."}`, diff --git a/lib/api-reference.ts b/lib/api-reference.ts index 695bb19..142b0aa 100644 --- a/lib/api-reference.ts +++ b/lib/api-reference.ts @@ -10,7 +10,7 @@ function category(slug: string, source: string, signatures: string[]): ApiCatego const method = name.split(/[.:]/).pop()!; // Include the receiver when a category exposes both service and handle calls. const receiver = name.split(/[.:]/).slice(0, -1).join("."); - const local = ["voice", "handle", "connection", "builder", "rec", "mic", "capture"].includes(receiver); + const local = ["voice", "handle", "connection", "builder", "rec", "mic", "capture", "loaded"].includes(receiver); let segment = `${local ? `${receiver}-` : ""}${method}` .replace(/([a-z0-9])([A-Z])/g, "$1-$2").replaceAll("_", "-").toLowerCase(); if (segments.has(segment)) segment = `${receiver.split(".").pop()!.toLowerCase()}-${segment}`; @@ -72,6 +72,14 @@ export const apiCategories: ApiCategory[] = [ "Audio.get_input_devices()", "Audio.set_output_device(id = null)", "Audio.set_input_device(id = null)", + "Audio.load(source)", + "audio.load()", + "audio.play(options?)", + "audio.stop()", + "audio.pause()", + "audio.resume()", + "audio.unload()", + "audio.Unload()", "Audio.play(source, options?)", "Audio.playAt(source, position, options?)", "voice.pause()", @@ -91,6 +99,10 @@ export const apiCategories: ApiCategory[] = [ "capture:read()", "capture:stop()", "capture:unload()", + "Game.startRecording(path?, options?)", + "Game.stopRecording(recording?)", + "Game.startHotMic()", + "Game.stopHotMic(mic?)", ]), category("camera-actions", "docs/Scripting/API/camera-actions.md", [ "Camera.current()", @@ -233,6 +245,8 @@ export const apiCategories: ApiCategory[] = [ category("attributes", "api:attributes", [ "rustic.get_attribute(name)", "rustic.edit_attribute(name, value)", + "rustic.get_object_attribute(source, name)", + "rustic.edit_object_attribute(source, name, value)", "rustic.game.Scene.Root.Child.EditAttribute(name, value)", ]), category("input", "api:input", [ @@ -256,7 +270,7 @@ export const apiCategories: ApiCategory[] = [ ]), category("scene", "api:scene", [ "Game.scene.Find(path)", - "Game.scene.List()", + "Game.scene.List(path?)", "rustic.find_entity(name_or_id)", ]), category("instances", "api:instances", [ diff --git a/lib/docs-catalog.ts b/lib/docs-catalog.ts index 138b9a4..90b00a6 100644 --- a/lib/docs-catalog.ts +++ b/lib/docs-catalog.ts @@ -30,6 +30,7 @@ const categoryGroups: DocGroup[] = [ ] }, { label: "Gameplay APIs", docs: [ { slug: "api/gameplay", title: "Gameplay actions", description: "Find the focused reference for each gameplay module.", source: "content/gameplay-api.md" }, + { slug: "guides/api-languages", title: "APIs in every language", description: "Shared API support, reusable audio, recording, microphone capture and live object reads across all script types.", source: "docs/Scripting/apiLanguages.md" }, { slug: "guides/gameplay-actions", title: "Use gameplay actions", description: "Choose a focused guide to use gameplay actions.", source: "docs/Scripting/gameplayActions.md" }, { slug: "guides/gameplay-actions/setup", title: "Set up gameplay actions", description: "Set up gameplay actions for Use gameplay actions.", source: "docs/Scripting/gameplayActions/setup.md", parent: "guides/gameplay-actions" }, { slug: "guides/gameplay-actions/language-bindings", title: "Gameplay language bindings", description: "Gameplay language bindings for Use gameplay actions.", source: "docs/Scripting/gameplayActions/language-bindings.md", parent: "guides/gameplay-actions" }, @@ -76,7 +77,7 @@ const categoryGroups: DocGroup[] = [ { slug: "api/physics-actions/queries", title: "Physics queries", description: "Find primitive colliders with raycasts, sphere casts and overlaps.", source: "docs/Scripting/API/physics-actions/queries.md", parent: "api/physics-actions" }, { slug: "api/physics-actions/forces", title: "Physics forces", description: "Apply forces, impulses and velocity changes to simulated bodies.", source: "docs/Scripting/API/physics-actions/forces.md", parent: "api/physics-actions" }, { slug: "api/effects", title: "Effects", description: "Fade, flash, shake and pulse entity properties with shared easing.", source: "docs/Scripting/API/effects.md" }, - { slug: "api/audio", title: "Audio", description: "Record audio, read microphone PCM and load reusable audio in Lua/Luau; select Windows audio devices and play and fade WAV, MP3, AAC, FLAC, and OGG voices.", source: "docs/Scripting/API/audio.md" }, + { slug: "api/audio", title: "Audio", description: "Record audio, read microphone PCM and load reusable audio in every gameplay language; select Windows audio devices and play and fade WAV, MP3, AAC, FLAC, and OGG voices.", source: "docs/Scripting/API/audio.md" }, { slug: "api/events", title: "Events and signals", description: "Communicate through queued global events and object-scoped signals across scripting languages.", source: "docs/Scripting/API/events.md" }, { slug: "api/clock", title: "Clock", description: "Scale or pause the shared scene clock for actions, animation, physics and audio.", source: "docs/Scripting/API/clock.md" }, { slug: "api/operation-handles", title: "Operation handles", description: "Inspect, pause, resume, cancel and observe engine-scheduled actions.", source: "docs/Scripting/API/operation-handles.md" }, diff --git a/lib/engine-api-baseline.json b/lib/engine-api-baseline.json index 421afda..a162e39 100644 --- a/lib/engine-api-baseline.json +++ b/lib/engine-api-baseline.json @@ -1,5 +1,5 @@ { - "revision": "efc834d838780dc955ac9a4fe5925a972f27b5ff", + "revision": "1f6d9bed41795e1cb40ec3dec588bec2b3d93941", "documented": { "languages": [ "Lua 5.4", @@ -36,6 +36,7 @@ "Audio.fadeOut(voice, duration, easing?)", "Audio.get_input_devices()", "Audio.get_output_devices()", + "Audio.load(source)", "Audio.pitch(voice, value)", "Audio.play(source, options?)", "Audio.playAt(source, position, options?)", @@ -102,11 +103,15 @@ "Events.on(name, callback)", "Events.once(name, callback)", "Game.scene.Find(path)", - "Game.scene.List()", + "Game.scene.List(path?)", "Game.scene.getEnvironment()", "Game.scene.setEnvironment(settings)", "Game.scene.setSkyTexture(path)", "Game.setCurrentCamera(source)", + "Game.startHotMic()", + "Game.startRecording(path?, options?)", + "Game.stopHotMic(mic?)", + "Game.stopRecording(recording?)", "Groups.add(name, entity)", "Groups.create(name)", "Groups.get_members(name)", @@ -148,6 +153,13 @@ "Tween.value(from, to, timing, onUpdate, easing?)", "World.find_by_tag(name)", "World.find_in_radius(center, radius)", + "audio.Unload()", + "audio.load()", + "audio.pause()", + "audio.play(options?)", + "audio.resume()", + "audio.stop()", + "audio.unload()", "builder.animation(entity, clipName, options?)", "builder.call(callback)", "builder.move(entity, position, timing, easing?)", @@ -177,6 +189,7 @@ "rustic.any_key_pressed()", "rustic.delta_time()", "rustic.edit_attribute(name, value)", + "rustic.edit_object_attribute(source, name, value)", "rustic.entity_id()", "rustic.find_entity(name_or_id)", "rustic.fixed_delta_time()", @@ -186,6 +199,7 @@ "rustic.game:stopHotMic(mic?)", "rustic.game:stopRecording(recording?)", "rustic.get_attribute(name)", + "rustic.get_object_attribute(source, name)", "rustic.get_property(name)", "rustic.get_translation()", "rustic.input(action)", @@ -267,7 +281,7 @@ ] }, "crates/engine-scripting/src/javascript.rs": { - "sha256": "627cf3e852fc8dc9ee15b4e257430a8128f4362fe9566a063a4dca77381ecbaa", + "sha256": "a8ff8c6554cb24da7efbbaa344dd76a11f1881b5685437cf1de2314e1e600473", "declarations": [ "pub enum JavaScriptRuntimeError {", "pub fn fixed_update(&mut self, delta: f64) -> Result<(), JavaScriptRuntimeError> {", @@ -286,7 +300,7 @@ ] }, "crates/engine-scripting/src/external_runtime.rs": { - "sha256": "096b17c86c348824ebd5ab439a9469d4ef7a5dd537a30ad8e830e88e1e3365e7", + "sha256": "22bcff3022c24923c83be784eeae9359bb280f2427daac3bb5ab7e4164ccbd0f", "declarations": [ "def fixed_update(dt):", "def on_disable():", @@ -313,28 +327,31 @@ ] }, "crates/engine-scripting/src/cpp_sdk.rs": { - "sha256": "46cae6f9f6ef62ae3d5b44689d92f7caec357177981fec7050c1056107a9088f", + "sha256": "aa39b6839438f63740a0960651cee413d4ecb1378518411db3b529cf4cb97539", "declarations": [] }, "crates/engine-scripting/src/sdk/rustic.h": { - "sha256": "6a9c7e860fff8d28ed87b3bbba3f11410b2420a6a8a03a30e23d5ee2db85a839", + "sha256": "0f832e037993e5fa6def10aaa2a3abc09b0e019d61d3a3fb0c868ef47c26a5d0", "declarations": [] }, "crates/engine-scripting/src/sdk/rustic.py": { - "sha256": "fa7b28565070f30c428b9d5964079c6b272844e08967d2f29b0329f20dc45f65", + "sha256": "6b5cfa99fd798872e1fb3511d06d9a90086d7f058e6fc5279a2b9fe44f51660c", "declarations": [ "def EditAttribute(self, name, value):", "def Find(self, path): return self.state.get(path)", + "def GetAttribute(self,name): return rustic.query(dict(op=\"object_attribute\",source=self.source,name=name))", "def List(self, path=\"Game.scene\"):", - "def __getattr__(self, name): return ObjectPath(self.source + \".\" + name)", + "def __getattr__(self, name):", "def __getitem__(self, name): return ObjectPath(self.source + \".\" + str(name))", "def __init__(self): self.actions=[]", "def __init__(self): self.commands = []", "def __init__(self): self.state = {}", "def __init__(self): self.state = {}; self.commands = instance.commands", "def __init__(self, action, loop=False, repeats=0, ping_pong=False):", + "def __init__(self, source): self.source=source; self.id=None", "def __init__(self, source=\"rustic.game\"): self.source = source", "def __init__(self,id): self.id=id", + "def __init__(self,id,recording): self.id=id; self.recording=recording", "def __init__(self,name,fn,once=False,source=None):", "def _callback(fn, persistent=False):", "def _cleanup(self):", @@ -343,6 +360,8 @@ "def _entity(obj):", "def _gameplay_dispatch():", "def _ik(root,middle,tip,target,options=None):", + "def _loaded(self):", + "def _start_recording(path=None,options=None): return CaptureHandle(rustic.query(dict(op=\"capture_recording_start\",path=path,source=(options or {}).get(\"source\",\"game\"))),True)", "def _token(): return str(uuid.uuid4())", "def _tween(obj, property, to, duration, easing):", "def action(o,name,options=None):", @@ -369,6 +388,7 @@ "def disconnect(connection): connection.disconnect()", "def disconnect(self):", "def edit_attribute(self, name, value): self.commands.append({\"op\":\"edit_attribute\",\"name\":name,\"value\":value})", + "def edit_object_attribute(self,source,name,value): self.commands.append(dict(op=\"edit_attribute\",source=source,name=name,value=value))", "def emit(name,arguments=(),source=None): rustic.gameplay({\"command\":\"emit\",\"signal\":{\"name\":name,\"source\":None if source is None else _entity(source)},\"arguments\":arguments})", "def entity_id(self): return self.state[\"entity_id\"]", "def every(interval,fn,count=None):", @@ -378,6 +398,7 @@ "def fadeOut(voice,duration,easing=\"Linear\"): return Tween.to(voice,\"Volume\",0,duration,easing)", "def find_all(name): return rustic.query({\"op\":\"tags_find_all\",'name':name})", "def find_by_tag(name): return rustic.query({\"op\":\"world_find_by_tag\",'name':name})", + "def find_entity(self,source): return self.query(dict(op=\"find_entity\",source=source))", "def find_in_children(entity,name): return rustic.query({\"op\":\"components_find_in_children\",'entity':_entity(entity),'name':name})", "def find_in_radius(center,radius): return rustic.query({\"op\":\"world_find_in_radius\",'center':center,'radius':radius})", "def fixed_delta_time(self): return self.state[\"fixed_delta_time\"]", @@ -389,12 +410,13 @@ "def gameplay_callbacks(self): return self.state.get(\"gameplay_callbacks\", [])", "def gameplay_connections(self): return self.state.get(\"gameplay_connections\",[])", "def gameplay_states(self): return self.state.get(\"gameplay_states\", [])", - "def getEnvironment(self): return rustic.query({\"op\":\"scene_environment\"})", + "def getEnvironment(self,field=None): return rustic.query({\"op\":\"scene_environment\"} if field is None else dict(op=\"scene_environment_field\",field=field))", "def get_all(entity): return rustic.query({\"op\":\"components_get_all\",'entity':_entity(entity)})", "def get_attribute(self, name): return self.state[\"attributes\"].get(name)", "def get_children(entity): return rustic.query({\"op\":\"hierarchy_get_children\",'entity':_entity(entity)})", "def get_input_devices(): return rustic.query(dict(op=\"audio_get_input_devices\"))", "def get_members(name): return rustic.query({\"op\":\"groups_get_members\",'name':name})", + "def get_object_attribute(self,source,name): return self.query(dict(op=\"object_attribute\",source=source,name=name))", "def get_output_devices(): return rustic.query(dict(op=\"audio_get_output_devices\"))", "def get_property(self, name): return self.state[\"properties\"].get(name)", "def get_translation(self): return self.state[\"translation\"][:]", @@ -408,6 +430,7 @@ "def launch(o,vector): return rustic.query(dict(op=\"physics_launch\",entity=_entity(o),vector=vector))", "def lerp(a,b,t,easing=\"Linear\"): return rustic.query({\"op\":\"lerp\",\"from\":a,\"to\":b,\"progress\":t,\"easing\":easing})", "def load(o,source): return rustic.query(dict(op=\"animation_load\",entity=_entity(o),source=source))", + "def load(self):", "def log(self, level, message): self.commands.append({\"op\":\"log\",\"level\":str(level),\"message\":str(message)})", "def lookAt(obj,position,duration=0,easing=Ease.Linear):", "def looping(v=True): rustic.gameplay(dict(command=\"loop\",handle=h.id,looping=v));return h", @@ -425,19 +448,24 @@ "def pause(): rustic.query(dict(op=\"clock\",paused=True))", "def pause(self): return rustic.query(dict(op=\"audio_pause\",entity=self.id))", "def pause(self): return self._control(\"pause\")", + "def pause(self): rustic.query(dict(op=\"audio_loaded_pause\",entity=self._loaded()))", "def pitch(voice,value): return rustic.query(dict(op=\"audio_pitch\",entity=_entity(voice),value=value))", "def play(o,name,options=None):", "def play(self):", + "def play(self, options=None): return AudioVoice(rustic.query(dict(options or {},op=\"audio_loaded_play\",entity=self._loaded())))", "def play(source,options=None): return AudioVoice(rustic.query(dict(options or {},op=\"audio_play\",source=source)))", "def playAt(source,position,options=None): return Audio.play(source,dict(options or {},position=position))", + "def print(self,*values): self.log(\"info\",\"\\t\".join(map(str,values)))", "def query(self, request):", "def raycast(origin,direction,distance,ignore=None): return rustic.query(dict(op=\"physics_raycast\",origin=origin,direction=direction,distance=distance,ignore=ignore or []))", + "def read(self): return rustic.query(dict(op=\"capture_read\",id=self.id))", "def register(o,clip): return rustic.query(dict(op=\"animation_register\",entity=_entity(o),clip=clip))", "def remap(v,a,b,c,d): return rustic.query(dict(op=\"remap\",value=v,in_min=a,in_max=b,out_min=c,out_max=d))", "def remove(entity,name): return rustic.query({\"op\":\"tags_remove\",'entity':_entity(entity),'name':name})", "def resume(): rustic.query(dict(op=\"clock\",paused=False))", "def resume(self): return rustic.query(dict(op=\"audio_resume\",entity=self.id))", "def resume(self): return self._control(\"resume\")", + "def resume(self): rustic.query(dict(op=\"audio_loaded_resume\",entity=self._loaded()))", "def reverse(self): return self._control(\"reverse\")", "def rotate(obj, to, duration, easing=Ease.Linear): return Tween.to(obj,\"Rotation\",to,duration,easing)", "def run(callbacks):", @@ -459,26 +487,48 @@ "def state(): return rustic.query(dict(op=\"clock\"))", "def state(self): return next((s for s in rustic.gameplay_states() if s[\"handle\"]==self.id),None)", "def stop(self): return rustic.query(dict(op=\"audio_stop\",entity=self.id))", + "def stop(self): rustic.query(dict(op=\"audio_loaded_stop\",entity=self._loaded()))", + "def stop(self): rustic.query(dict(op=\"capture_recording_stop\" if self.recording else \"capture_mic_stop\",id=self.id))", "def timeScale(scale=None): return rustic.query(dict(op=\"clock\",**({} if scale is None else {\"scale\":scale})))[\"scale\"]", "def to(obj, property, to, duration, easing=Ease.Linear): return GameplayHandle(_tween(obj,property,to,duration,easing))", "def to(self,obj,property,to,duration,easing=Ease.Linear): self.actions.append(_tween(obj,property,to,duration,easing)); return self", + "def unload(self):", + "def unload(self): rustic.query(dict(op=\"capture_unload\",id=self.id))", "def value(from_value,to,duration,fn,easing=Ease.Linear): return GameplayHandle({\"kind\":\"value\",\"from\":from_value,\"to\":to,\"duration\":duration,\"easing\":easing,\"token\":_callback(fn,True)})", "def value(keys,fn): return GameplayHandle(rustic.query(dict(op=\"keyframes\",keys=keys,token=_callback(fn,True))))", "def volume(voice,value): return rustic.query(dict(op=\"audio_volume\",entity=_entity(voice),value=value))", "def wait(self,duration): self.actions.append({\"kind\":\"wait\",\"duration\":duration}); return self", + "def warn(self,*values): self.log(\"warn\",\"\\t\".join(map(str,values)))", "def zoom(camera,fov,duration,easing=Ease.Linear): return Tween.to(camera,\"Fov\",fov,duration,easing)" ] }, "crates/engine-scripting/src/sdk/rustic.php": { - "sha256": "95351c11643dfc52f836a11b19d810ef9f1dab455ae4f2d6b9741be2b315cfaf", + "sha256": "17d2528f545480299343c06cdadf4a86622bd47e2ed81b9ee795d1e812ce7825", "declarations": [ + "function __construct(private string $source){}", "function __construct(public string $id){}function stop():void{global $rustic;$rustic->query([\"op\"=>\"audio_stop\",\"entity\"=>$this->id]);}function pause():void{global $rustic;$rustic->query([\"op\"=>\"audio_pause\",\"entity\"=>$this->id]);}function resume():void{global $rustic;$rustic->query([\"op\"=>\"audio_resume\",\"entity\"=>$this->id]);}", + "function __construct(public string $id,private bool $recording){}", + "function load():self{global $rustic;if($this->id===null)$this->id=$rustic->query([\"op\"=>\"audio_load\",\"source\"=>$this->source]);return $this;}", + "function pause():void{global $rustic;$rustic->query([\"op\"=>\"audio_loaded_pause\",\"entity\"=>$this->loaded()]);}", + "function play(array $options=[]):AudioVoice{global $rustic;return new AudioVoice($rustic->query([\"op\"=>\"audio_loaded_play\",\"entity\"=>$this->loaded()]+$options));}", + "function read():array{global $rustic;return $rustic->query([\"op\"=>\"capture_read\",\"id\"=>$this->id]);}", + "function resume():void{global $rustic;$rustic->query([\"op\"=>\"audio_loaded_resume\",\"entity\"=>$this->loaded()]);}", "function rustic_run(array $callbacks): void {", + "function startHotMic():CaptureHandle{global $rustic;return new CaptureHandle($rustic->query([\"op\"=>\"capture_mic_start\"]),false);}", + "function startRecording(?string $path=null,array $options=[]):CaptureHandle{global $rustic;return new CaptureHandle($rustic->query([\"op\"=>\"capture_recording_start\",\"path\"=>$path,\"source\"=>$options[\"source\"]??\"game\"]),true);}", + "function stop():void{global $rustic;$rustic->query([\"op\"=>\"audio_loaded_stop\",\"entity\"=>$this->loaded()]);}", + "function stop():void{global $rustic;$rustic->query([\"op\"=>$this->recording?\"capture_recording_stop\":\"capture_mic_stop\",\"id\"=>$this->id]);}", + "function stopHotMic(?CaptureHandle $handle=null):void{global $rustic;$rustic->query([\"op\"=>\"capture_mic_stop\",\"id\"=>$handle?->id]);}", + "function stopRecording(?CaptureHandle $handle=null):void{global $rustic;$rustic->query([\"op\"=>\"capture_recording_stop\",\"id\"=>$handle?->id]);}", + "function unload():void{global $rustic;$rustic->query([\"op\"=>\"capture_unload\",\"id\"=>$this->id]);}", + "function unload():void{global $rustic;if($this->id!==null){$rustic->query([\"op\"=>\"audio_unload\",\"entity\"=>$this->id]);$this->id=null;}}", "public function EditAttribute(string $name, mixed $value): void { $this->instance->commands[]=[\"op\"=>\"edit_attribute\",\"source\"=>$this->source,\"name\"=>$name,\"value\"=>$value]; }", "public function EditAttribute(string $name,mixed $value): void { $this->instance->commands[]=[\"op\"=>\"edit_attribute\",\"name\"=>$name,\"value\"=>$value]; }", "public function Find(string $path): ?string { return $this->state[$path] ?? null; }", "public function GetAttribute(string $name): mixed { return $this->state[\"attributes\"][$name] ?? null; }", - "public function List(string $path=\"Game.scene\"): array { return array_values(array_filter($this->state, fn($id,$name)=>$path===\"Game.scene\" || str_starts_with($name,$path.\".\"), ARRAY_FILTER_USE_BOTH)); }", + "public function GetAttribute(string $name):mixed{global $rustic;return $rustic->query([\"op\"=>\"object_attribute\",\"source\"=>$this->source,\"name\"=>$name]);}", + "public function List(string $path=\"Game.scene\"): array { return array_values(array_filter($this->state, fn($id,$name)=>str_starts_with($name,($path===\"\"?\"Game.scene\":$path).\".\"), ARRAY_FILTER_USE_BOTH)); }", + "public function __call(string $name,array $args):mixed{global $Game;if($this->source!==\"rustic.game\")throw new BadMethodCallException($name);return $Game->$name(...$args);}", "public function __construct(private InstanceApi $instance){ $this->scene=new SceneApi(); }", "public function __construct(private InstanceApi $instance, private string $source=\"rustic.game\") {}", "public function __construct(public InstanceApi $instance) { $this->game=new ObjectPath($instance); }", @@ -493,10 +543,15 @@ "public function delta_time(): float { return $this->state[\"delta_time\"]; }", "public function edit_attribute(string $name, mixed $value): void { $this->EditAttribute($name,$value); }", "public function edit_attribute(string $name,mixed $value): void { $this->EditAttribute($name,$value); }", + "public function edit_object_attribute(string $source,string $name,mixed $value):void{$this->instance->commands[]=[\"op\"=>\"edit_attribute\",\"source\"=>$source,\"name\"=>$name,\"value\"=>$value];}", "public function entity_id(): string { return $this->state[\"entity_id\"]; }", + "public function find_entity(string $source):?string{return $this->query([\"op\"=>\"find_entity\",\"source\"=>$source]);}", "public function fixed_delta_time(): float { return $this->state[\"fixed_delta_time\"]; }", "public function getEnvironment(): array { global $rustic; return $rustic->query([\"op\"=>\"scene_environment\"]); }", + "public function getEnvironmentField(string $field):mixed{global $rustic;return $rustic->query([\"op\"=>\"scene_environment_field\",\"field\"=>$field]);}", "public function get_attribute(string $name): mixed { return $this->GetAttribute($name); }", + "public function get_attribute(string $name):mixed{return $this->GetAttribute($name);}", + "public function get_object_attribute(string $source,string $name):mixed{return $this->query([\"op\"=>\"object_attribute\",\"source\"=>$source,\"name\"=>$name]);}", "public function get_property(string $name): mixed { return $this->state[\"properties\"][$name] ?? null; }", "public function get_translation(): array { return $this->state[\"translation\"]; }", "public function input(string $name): array { return $this->state[\"actions\"][$name] ?? [\"pressed\"=>false,\"released\"=>false,\"held\"=>false,\"axis\"=>0]; }", @@ -510,6 +565,7 @@ "public function parallel(array $branches):self{$this->actions[]=[\"kind\"=>\"parallel\",\"actions\"=>array_map(fn($b)=>[\"kind\"=>\"sequence\",\"actions\"=>$b->actions],$branches)];return $this;}", "public function pause(): self {return $this->control(\"pause\");}", "public function play(): GameplayHandle {return Gameplay::start([\"kind\"=>\"sequence\",\"actions\"=>$this->actions]);}", + "public function print(mixed ...$values):void{$this->log(\"info\",implode(\" \",array_map(\"strval\",$values)));}", "public function query(array $query): mixed {", "public function resume(): self {return $this->control(\"resume\");}", "public function reverse(): self {return $this->control(\"reverse\");}", @@ -524,6 +580,7 @@ "public function stop():self{return $this->cancel();}", "public function to(?string $e,string $p,mixed $value,float $d,string $ease=\"Linear\"):self{$this->actions[]=Tween::action($e,$p,$value,$d,$ease);return $this;}", "public function wait(float $duration): self {$this->actions[]=[\"kind\"=>\"wait\",\"duration\"=>$duration];return $this;}", + "public function warn(mixed ...$values):void{$this->log(\"warn\",implode(\" \",array_map(\"strval\",$values)));}", "public static function action(?string $entity,string $property,mixed $to,float|array $duration,string $easing=\"Linear\"): array {", "public static function after(float $delay,Closure $fn): GameplayHandle {return Gameplay::start([\"kind\"=>\"wait\",\"duration\"=>$delay])->onFinished($fn);}", "public static function callback(callable $fn,bool $once=false):string{$id=bin2hex(random_bytes(16));self::$callbacks[$id]=$fn;self::$once[$id]=$once;return $id;}", @@ -542,7 +599,7 @@ ] }, "crates/engine-scripting/src/sdk/rustic.luau": { - "sha256": "f4b2fcccd1976dd6bdc980d1deec65da490d535eeeb197aa8f66f18522d07a4b", + "sha256": "8f9b7a5172be785279a3cfdd37fe8b1ea288c6f2666080be43b9ae1caa2b5c8b", "declarations": [ "function Game.scene.Find(path) return state.scene_paths[path] end", "function Game.scene.List(path)", @@ -553,13 +610,16 @@ "function rustic.any_key_pressed() return state.any_key_pressed end", "function rustic.delta_time() return state.delta_time end", "function rustic.edit_attribute(name,value) push('edit_attribute',{name=name,value=value}) end", + "function rustic.edit_object_attribute(source,name,value) push(\"edit_attribute\",{source=source,name=name,value=value}) end", "function rustic.entity_id() return state.entity_id end", + "function rustic.find_entity(source) return rustic.query({op='find_entity',source=source}) end", "function rustic.fixed_delta_time() return state.fixed_delta_time end", "function rustic.gameplay(request) push('gameplay',{request=request}) end", "function rustic.gameplay_callbacks() return state.gameplay_callbacks or {} end", "function rustic.gameplay_connections() return state.gameplay_connections or {} end", "function rustic.gameplay_states() return state.gameplay_states or {} end", "function rustic.get_attribute(name) return state.attributes[name] end", + "function rustic.get_object_attribute(source,name) return rustic.query({op='object_attribute',source=source,name=name}) end", "function rustic.get_property(name) return state.properties[name] end", "function rustic.get_translation() return table.unpack(state.translation) end", "function rustic.input(name) return state.actions[name] or inactive() end", @@ -573,17 +633,20 @@ ] }, "crates/engine-scripting/src/sdk/Rustic.java": { - "sha256": "6339f27815ea12477d70722c074b4b6373e8153021090bb15b90d7878303d0ad", + "sha256": "a1379fe2a7bf0584848cf275e3a559a1e63063759d6c55df768e2187c95cfb5b", "declarations": [] }, "crates/engine-scripting/src/sdk/Rustic.cs": { - "sha256": "c0983c98c93fbe0c8eb8eef5238d23905ff71fe8f2da55fe451b6170264de6bf", + "sha256": "ee76e572c039f72d755730608de2a7311aa5f40ea125d1518dd1073ba7e22acb", "declarations": [ "public double delta_time() => State.GetProperty(\"delta_time\").GetDouble();", "public double fixed_delta_time() => State.GetProperty(\"fixed_delta_time\").GetDouble();", "public double[] get_translation() => State.GetProperty(\"translation\").EnumerateArray().Select(x => x.GetDouble()).ToArray();", "public object? GetAttribute(string name) => Read(\"attributes\", name);", + "public object? GetAttribute(string name)=>RusticApi.ConvertValue(Rustic.Query(new{op=\"object_attribute\",source,name}));", "public object? get_attribute(string name) => GetAttribute(name);", + "public object? get_attribute(string name)=>GetAttribute(name);", + "public object? get_object_attribute(string source,string name)=>ConvertValue(Rustic.Query(new{op=\"object_attribute\",source,name}));", "public object? get_property(string name) => Read(\"properties\", name);", "public static class Animation{", "public static class Audio{", @@ -646,24 +709,39 @@ "public string Id {get;}", "public string entity_id() => State.GetProperty(\"entity_id\").GetString()!;", "public string? Find(string path) => State.TryGetProperty(path, out var value) ? value.GetString() : null;", + "public string? find_entity(string source)=>Rustic.Query(new{op=\"find_entity\",source}).GetString();", "public void Add(string source, string? parent = null) => Commands.Add(new { op = \"add_instance\", source, parent });", "public void Clone(string source, string? parent = null) => Commands.Add(new { op = \"clone_instance\", source, parent });", "public void EditAttribute(string name, object? value) => Commands.Add(new { op=\"edit_attribute\", name, value });", "public void EditAttribute(string name, object? value) => commands.Add(new {op=\"edit_attribute\",source,name,value});", "public void SetCurrentCamera(string source) => commands.Add(new { op=\"set_current_camera\", source });", + "public void Unload()=>unload();", "public void add(string source, string? parent = null) => Add(source, parent);", "public void clone(string source, string? parent = null) => Clone(source, parent);", "public void edit_attribute(string name, object? value) => EditAttribute(name, value);", "public void edit_attribute(string name, object? value) => EditAttribute(name,value);", + "public void edit_object_attribute(string source,string name,object? value)=>Commands.Add(new{op=\"edit_attribute\",source,name,value});", "public void log(string level,string message) => Commands.Add(new { op=\"log\", level, message });", + "public void pause()=>Query(new{op=\"audio_loaded_pause\",entity=Loaded()});", + "public void print(string message)=>log(\"info\",message);", + "public void resume()=>Query(new{op=\"audio_loaded_resume\",entity=Loaded()});", "public void setCurrentCamera(string source) => SetCurrentCamera(source);", "public void set_enabled(bool enabled) => Commands.Add(new { op=\"set_enabled\", enabled });", "public void set_property(string name, object value) => Commands.Add(new { op=\"set_property\", name, value });", - "public void set_translation(double x,double y,double z) => Commands.Add(new { op=\"set_translation\", value=new[]{x,y,z} });" + "public void set_translation(double x,double y,double z) => Commands.Add(new { op=\"set_translation\", value=new[]{x,y,z} });", + "public void stop()=>Query(new{op=\"audio_loaded_stop\",entity=Loaded()});", + "public void stop()=>Query(new{op=recording?\"capture_recording_stop\":\"capture_mic_stop\",id=Id});", + "public void stopHotMic(Rustic.CaptureHandle? handle=null)=>Rustic.Game.stopHotMic(handle);", + "public void stopHotMic(Rustic.CaptureHandle? handle=null)=>Rustic.Query(new{op=\"capture_mic_stop\",id=handle?.Id});", + "public void stopRecording(Rustic.CaptureHandle? handle=null)=>Rustic.Game.stopRecording(handle);", + "public void stopRecording(Rustic.CaptureHandle? handle=null)=>Rustic.Query(new{op=\"capture_recording_stop\",id=handle?.Id});", + "public void unload()=>Query(new{op=\"capture_unload\",id=Id});", + "public void unload(){if(id is not null){Query(new{op=\"audio_unload\",entity=id});id=null;}}", + "public void warn(string message)=>log(\"warn\",message);" ] }, "crates/engine-scripting/src/sdk/gameplay.lua": { - "sha256": "6e4303fc9e43817e5942e3e76a02992fa688db260912f631fed0d974a34edae1", + "sha256": "e7e40da4a63c0e0ef869e491b3cabac13aae7112e428b1e10a2976a6823236e6", "declarations": [ "function Animation.addMarker(o,clip,time,name) return rustic.query({op=\"animation_marker\",entity=entity(o),clip=clip,time=time,name=name}) end", "function Animation.blend(o,from,to,opts) opts=opts or {};opts.blendIn=opts.duration or 0.3;opts.sourceClip=type(from)=='table' and from or rustic.query({op='animation_ref',entity=entity(o),name=from});return Animation.play(o,to,opts) end", @@ -709,9 +787,13 @@ "function Events.emit(name,arguments,source) send({command='emit',signal={name=name,source=source and entity(source)},arguments=arguments}) end", "function Events.on(name,fn) return connect(name,fn,false) end", "function Events.once(name,fn) return connect(name,fn,true) end", - "function Game.scene.getEnvironment() return rustic.query({op='scene_environment'}) end", + "function Game.scene.getEnvironment(field) return rustic.query(field and {op='scene_environment_field',field=field} or {op='scene_environment'}) end", "function Game.scene.setEnvironment(settings)", "function Game.scene.setSkyTexture(path)", + "function Game.startHotMic() return rustic.game:startHotMic() end", + "function Game.startRecording(path,options) return rustic.game:startRecording(path,options) end", + "function Game.stopHotMic(handle) return rustic.game:stopHotMic(handle) end", + "function Game.stopRecording(handle) return rustic.game:stopRecording(handle) end", "function Groups.add(name,entity) return rustic.query({op=\"groups_add\",name=name,entity=resolve_entity(entity)}) end", "function Groups.create(name) return rustic.query({op=\"groups_create\",name=name}) end", "function Groups.get_members(name) return rustic.query({op=\"groups_get_members\",name=name}) end", @@ -762,11 +844,22 @@ "function rustic.game:startHotMic()", "function rustic.game:startRecording(path, options)", "function rustic.game:stopHotMic(mic)", - "function rustic.game:stopRecording(recording)" + "function rustic.game:stopRecording(recording)", + "function rustic.get_object_attribute(source,name) return rustic.query({op='object_attribute',source=source,name=name}) end", + "function rustic.print(...) print(...) end", + "function rustic.warn(...) warn(...) end" ] }, "crates/engine-scripting/src/sdk/gameplay.js": { - "sha256": "abdfa05d6f196331614e319a605a33d1b0fb5ff7ce16919c231362d11503d374", + "sha256": "768b4ea097dab95c38a7ef93068bc4354bd7579371ad90fd13a70b8f676a756f", + "declarations": [] + }, + "crates/engine-scripting/src/sdk/media-api.lua": { + "sha256": "0cb7c17ea375ff5c7e5d7dcf0c05ca0b3d1d6aa3155391428e3681fc5354ff73", + "declarations": [] + }, + "crates/engine-scripting/src/sdk/media-api.d.ts": { + "sha256": "1df5092973fd3cd6f012f2cfae705481bec30f650f06b468c1602f72723bad36", "declarations": [] }, "crates/engine-core/src/gameplay/mod.rs": { @@ -865,7 +958,7 @@ ] }, "crates/engine-play/src/script_runtime.rs": { - "sha256": "492ca137bbdf973b030f6458f8e55cccaf63d17a6c326dd5b2ea56dc9ae5e369", + "sha256": "3e5b58c423ab6f7ac78eac1606e53ccc56b26c74e0a35950c88fe7df14ab2cb5", "declarations": [ "def on_start():", "def on_start(): rustic.game.Demo.Player.EditAttribute(\"Position\",[1,2,3])", diff --git a/scripts/check-engine-drift.mjs b/scripts/check-engine-drift.mjs index dfa3018..8950fc2 100644 --- a/scripts/check-engine-drift.mjs +++ b/scripts/check-engine-drift.mjs @@ -10,7 +10,7 @@ export const engineSources = [ "crates/engine-scripting/src/model.rs", "crates/engine-scripting/src/runtime.rs", "crates/engine-scripting/src/lua.rs", "crates/engine-scripting/src/javascript.rs", "crates/engine-scripting/src/external_runtime.rs", "crates/engine-scripting/src/cpp_sdk.rs", - ...["rustic.h", "rustic.py", "rustic.php", "rustic.luau", "Rustic.java", "Rustic.cs", "gameplay.lua", "gameplay.js"].map((file) => `crates/engine-scripting/src/sdk/${file}`), + ...["rustic.h", "rustic.py", "rustic.php", "rustic.luau", "Rustic.java", "Rustic.cs", "gameplay.lua", "gameplay.js", "media-api.lua", "media-api.d.ts"].map((file) => `crates/engine-scripting/src/sdk/${file}`), ...["mod.rs", "query.rs", "audio.rs", "signals.rs", "ease.rs"].map((file) => `crates/engine-core/src/gameplay/${file}`), ...["audio_output.rs", "audio_input.rs", "audio_capture.rs", "script_runtime.rs"].map((file) => `crates/engine-play/src/${file}`), ]; diff --git a/tests/api-reference.test.mjs b/tests/api-reference.test.mjs index 1362601..784c94b 100644 --- a/tests/api-reference.test.mjs +++ b/tests/api-reference.test.mjs @@ -9,13 +9,7 @@ const { buildApiCallMarkdown, buildApiCategoryMarkdown } = load("api-reference-m const { exampleLanguages } = load("api-examples.ts"); const exampleData = JSON.parse(await readFile(new URL("../lib/api-example-data.json", import.meta.url), "utf8")); const captureSlugs = ["start-recording", "stop-recording", "start-hot-mic", "stop-hot-mic", "rec-stop-recording", "mic-stop-hot-mic", "capture-read", "capture-stop", "capture-unload"]; -const supportedLanguages = (call) => { - // Display titles omit receivers; signatures retain the API's identity. - const name = call.signature.split("(")[0]; - return captureSlugs.includes(call.slug.split("/").pop()) ? ["Lua 5.4", "Luau"] - : name === "rustic.find_entity" ? ["Lua 5.4"] - : ["print", "warn"].includes(name) ? ["Lua 5.4", "JavaScript", "HTML / inline JS"] : [...exampleLanguages]; -}; +const supportedLanguages = () => [...exampleLanguages]; const calls = apiCategories.flatMap((category) => category.calls); @@ -109,19 +103,18 @@ test("static export publishes every category and call with the category breadcru }); -test("recording and hot mic APIs are registered and publish Lua/Luau usage", async () => { +test("recording and hot mic APIs preserve routes and publish every language binding", async () => { const category = apiCategories.find((entry) => entry.slug === "api/audio"); const shared = await readFile(new URL(`../${category.source}`, import.meta.url), "utf8"); for (const slug of captureSlugs) { const call = category.calls.find((entry) => entry.slug === `api/audio/${slug}`); assert.ok(call, `Missing published capture API: ${slug}`); const markdown = buildApiCallMarkdown(category, call, "Audio", shared); - assert.match(markdown, /Available in Lua 5.4 and Luau/); + assert.match(markdown, /Available in every supported gameplay language/); assert.match(markdown, /```lua/); - assert.ok(!markdown.includes("```javascript"), slug); + assert.ok(markdown.includes("```javascript"), slug); assert.ok(!markdown.includes("undefined"), slug); - assert.match(markdown, /OnStop = function/); - assert.doesNotMatch(markdown, /\bStop = function/); + for (const language of exampleLanguages) assert.ok(markdown.includes("### "+language), slug); const exported = await readFile(new URL(`../out/docs/${call.slug}.html`, import.meta.url), "utf8"); assert.ok(exported.includes(call.title), slug); assert.ok(exported.includes("64 MiB"), slug); diff --git a/tests/docs-integrity.test.mjs b/tests/docs-integrity.test.mjs index b04829f..e18c79e 100644 --- a/tests/docs-integrity.test.mjs +++ b/tests/docs-integrity.test.mjs @@ -44,7 +44,7 @@ test("capture teardown uses OnStop and references actual restrictions", async () const doc = await readDoc("api-call:api/audio/stop-recording"); assert.match(doc, /OnStop = function/); assert.doesNotMatch(doc, /\bStop = function/); - assert.match(doc, /Lua 5.4 and Luau/); + assert.match(doc, /every supported gameplay language/); assert.match(doc, /Microphone input requires Windows/); }); From 03c6f5968273dda6bf0e91c401677910e57ad750 Mon Sep 17 00:00:00 2001 From: Jamie Wells Date: Wed, 14 Oct 2026 01:34:23 +0000 Subject: [PATCH 2/2] Fix recording language preference browser expectations --- tests/browser/docs.spec.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tests/browser/docs.spec.ts b/tests/browser/docs.spec.ts index 3a3dd66..0e0d071 100644 --- a/tests/browser/docs.spec.ts +++ b/tests/browser/docs.spec.ts @@ -1,4 +1,5 @@ import { expect, test } from "@playwright/test"; +import { exampleLanguages } from "../../lib/script-languages"; const api = "/docs/api/transforms/set-translation"; test("representative routes, themes and responsive layout", async ({ page }) => { @@ -12,7 +13,7 @@ test("representative routes, themes and responsive layout", async ({ page }) => await page.getByRole("button", { name: "Toggle theme" }).click(); await expect(page.locator(".site")).not.toHaveClass(/dark/); expect(errors).toEqual([]); }); -test("language preference persists across navigation and refresh, supports unsupported APIs and comparison", async ({ page }) => { +test("language preference persists across navigation and refresh, supports recording and comparison", async ({ page }) => { await page.goto(api); await expect(page.getByRole("tab", { name: "Lua 5.4", exact: true })).toHaveAttribute("aria-selected", "true"); await page.getByRole("tab", { name: "Python", exact: true }).click(); @@ -22,9 +23,13 @@ test("language preference persists across navigation and refresh, supports unsup await page.locator("article").getByRole("link", { name: "rustic.get_translation()", exact: true }).click(); await expect(page.getByRole("tab", { name: "Python", exact: true })).toHaveAttribute("aria-selected", "true"); await page.reload(); await expect(page.getByRole("tab", { name: "Python", exact: true })).toHaveAttribute("aria-selected", "true"); - await page.goto("/docs/api/audio/start-recording"); await expect(page.getByRole("status")).toContainText("No Python example"); + await page.goto("/docs/api/audio/start-recording"); + await expect(page.getByRole("tab", { name: "Python", exact: true })).toHaveAttribute("aria-selected", "true"); + await expect(page.locator('[data-language="Python"]')).toBeVisible(); + await expect(page.locator('[data-language="Python"]')).toContainText("Game.startRecording()"); + await expect(page.getByText("No Python example", { exact: false })).toHaveCount(0); await page.getByRole("tab", { name: "Show All Languages" }).click(); - await expect(page.locator('[data-language="Lua 5.4"]')).toBeVisible(); await expect(page.locator('[data-language="Luau"]')).toBeVisible(); + for (const language of exampleLanguages) await expect(page.locator(`[data-language="${language}"]`)).toBeVisible(); }); test("keyboard tabs, focus, deep links and copying", async ({ page, context }) => { await context.grantPermissions(["clipboard-read", "clipboard-write"]);