System Requirement Specifications for Suika3

1. Overview

Suika3 is a full-stack visual novel (VN) engine optimized for cross-platform deployment, including mobile-first environments. It provides a multi-layered DSL environment designed to balance ease of authoring with professional-grade extensibility and portability.

Target Audience

This document is intended for engineers at organizations that fork Suika3 to build an in-house engine. It specifies the behavioral, architectural, and interface requirements that any conformant implementation must satisfy. Game developers using the engine as-is should refer to the tutorial and tag reference documentation instead.

Scope

This document covers:

  • Engine architecture and layer responsibilities
  • Boot and execution sequence
  • DSL specifications (NovelML, Anime, GUI, Ray)
  • Platform requirements
  • Out-of-scope constraints

For the architectural rationale and design discussion, see design.md. For the full tag reference, see novelml-tags.md. For the API reference, see ray-vn-api.md and ray-2d-api.md. Full tag and API requirements in SRS form are given in Appendices A, B, and C (forthcoming).


2. Design Philosophy

2.1 Mobile-First

Suika3 shall treat smartphones and tablets as primary target devices. UI/UX decisions shall prioritize touch-first interaction patterns over PC-centric conventions. Specifically:

  • The engine shall not place small, persistent button clusters around the message window.
  • The system menu shall be accessed via a single hamburger-style button (SysBtn) that appears on interaction and auto-hides after inactivity.
  • This behavior shall be hard-coded to ensure compliance with mobile store guidelines. It may be disabled only for kiosk or demo builds via configuration.

2.2 Store Publishing Compatibility

The engine shall be deployable to all major app stores without policy violations.

  • On iOS, Ray scripts shall support Ahead-of-Time (AOT) compilation to ANSI C, satisfying the App Store prohibition on runtime code generation.
  • The engine shall not depend on any proprietary middleware (e.g., Live2D) that would restrict store submission or redistribution.
  • UI layout and interaction models shall comply with store human interface guidelines on all Tier 1 platforms.

2.3 High Portability

The engine shall be portable across a wide range of platforms through a hardware abstraction architecture. Platform support is tiered by required compliance level:

TierPlatforms
1iOS, Android, HarmonyOS NEXT, Windows, macOS, Linux
2Gaming consoles (via Unity integration)
3WebAssembly, Chromebook

Tier 1 platforms shall receive full feature support and be tested against every release. Tier 2 and 3 platforms shall receive best-effort support.

2.4 Extensibility Beyond Visual Novels

While the engine is optimized for visual novels, the underlying 2D engine layer shall expose sufficient APIs to support genre-fusing titles (e.g., VN combined with RPG or action mechanics) without requiring engine modification.

2.5 Exclusion of Proprietary Technologies

The engine shall depend exclusively on open, royalty-free technologies. Closed-source middleware, proprietary asset formats, and patented compression schemes shall not be introduced into the core engine.


3. Architecture

3.1 Layered Component Model

Suika3 is structured as four discrete layers stacked vertically. Each layer exposes a public C API to the layer immediately above it and consumes only the C API of the layer immediately below it. No layer shall bypass this constraint by calling across non-adjacent layers or accessing internal symbols of another layer.

+----------------------------------------+
| VN Engine (Suika3)                     |  Runs .novel (NovelML)
+----------------------------------------+
| 2D Game Engine (Playfield Engine)      |  Runs .ray (Ray / NoctLang)
+----------------------------------------+
| Scripting Language (NoctLang)          |  JIT / AOT infrastructure
+----------------------------------------+
| Hardware Abstraction Layer (StratoHAL) |  OS and hardware portability
+----------------------------------------+

This architecture is referred to as the Layered Component Model.

The constraints on inter-layer communication shall be:

  • Each layer shall implement exactly one area of concern.
  • Each layer shall expose its functionality through a public C language API.
  • Each layer shall call only the public C API of the layer one level below it.
  • Direct access to internal functions or data of non-adjacent layers is prohibited.

This one-to-one dependency model is chosen over object-oriented many-to-many inheritance to minimize coupling, simplify porting, and enable independent layer replacement.

3.2 StratoHAL

StratoHAL (Hardware Abstraction Layer) is the lowest layer. It shall:

  • Abstract all OS-specific and hardware-specific operations, including windowing, input, audio, file I/O, and rendering surface creation.
  • Expose a uniform C API to Playfield Engine regardless of the underlying platform.
  • Define the platform-dependent main() entry point via the PF_DEFINE_MAIN macro.
  • Provide the hal_main() function as the engine's actual runtime entry point.

3.3 NoctLang

NoctLang is a dynamically typed scripting language and its runtime. It shall:

  • Provide a sandboxed virtual machine (VM) for executing .ray scripts.
  • Support Just-In-Time (JIT) compilation on PC platforms for development performance.
  • Support Ahead-of-Time (AOT) compilation to ANSI C for native binary deployment, required for iOS App Store compliance.
  • Expose a C API for VM creation, script loading, function invocation, and value exchange.

3.4 Playfield Engine

Playfield Engine is the 2D game engine layer. It shall:

  • Load and execute main.ray through the NoctLang VM.
  • Register StratoHAL event callbacks (setup, start, update, render).
  • Expose the low-level 2D rendering and audio API (Engine.*) to Ray scripts.
  • Drive the frame loop by invoking update() and render() in main.ray each frame.

3.5 VN Engine (Suika3)

The VN Engine is the topmost layer. It shall:

  • Implement the NovelML interpreter, including all standard tags.
  • Manage the layer system (stage, character layers, message box, etc.).
  • Implement the Anime, GUI, and Config DSLs.
  • Expose the VN-level API (Suika.*) to Ray scripts for custom tag development.
  • Manage save/load state, including execution position and variable state.

4. Boot and Execution Sequence

4.1 Entry Point Definition

The engine entry point shall be defined in game.c using the macro:

PF_DEFINE_MAIN(pf_init_hook, init_aot_code)

This macro shall expand to a platform-appropriate main() function (or equivalent, such as WinMain() on Windows or UIApplicationMain() on iOS). The pf_init_hook parameter registers engine-level initialization. The init_aot_code parameter registers AOT-compiled script code when AOT mode is active; it shall be NULL in interpreter mode.

4.2 Bootstrap Sequence

The bootstrap sequence shall proceed as follows:

  1. main() calls hal_main() in StratoHAL.
  2. hal_main() calls hal_bootstrap() in Playfield Engine.
  3. hal_bootstrap() registers StratoHAL event callbacks for the start, update, and render events.
  4. hal_bootstrap() creates a NoctLang VM instance.
  5. The NoctLang VM loads main.ray.
  6. hal_bootstrap() invokes setup() in main.ray.
  7. setup() returns a dictionary containing the window width, height, and title string.
  8. hal_bootstrap() returns these values to hal_main().
  9. hal_main() creates the application window at the specified dimensions with the specified title.

4.3 Start Sequence

After window creation, StratoHAL fires the start event:

  1. StratoHAL invokes the registered start callback in Playfield Engine.
  2. Playfield Engine invokes start() in main.ray via the NoctLang VM.
  3. start() calls Suika.start().
  4. Suika.start() loads and parses config.ini.
  5. Suika.start() loads start.novel and sets the execution position to the first tag.
  6. Suika.start() returns. Control returns up the call chain to StratoHAL.

4.4 Game Loop

After the start sequence completes, StratoHAL enters the game loop. Each frame shall proceed as follows:

  1. StratoHAL invokes the update callback in Playfield Engine.
  2. Playfield Engine invokes update() in main.ray.
  3. update() calls Suika.update().
  4. Suika.update() reads the current tag name and parameters from start.novel (or the currently loaded NovelML file).
  5. Suika.update() resolves and calls the corresponding Tag_*() function.
  6. Tag_*() executes one frame of the tag's behavior. If the tag's work is complete, it calls Suika.moveToNextTag() and returns true. If more frames are needed, it returns false without advancing.
  7. Control returns to StratoHAL.
  8. StratoHAL invokes the render callback in Playfield Engine.
  9. Playfield Engine invokes render() in main.ray.
  10. render() calls Suika.render().
  11. Suika.render() composites and draws all stage layers to the framebuffer.
  12. StratoHAL presents the framebuffer and begins the next frame.

5. DSL Stack

Suika3 exposes four domain-specific languages to game creators. Each targets a distinct authoring concern and is processed by a distinct subsystem of the engine.

5.1 NovelML

NovelML is a tag-based markup language for authoring VN scenarios.

  • File extension: .novel
  • Entry point: start.novel (loaded by Suika.start())
  • Syntax: A sequence of tags of the form [tagname param="value" ...]
  • Execution: Tags execute sequentially, one per update cycle (or across multiple frames for time-based tags). A single execution position is maintained at all times.

NovelML is the primary authoring surface for game creators. It is intentionally simple and does not expose general-purpose programming constructs directly; branching and variables are provided through specific tags ([if], [set], etc.).

5.2 Anime

Anime is a declarative language for describing layer-based raster image animations.

  • File extension: .anime
  • Loaded by: [anime] tag or indirectly by GUI button state transitions
  • Syntax: Named blocks, each describing affine transform sequences for one layer
  • Execution: Blocks in a single file may execute in parallel

Anime targets artists and designers who need precise control over timing and layer transforms without writing code.

5.3 GUI

GUI is a declarative language for defining interactive button screens.

  • File extension: .gui
  • Loaded by: [gui] tag in NovelML
  • Syntax: Named blocks, each defining one button with type, images, position, and behavior
  • Execution: Synchronous — [gui] blocks until a button is clicked or the screen is dismissed

The GUI DSL is deliberately synchronous to avoid the complexity of asynchronous callback models, which are difficult for non-programmer authors to reason about.

5.4 Ray (NoctLang)

Ray is the general-purpose scripting layer, built on NoctLang.

  • File extension: .ray
  • Entry point: main.ray (loaded at bootstrap)
  • Syntax: NoctLang (dynamically typed, C-family syntax)
  • Execution: JIT on PC; AOT to native binary for iOS

Ray serves two roles:

  1. Engine extension: Authors define custom NovelML tags as Tag_*() functions.
  2. Low-level access: Direct access to Suika.* VN API and Engine.* 2D API.

6. NovelML Requirements

6.1 Execution Model

  • The engine shall maintain exactly one execution position in the currently loaded NovelML file at any time.
  • Tags shall execute in the order they appear in the file, from top to bottom.
  • Execution shall advance to the next tag only when the current tag calls Suika.moveToNextTag().
  • Time-based tags (e.g., [bg] with a fade duration) shall span multiple frames before advancing execution.

6.2 File Loading

  • The engine shall load start.novel automatically on startup via Suika.start().
  • The [load] tag shall replace the current execution context with a new NovelML file.
  • The [load] tag shall optionally jump to a named label within the loaded file.
  • Loaded files shall share the same variable namespace as the calling file.

6.3 Variable Model

  • The engine shall support a flat, dynamically typed variable namespace shared across all loaded NovelML files within a session.
  • Variables shall be set and read by tags ([if], [choose], etc.) using named identifiers.
  • The variable namespace shall be saved and restored as part of save/load state.

6.4 Localization

  • The [text] tag shall support locale-suffixed text-<locale> parameters.
  • The [choose] tag shall support locale-suffixed text<N>-<locale> parameters.
  • The engine shall resolve the best-matching locale using the following priority:
    1. Most specific locale variant (e.g., text-en-gb)
    2. Language-level fallback (e.g., text-en)
    3. Unsuffixed default (text)
  • The engine shall use the locale specified by game.locale in config.ini, or the OS system locale if game.locale is empty.
  • Changing game.locale at runtime (via [config] or Suika.setConfig()) shall take effect on all subsequent tag executions without requiring a restart.

6.5 Macro System

  • The engine shall support macro definition via [defmacro] / [endmacro].
  • A macro shall be callable via [callmacro] and shall return via [returnmacro].
  • Macros shall support parameterized invocation.

6.6 Plugin Tag Extension

  • Any Ray function named Tag_<name>() that is loaded into the NoctLang VM shall become available as the NovelML tag [<name>].
  • Plugin tags shall follow the same execution contract as built-in tags: they shall call Suika.moveToNextTag() when complete and return true.
  • Plugins shall be loaded via Suika.loadPlugin({name: "<name>"}) in main.ray.
  • Plugin files shall reside at system/plugin/<name>/<name>.ray.
  • A plugin shall define a plugin_init_<name>() function that is called on load.

6.7 Save and Load

  • The engine shall be capable of saving the current execution position (file path and tag index) and the full variable namespace to a save slot.
  • On load, the engine shall restore execution to the saved position and restore all variables.
  • The engine shall support multiple save slots.
  • In release mode (release_mode.enable=true), save data shall be written to the OS-designated user data directory rather than the game folder.

7. Ray Requirements

7.1 NoctLang Language Requirements

The NoctLang VM embedded in the engine shall support the following language features:

  • Dynamic typing: Variables shall not require type declarations and shall accept any value type at any time.
  • Primitive types: Integer, floating-point, string, boolean, and null.
  • Composite types: Arrays (ordered, integer-indexed) and dictionaries (string-keyed).
  • Control flow: if/else, for/while loops, break, continue, return.
  • Functions: Named functions, anonymous functions (lambdas), and first-class function values.
  • Closures: Functions shall capture variables from their enclosing scope.
  • Range iteration: The .. operator shall produce an iterable integer range.
  • Global variables: Variables declared outside any function shall be accessible from all functions in the same script file.

7.2 JIT Execution Requirements

  • On PC platforms (Windows, macOS, Linux), the NoctLang VM shall support Just-In-Time compilation for performance during development iteration.
  • JIT compilation shall be transparent to the script author.

7.3 AOT Execution Requirements

  • The suika3-aotcomp tool shall convert .ray scripts into ANSI C source code.
  • The generated library.c shall be compiled together with the engine into a single native binary.
  • In AOT mode, calls to Suika.loadPlugin() in main.ray shall be disabled, as plugin scripts are compiled directly into the binary.
  • AOT mode is required for iOS App Store compliance and shall be supported on all Tier 1 platforms.
  • See aot.md for the AOT compilation workflow.

7.4 Plugin System Requirements

  • The engine shall support loading Ray plugin files at runtime via Suika.loadPlugin().
  • Each plugin shall reside at system/plugin/<name>/<name>.ray.
  • Each plugin shall define a function plugin_init_<name>() that is called immediately after the plugin is loaded.
  • All Tag_*() functions defined in a loaded plugin shall become available as NovelML tags after the plugin is loaded.
  • In AOT mode, plugins shall be compiled into the binary and their init functions called at startup instead of being loaded at runtime.

8. Anime Requirements

8.1 File Structure

  • An Anime file shall consist of one or more named blocks.
  • Each block shall describe a transform sequence for exactly one named layer.
  • Block names shall be arbitrary identifiers with no semantic effect on execution.

8.2 Layer Properties

Each block shall support the following animatable properties:

PropertyDescription
from-x / to-xHorizontal position in pixels
from-y / to-yVertical position in pixels
from-a / to-aAlpha (transparency), 0–255
from-sx / to-sxHorizontal scale factor
from-sy / to-syVertical scale factor
from-r / to-rRotation in degrees

8.3 Coordinate System

  • Absolute coordinates shall be specified as plain integers (e.g., 100).
  • Relative coordinates shall be prefixed with r (e.g., r0 = current position, r-100 = 100 pixels left of current position).

8.4 Timing

  • Each block shall specify start: and end: times in seconds, relative to the start of the anime file's playback.
  • The engine shall interpolate property values linearly between from-* and to-* values over the specified time range.

8.5 Parallel Execution

  • All blocks within a single Anime file shall execute in parallel, not sequentially.
  • The [anime] tag shall block NovelML execution until all blocks in the file complete.

9. GUI Requirements

9.1 Button Definition Model

  • A GUI file shall consist of a global block and one or more named button blocks.
  • The global block shall specify fade-in and fade-out durations for the GUI screen.
  • Each button block shall specify at minimum: type, position (x, y), and idle/hover image paths.

9.2 Execution Model

  • The [gui] tag shall display the defined buttons and block NovelML execution.
  • Execution shall resume only when the player clicks a button or dismisses the GUI.
  • Asynchronous button callbacks (buttons active during normal tag execution) are explicitly not supported and shall not be implemented.

9.3 Button Types

The engine shall support the following button types:

TypeBehavior
labelJump to a named label in the current NovelML script.
saveOpen the save screen.
loadOpen the load screen.
quitExit the application.
languageSwitch the active locale to the value specified by lang:.
configOpen the configuration screen.
historyOpen the dialogue history screen.
autoToggle auto-advance mode.
skipToggle skip mode.
previewDisplay a text preview area within the GUI.
customInvoke a named Ray function.

9.4 Language Button Requirements

  • A language button shall display an image-active image when its lang: value matches the current game.locale.
  • Clicking a language button shall set game.locale to the button's lang: value and take effect immediately on all subsequent tag executions.
  • The new locale shall persist across sessions (saved to global save data).

9.5 Animation Integration

  • Each button shall optionally specify idle_anime and hover_anime paths.
  • The engine shall play hover_anime when the pointer enters the button, and resume idle_anime when the pointer leaves.
  • Button state animations shall loop continuously.

10. Config Requirements

10.1 File Format

  • The engine shall load config.ini from the game root directory on startup.
  • The file shall use a flat key=value format, one entry per line.
  • Lines beginning with # shall be treated as comments and ignored.
  • Keys shall be organized by namespace prefix (e.g., msgbox.*, character.*).

10.2 Runtime Modification

  • The [config] NovelML tag shall allow any config key to be modified at runtime.
  • The Suika.setConfig() Ray API function shall provide the same capability from scripts.
  • Runtime modifications shall take effect immediately on subsequent engine behavior.
  • Modified values shall be persisted in global save data so they survive session restarts.

10.3 Save Data Classification

Config keys shall be classified into two categories:

  • Global save keys: Persisted across all save slots (e.g., volume, locale, seen-message flags). These are written to a single global save file.
  • Local save keys: Persisted per save slot (e.g., current chapter name for slot thumbnails). These are written alongside the save slot data.

The engine shall expose Suika.isGlobalSaveConfig() and Suika.isLocalSaveConfig() to allow Ray scripts to query the classification of any key.

10.4 Release Mode

  • When release_mode.enable=true, the engine shall write all save data to the OS-designated user data directory (e.g., %APPDATA% on Windows, ~/Library on macOS).
  • When release_mode.enable=false (default), save data shall be written to the game's working directory.
  • Release mode shall be required for store submissions on all Tier 1 platforms.

11. Platform Requirements

11.1 Common Requirements (All Platforms)

The following requirements apply to all supported platforms:

  • Unicode: The engine shall render text using Unicode-encoded TrueType fonts. All UI text, dialogue, and file paths shall support Unicode.
  • Localization: The engine shall detect the OS system locale on startup and use it as the default game.locale if not overridden in config.ini.
  • Save data compatibility: Save data written on one Tier 1 platform shall be loadable on any other Tier 1 platform without modification, provided the game files are identical.
  • Asset packaging: The engine shall support loading game assets from either a packed assets.arc archive or loose files in the working directory. assets.arc takes precedence if both are present.

11.2 Tier 1 Platform Requirements

Windows

  • The engine shall build using Visual Studio 2022 or later, or GCC/Clang on WSL.
  • Game files shall be distributed as suika3.exe and assets.arc in the same folder.
  • Save data shall be written to %APPDATA% when release mode is enabled.

macOS

  • The engine shall build using Xcode 8.2.1 or later.
  • Simple distribution shall be supported via Suika3.dmg paired with assets.arc.
  • Full distribution (embedded assets, custom icon) shall be supported via the Xcode project at SDK/macos/project/.
  • Save data shall be written to ~/Library/Application Support/ when release mode is enabled.

Linux

  • The engine shall build using GCC 4.4 or later, or Clang, with CMake 3.22 or later.
  • The engine depends on GStreamer for audio/video; Flatpak is the recommended distribution method to ensure dependency availability on player systems.
  • For SteamOS, the recommended Valve packaging method for Steam Deck shall be followed.

iOS

  • Ray scripts shall be compiled via AOT before submission to the App Store.
  • The engine shall build using the Xcode project at SDK/ios/.
  • A VS Code build task (Build iOS IPA) shall be provided for automated on-device deployment.
  • The device shall be registered as a development device in Xcode prior to use of the VS Code task.

Android

  • Game files shall be deployed as loose files into SDK/android/app/src/main/assets/. assets.arc is not used on Android.
  • The engine shall build using Android Studio or the VS Code Build Android APK task.
  • The VS Code task shall automatically download OpenJDK and the Android SDK if absent.

HarmonyOS NEXT (OpenHarmony)

  • Game files shall be deployed as loose files into SDK/openharmony/entry/src/main/resources/rawfile/. assets.arc is not used on HarmonyOS NEXT.
  • The engine shall build using DevEco Studio.

11.3 Tier 2 Platform Requirements (Consoles via Unity)

  • The engine shall be integrable into a Unity project via the plugin at SDK/unity/.
  • Game files shall be deployed as loose files into SDK/unity/Assets/StreamingAssets/.
  • The Unity project shall require Allow unsafe code enabled in Player Settings.
  • The Unity project shall require the audio sampling rate set to 44100 Hz.
  • Console-specific certification and submission requirements are outside the scope of the engine and are the responsibility of the integrating party.

11.4 Tier 3 Platform Requirements (WebAssembly)

  • The engine shall be deployable as a WebAssembly application via SDK/wasm/index.html paired with assets.arc on a static web server.
  • The WebAssembly build is intended for demos and browser-based previews, not as a primary distribution channel.
  • A local test server (suika3-web.exe on Windows; python3 -m http.server on macOS/Linux) shall be provided for development use.

12. Out of Scope

The following are explicitly outside the scope of Suika3 and shall not be introduced into the core engine without a revision to this specification:

12.1 PC-Exclusive Features

Suika3 is not a replacement for legacy PC-only VN engines. Features that cannot be implemented portably across all Tier 1 platforms shall not be added to the core engine.

12.2 3D Graphics

The engine is currently focused on 2D rendering. 3D graphics support is not in scope for the current version. Future versions may introduce 3D support alongside AI-driven asset generation pipelines, but this requires a separate specification revision.

12.3 Proprietary Middleware

Integration with proprietary middleware (e.g., Live2D, Spine, FMOD) shall not be added to the core engine. Such integrations may be implemented as external plugins by the integrating party, but shall not introduce store submission restrictions or royalty obligations on the core engine.

12.4 WebAssembly as Primary Distribution

The WebAssembly port is scoped for demo and preview use only. It shall not be treated as a primary distribution channel. Features that would require significant WebAssembly-specific implementation (e.g., persistent local file system access, background audio on mobile browsers) are out of scope.


Appendix A: NovelML Tag Requirements

This appendix specifies the behavioral requirements for every built-in NovelML tag. Each tag shall be implemented as a Tag_<name>() function in Ray and registered via Suika.installTag(). When the VN Engine resolves a tag name to a function and the function completes its frame work, it shall call Suika.moveToNextTag() to advance the execution pointer, unless the tag is explicitly designed to hold execution across multiple frames (e.g., [text], [wait], [click]).

A.1 Localization Parameter Resolution

For tags that accept localized parameters (e.g., text, text-<locale>, voice, voice-<locale>), the implementation shall apply the following priority when the current locale is <lang>-<region>:

  1. Parameter with suffix -<lang>-<region> (e.g., text-en-gb)
  2. Parameter with suffix -<lang> (e.g., text-en)
  3. Parameter without suffix (e.g., text)

The implementation shall support the following locale suffixes:

SuffixLanguage
-enEnglish (fallback for all regions)
-en-usEnglish (United States)
-en-gbEnglish (United Kingdom)
-en-auEnglish (Australia)
-en-nzEnglish (New Zealand)
-frFrench (fallback)
-fr-frFrench (France)
-fr-caFrench (Canada)
-esSpanish (fallback)
-es-laSpanish (Latin America)
-deGerman
-itItalian
-ruRussian
-elGreek
-zhChinese (Simplified)
-zh-twChinese (Traditional, Taiwan)
-jaJapanese

There shall be no automatic fallback from Traditional Chinese (-zh-tw) to Simplified Chinese (-zh).

A.2 Tag Specifications

[anime] — Run Animation

The [anime] tag shall load and execute an animation definition file.

ParameterDefaultTypeRequirement
fileStringRequired unless stop="true". Path to anime file.
asyncfalseBooleanIf false, execution shall block until completion.
registerStringRequired to start or stop a named async animation.
stopfalseBooleanIf true, shall stop the named registered animation.
showsysbtntrueBooleanControls system button visibility (sync only).
showmsgboxtrueBooleanControls message box visibility (sync only).
shownameboxtrueBooleanControls name box visibility (sync only).

When async="true", the tag shall return immediately and the animation shall run in the background. The register parameter shall be required to later stop the animation via stop="true".


[bg] — Change Background

The [bg] tag shall change the background layer with an optional transition effect.

ParameterDefaultTypeRequirement
fileStringRequired. Set to none to clear the background.
time0FloatTransition duration in seconds.
clearfalseBooleanIf true, shall hide all character layers.
fadenormalStringTransition method: normal, rule, or melt.
ruleStringRequired when fade is rule or melt.
x0IntegerX-axis offset of the background.
y0IntegerY-axis offset of the background.
scale-x1.0FloatX-axis scaling factor.
scale-y1.0FloatY-axis scaling factor.
center-x0IntegerRotation pivot X.
center-y0IntegerRotation pivot Y.
rotate0FloatRotation in degrees (clockwise positive).
alpha255IntegerOpacity: 0 (transparent) to 255 (opaque).

Numeric parameters shall support an r prefix to denote a relative offset from the current value (e.g., x="r50" moves 50 pixels right of the current position).


[bgm] — Play Background Music

The [bgm] tag shall start or stop background music playback.

ParameterDefaultTypeRequirement
fileStringRequired. Set to none to stop playback.
oncefalseBooleanIf true, the track shall not loop.

The implementation shall accept Ogg Vorbis files at 44,100 Hz sampling rate. By default, BGM shall loop indefinitely until stopped or replaced.


[callmacro] — Call Macro

The [callmacro] tag shall invoke a named macro defined by [defmacro].

ParameterDefaultTypeRequirement
nameStringRequired. Must match a [defmacro] name.
fileStringOptional. File containing the macro; defaults to current.
arg1–10StringArguments accessible inside the macro as ${arg1}${arg10}.

After the macro completes or a [returnmacro] is reached, execution shall return to the tag immediately following [callmacro].


[ch] — Character Display

The [ch] tag shall load, replace, or hide character images on named layer slots.

Layer slots (each accepts a filename or none): bg, back, left, left-center, center, right-center, right, face.

Per-slot suffix parameters (e.g., center-x, left-a):

SuffixDefaultRequirement
-x0X position; supports r prefix for relative.
-y0Y position; supports r prefix for relative.
-a255Opacity 0–255.
-scale-x1.0X scaling; supports r prefix.
-scale-y1.0Y scaling; supports r prefix.
-center-x0Rotation pivot X.
-center-y0Rotation pivot Y.
-rotate0Rotation in degrees; supports r prefix.
-dimfalseIf true, renders layer at 50% brightness.

Transition parameters:

ParameterDefaultRequirement
time0Transition duration in seconds; applies to all slots.
fadenormalnormal, rule, or melt.
ruleRequired when fade is rule or melt.

[chapter] — Set Chapter Name

The [chapter] tag shall update the current chapter name stored in game state.

ParameterDefaultRequirement
nameRequired. The chapter name string.

The chapter name shall be included in save slot metadata and shall be readable via Suika.getChapterName().


[choose] — Display Selection Options

The [choose] tag shall display up to 8 choice buttons and store the player's selection in a named variable.

ParameterDefaultRequirement
text1text8Display text for each option. At least one required.
text<N>-<locale>Localized display text; resolved via §A.1.
nameRequired. Variable name to receive the selected value.
value1value8Value stored when the corresponding option is selected.
time0If >0, auto-selects after this many seconds (timer mode).

When value<N> is absent for a selected option, the implementation shall store the corresponding text<N> in the variable instead.


[click] — Wait for Click

The [click] tag shall pause execution until the player clicks the mouse button or presses a recognized advance key. It takes no parameters.


[config] — Set Configuration Value

The [config] tag shall update a named configuration key at runtime.

ParameterRequirement
nameRequired. Must match a valid key in config.ini.
valueRequired. New value string.

The effect shall be identical to calling Suika.setConfig({key:..., value:...}).


[defmacro] — Define Macro

The [defmacro] tag shall mark the start of a macro definition block. All tags between [defmacro] and the corresponding [endmacro] shall be stored and not executed at definition time.

ParameterRequirement
nameRequired. Unique identifier for the macro.

[else] — Default Conditional Branch

The [else] tag shall mark the fallback branch of an [if] block. It takes no parameters. It shall be executed only when no preceding [if] or [elseif] condition in the same block evaluated to true.


[elseif] — Additional Conditional Branch

The [elseif] tag shall evaluate a condition within an [if] block.

ParameterRequirement
lhsRequired. Left-hand side of comparison.
opRequired. Comparison operator (see [if]).
rhsRequired. Right-hand side of comparison.

[endif] — End Conditional Block

The [endif] tag shall mark the end of an [if] conditional block. It takes no parameters. Every [if] shall have exactly one matching [endif].


[endmacro] — End Macro Definition

The [endmacro] tag shall mark the end of a [defmacro] block. It takes no parameters. Execution control shall return to the call site after [endmacro] is reached during macro execution.


[goto] — Jump to Label

The [goto] tag shall unconditionally transfer execution to a named label within the current script file.

ParameterRequirement
nameRequired. Must match a [label] name.

[gui] — Show GUI

The [gui] tag shall load and display a GUI definition file. Execution shall block until the GUI completes.

ParameterRequirement
fileRequired. Path to the .gui definition file.

[if] — Conditional Branch

The [if] tag shall evaluate a condition and execute the enclosed block only if the condition is true.

ParameterRequirement
lhsRequired. Left-hand side value or variable ref.
opRequired. One of: ===, ==, >, >=, <, <=.
rhsRequired. Right-hand side value or variable ref.

Operators === shall perform string comparison; all other operators shall perform numeric comparison after parsing both sides as floating-point numbers. Variable references of the form ${name} shall be expanded before comparison.


[label] — Define Label

The [label] tag shall define a named jump target.

ParameterRequirement
nameRequired. Unique within the current script file.
descOptional. Comment string; no effect.

[layer] — Direct Layer Manipulation

The [layer] tag shall directly set properties of a named stage layer.

ParameterDefaultRequirement
nameRequired. Layer name (see full list in §A.3).
fileIf provided, loads the image; none clears it.
x0X position.
y0Y position.
alpha255Opacity 0–255.
scale-x1.0X scaling factor.
scale-y1.0Y scaling factor.
center-x0Rotation pivot X.
center-y0Rotation pivot Y.
rotate0.0Rotation in degrees.

Layers marked "(Invisible to [layer])" in §A.3 shall not be accessible via this tag.


[load] — Load Script File

The [load] tag shall terminate execution of the current script and begin executing a new NovelML file.

ParameterRequirement
fileRequired. Path to the target .novel file.
labelOptional. Label to jump to within the new file.

All variables shall persist across [load] transitions. Any tags placed after [load] in the original file shall not be executed.


[move] — Animate Layer

The [move] tag shall animate one or more layer properties over a specified duration.

ParameterDefaultRequirement
nameRequired. Target layer (see slots list in [ch]).
timeRequired. Duration in seconds.
asyncfalseIf true, returns immediately without blocking.
accelnormalAcceleration curve: normal, accel, deaccel, smoothstep, invsmoothstep.
<slot>-<suffix>Per-layer property targets; supports r prefix for relative.

Animatable per-slot suffixes: -x, -y, -a, -scale-x, -scale-y, -center-x, -center-y, -rotate, -dim.


[pencil] — Draw Text on Layer

The [pencil] tag shall render text directly onto a stage layer's image surface.

ParameterDefaultRequirement
textRequired. Text string to draw.
layertext1Target layer name.
font-type0Font slot index (0–3).
font-size16Font size in points.
color#000000Text color in #RRGGBB format.
outline-width0Outline pixel width.
outline-color#ffffffOutline color.
line-marginExtra space between lines.
char-margin0Extra space between characters.
x0Drawing area X position.
y0Drawing area Y position.
widthDrawing area width.
heightDrawing area height.

[returnmacro] — Return from Macro

The [returnmacro] tag shall immediately exit the currently executing macro and return control to the tag following the invoking [callmacro]. It takes no parameters.


[se] — Play Sound Effect

The [se] tag shall play a sound effect on the SE audio track.

ParameterDefaultRequirement
fileRequired. Set to none to stop SE playback.
loopfalseIf true, the sound effect shall loop continuously.

The implementation shall accept Ogg Vorbis files at 44,100 Hz. A looped SE shall be restored when a save slot is loaded.


[set] — Set Variable

The [set] tag shall assign a value to a named variable.

ParameterDefaultRequirement
nameRequired. Variable name.
valueValue to assign directly.
value1Left operand for arithmetic assignment.
opOperator: +, -, *, /, // (int div), %.
value2Right operand for arithmetic assignment.
globalfalseIf true, the variable shall be marked global (saved in global save).

All variables shall be stored internally as strings. Arithmetic operators shall parse operands as floating-point numbers before computing.


[skip] — Set Skip Status

The [skip] tag shall enable or disable the script skip mode.

ParameterRequirement
enableRequired. true enables skip; false disables.

When skip is disabled, the engine shall ignore all player skip inputs.


[text] — Display Text

The [text] tag shall display a message in the message box and optionally show a speaker name. It shall block execution until the player advances (click or key).

ParameterDefaultRequirement
textMessage content.
text-<locale>Localized message; resolved via §A.1.
voiceVoice file to play with the message.
voice-<locale>Localized voice file; resolved via §A.1 voice rules.
nameSpeaker name shown in the name box.
actionControl action (see below).
spaceIn NVL mode, prepend this string before the message.

action values:

ValueBehavior
clearClear the message box content.
newClear the message box and make it visible.
showShow the message box without changing text.
hideHide the message box.
inlineAppend text without advancing to next line.

Voice resolution when locale is <lang>-<region>:

  1. voice-<lang>-<region> parameter
  2. voice/<lang>-<region>/ + voice parameter
  3. voice-<lang> parameter
  4. voice/<lang>/ + voice parameter
  5. voice parameter

[video] — Play Video

The [video] tag shall play a full-motion video file.

ParameterDefaultRequirement
fileRequired. Path to video file (.mp4 H.264+AAC format).
skippablefalseIf true, the player may skip by clicking.

Video files shall not be packed into assets.arc and shall be distributed as loose files. Execution shall block until the video ends or is skipped.


[volume] — Set Audio Volume

The [volume] tag shall set the volume of a named audio track.

ParameterRequirement
trackRequired. One of bgm, se, voice.
volume / volRequired. Float 0.0 (silent) to 1.0 (maximum).
timeOptional. Fade duration in seconds; 0 = immediate.

Volume changes with time > 0 shall be applied asynchronously; the tag shall return immediately.


[wait] — Wait for Time

The [wait] tag shall pause execution for a specified duration without requiring player input.

ParameterRequirement
timeRequired. Duration in seconds (decimal supported).
hidemsgboxOptional. If true, force-hide the message box.
hidenameboxOptional. If true, force-hide the name box.

A.3 Layer Name Reference

The following layer names shall be valid targets for the [layer] tag unless marked as invisible.

Layer NameDescriptionAccessible via [layer]
bgBackground ImageYes
bg2Background Image 2Yes
efb1efb4Back Effect layers 1–4Yes
chbCenter-Back CharacterYes
chlLeft CharacterYes
chlcLeft-Center CharacterYes
chrRight CharacterYes
chrcRight-Center CharacterYes
chcCenter CharacterYes
eff1eff4Front Effect layers 1–4Yes
chfFace CharacterYes
text1text8Text Layers 1–8Yes
msgboxMessage BoxNo (invisible)
nameboxName BoxNo (invisible)
clickClick AnimationNo (invisible)
gui1gui32GUI Button Layers 1–32No (invisible)

Eye (-eye), Lip (-lip), and Fade-Out (-fo) sub-layers exist for each character layer (e.g., chc-eye, chc-lip, chc-fo) and are managed by the animation subsystem; they shall not be targets of [layer].


Appendix B: Ray VN API Requirements (Suika.*)

This appendix specifies the requirements for all Suika.* API functions exposed to Ray scripts. Unless otherwise noted, each function takes a single dictionary argument and returns the type indicated. Functions with "No parameters" accept an empty dictionary {}.

All Suika.* API functions shall be callable from within Tag_*() plugin functions and from main.ray lifecycle callbacks (setup, start, update, render). The implementation shall register each function via Suika.installAPI() or equivalent internal mechanism.

B.1 Fundamental

FunctionParametersReturnsRequirement
Suika.loadPlugin()name (String, direct)Shall load the plugin Ray file at system/plugin/<name>/<name>.ray and execute plugin_init_<name>(). The sole Suika.* function that accepts a non-dictionary argument.
Suika.installAPI()name (String), func (Function)Shall register a Ray function as a callable API with the given name.
Suika.installTag()name (String), func (Function)Shall register a Ray function as a NovelML tag handler invoked as Tag_<name>().

B.2 Configuration

FunctionParametersReturnsRequirement
Suika.setConfig()key, valueShall update the named config key at runtime.
Suika.getConfigCount()IntegerShall return the total number of config keys.
Suika.getConfigKey()indexStringShall return the config key at the given index.
Suika.isGlobalSaveConfig()keyBooleanShall return true if the key is saved to global save.
Suika.isLocalSaveConfig()keyBooleanShall return true if the key is saved to local save.
Suika.getConfigType()keyStringShall return "s", "b", "i", or "f".
Suika.getStringConfig()keyStringShall return the config value as a string.
Suika.getBoolConfig()keyBooleanShall return the config value as a boolean.
Suika.getIntConfig()keyIntegerShall return the config value as an integer.
Suika.getFloatConfig()keyFloatShall return the config value as a float.
Suika.getConfigAsString()keyStringShall return any config value type as a string.
Suika.compareLocale()localeBooleanShall return true if the given locale matches the current runtime locale.

B.3 Input

FunctionParametersReturnsRequirement
Suika.getMousePosX()IntegerShall return the current mouse X coordinate.
Suika.getMousePosY()IntegerShall return the current mouse Y coordinate.
Suika.isMouseLeftPressed()BooleanShall return true while the left button is held down.
Suika.isMouseRightPressed()BooleanShall return true while the right button is held down.
Suika.isMouseLeftClicked()BooleanShall return true for the frame in which a left-click completed.
Suika.isMouseRightClicked()BooleanShall return true for the frame in which a right-click completed.
Suika.isMouseDragging()BooleanShall return true while the mouse is moving with a button held.
Suika.isReturnKeyPressed()BooleanShall return true while Return/Enter is held.
Suika.isSpaceKeyPressed()BooleanShall return true while Space is held.
Suika.isEscapeKeyPressed()BooleanShall return true while Escape is held.
Suika.isUpKeyPressed()BooleanShall return true while Up arrow is held.
Suika.isDownKeyPressed()BooleanShall return true while Down arrow is held.
Suika.isLeftKeyPressed()BooleanShall return true while Left arrow is held.
Suika.isRightKeyPressed()BooleanShall return true while Right arrow is held.
Suika.isPageUpKeyPressed()BooleanShall return true while Page Up is held.
Suika.isPageDownKeyPressed()BooleanShall return true while Page Down is held.
Suika.isControlKeyPressed()BooleanShall return true while Control is held.
Suika.isSKeyPressed()BooleanShall return true while S key is held.
Suika.isLKeyPressed()BooleanShall return true while L key is held.
Suika.isHKeyPressed()BooleanShall return true while H key is held.
Suika.isTouchCanceled()BooleanShall return true when a touch event was canceled.
Suika.isSwiped()BooleanShall return true when a swipe gesture was detected.
Suika.clearInputState()Shall suppress further input processing for the current frame.

B.4 Game State

FunctionParametersReturnsRequirement
Suika.startCommandRepetition()Shall begin a multi-frame tag execution mode.
Suika.stopCommandRepetition()Shall end multi-frame tag execution mode.
Suika.isInCommandRepetition()BooleanShall return true while in multi-frame mode.
Suika.setMessageActive()Shall set the message display state to active.
Suika.clearMessageActive()Shall reset the message display state.
Suika.isMessageActive()BooleanShall return the current message display state.
Suika.startAutoMode()Shall activate automatic advance mode.
Suika.stopAutoMode()Shall deactivate automatic advance mode.
Suika.isAutoMode()BooleanShall return true while auto mode is active.
Suika.startSkipMode()Shall activate skip mode.
Suika.stopSkipMode()Shall deactivate skip mode.
Suika.isSkipMode()BooleanShall return true while skip mode is active.
Suika.setSaveLoad()enable (Boolean)Shall enable or disable save/load operations.
Suika.isSaveLoadEnabled()BooleanShall return the current save/load enable state.
Suika.setNonInterruptible()enable (Boolean)Shall enable or disable non-interruptible mode.
Suika.isNonInterruptible()BooleanShall return the current non-interruptible state.
Suika.setPenPosition()x, y (Integer)Shall set the text drawing pen position.
Suika.getPenPositionX()IntegerShall return the pen X position.
Suika.getPenPositionY()IntegerShall return the pen Y position.
Suika.pushForCall()file, indexBooleanShall push a return point onto the call stack.
Suika.popForReturn()DictShall pop the call stack; returns {file, index}.
Suika.readCallStack()sp (Integer)DictShall read call stack entry at sp; returns {file, index}.
Suika.writeCallStack()sp, file, indexShall overwrite call stack entry at sp.
Suika.setCallArgument()index, valueBooleanShall set calling argument at the given index.
Suika.getCallArgument()indexStringShall return calling argument at the given index.
Suika.isPageMode()BooleanShall return true when NVL page mode is enabled.
Suika.appendBufferedMessage()messageShall append to the NVL page buffer.
Suika.getBufferedMessage()StringShall return the NVL page buffer contents.
Suika.clearBufferedMessage()Shall clear the NVL page buffer.
Suika.resetPageLine()Shall reset the NVL line counter.
Suika.incPageLine()Shall increment the NVL line counter.
Suika.isPageTop()BooleanShall return true if at the first line of a page.
Suika.registerBGVoice()fileShall register a background voice file.
Suika.getBVoice()StringShall return the registered background voice filename.
Suika.setBGVoicePlaying()isPlaying (Boolean)Shall update the background voice playing state.
Suika.isBGVoicePlaying()BooleanShall return the background voice playing state.
Suika.setChapterName()nameShall update the current chapter name.
Suika.getChapterName()StringShall return the current chapter name.
Suika.setLastMessage()message, isAppendShall set or append the last displayed message.
Suika.getLastMessage()StringShall return the last displayed message.
Suika.setTextSpeed()speed (Float)Shall set the character display speed.
Suika.getTextSpeed()FloatShall return the current text speed.
Suika.setAutoSpeed()speed (Float)Shall set the auto-advance speed.
Suika.getAutoSpeed()FloatShall return the current auto-advance speed.
Suika.markLastEnglishTagIndex()Shall record the current tag index as the last English tag.
Suika.getLastEnglishTagIndex()IntegerShall return the recorded English tag index.
Suika.clearLastEnglishTagIndex()Shall clear the recorded English tag index.
Suika.getLastTagName()StringShall return the name of the last executed tag.

B.5 Image

FunctionParametersReturnsRequirement
Suika.createImageFromFile()fileObjectShall load an image from the asset path; returns null on failure.
Suika.createImage()width, heightObjectShall create a blank RGBA image of the given dimensions.
Suika.getImageWidth()imgIntegerShall return the width in pixels.
Suika.getImageHeight()img or imageIntegerShall return the height in pixels.
Suika.getImagePixels()imgObjectShall return a packed pixel buffer for direct manipulation.
Suika.updateImagePixels()imgShall upload the pixel buffer to the GPU texture; must be called after pixel edits.
Suika.makePixel()r, g, b, a (Integer 0–255)IntegerShall pack RGBA components into a single pixel value.
Suika.destroyImage()imageShall free the image object and its GPU texture.
Suika.drawImage()dstImage, dstLeft, dstTop, srcImage, dstWidth, dstHeight, srcLeft, srcTop, alpha, blendShall blit the source image region onto the destination.
Suika.drawImage3D()dstImage, x1x4, y1y4, srcImage, srcLeft, srcTop, srcWidth, srcHeight, alpha, blendShall blit with four-point quad transformation.
Suika.makeColor()r, g, b, aIntegerShall return a packed RGBA color value.
Suika.fillImageRect()image, left, top, width, height, colorShall fill a rectangle with the given packed color.

Blend type constants: Suika.BLEND_COPY, Suika.BLEND_ALPHA, Suika.BLEND_ADD, Suika.BLEND_SUB, Suika.BLEND_DIM, Suika.BLEND_GLYPH, Suika.BLEND_EMOJI.

B.6 Stage

FunctionParametersReturnsRequirement
Suika.reloadStageImages()BooleanShall reload all stage layer images from config.
Suika.reloadStagePositions()Shall reload stage layer positions from config.
Suika.getLayerX()layer (Integer)IntegerShall return the layer's current X position.
Suika.getLayerY()layer (Integer)IntegerShall return the layer's current Y position.
Suika.setLayerPosition()layer, x, yShall set the layer's X and Y position.
Suika.getLayerScaleX()layerFloatShall return the layer's X scaling factor.
Suika.getLayerScaleY()layerFloatShall return the layer's Y scaling factor.
Suika.setLayerScale()layer, scale_x, scale_yShall set the layer's scaling factors.
Suika.getLayerRotate()layerFloatShall return the layer's rotation in radians.
Suika.setLayerRotate()layer, rotShall set the layer's rotation in radians.
Suika.getLayerDim()layerBooleanShall return the layer's dim state.
Suika.setLayerDim()layer, dimShall set the layer's dim state.
Suika.getLayerAlpha()layerIntegerShall return the layer's alpha (0–255).
Suika.setLayerAlpha()layer, alphaShall set the layer's alpha value.
Suika.setLayerBlend()layer, blendShall set the layer's blend mode.
Suika.setLayerFileName()layer, file_nameBooleanShall load an image file onto the specified layer.
Suika.setLayerFrame()layer, frameShall set the animation frame index for eye/lip sub-layers.
Suika.getLayerText()indexStringShall return the text content of a text layer.
Suika.setLayerText()index, textShall set the text content of a text layer.
Suika.getLayerImage()layerObjectShall return the image object currently on the layer.
Suika.setLayerImage()layer, imageShall assign an image object to the layer.
Suika.clearStageBasic()Shall clear all basic (non-UI) stage layers.
Suika.clearStage()Shall reset all stage layers to their initial state.
Suika.chposToLayer()chposIntegerShall return the layer index for a character position.
Suika.chposToEyeLayer()chposIntegerShall return the eye sub-layer index for a character position.
Suika.chposToLipLayer()chposIntegerShall return the lip sub-layer index for a character position.
Suika.layerToChpos()layerIntegerShall return the character position for a layer index.
Suika.renderStage()Shall composite all stage layers to the screen.
Suika.drawStageToThumb()Shall render the stage to the thumbnail image buffer.
Suika.getThumbImage()ObjectShall return the thumbnail image object.
Suika.getFadeMethod()IntegerShall return the current fade method constant.
Suika.getAccelMethod()IntegerShall return the current acceleration method constant.
Suika.startFade()desc, method, time, ruleImageBooleanShall begin a transition effect.
Suika.isFadeRunning()BooleanShall return true while a transition is in progress.
Suika.finishFade()Shall immediately complete the current transition.
Suika.getShakeOffset()DictShall return {x, y} shake offsets.
Suika.setShakeOffset()x, yShall set the screen shake offset.
Suika.setChNameMapping()chpos, chNameIndexShall map a character position to a character name index.
Suika.setChTalking()chposShall set the currently speaking character position.
Suika.getTalkingChpos()IntegerShall return the position of the speaking character.
Suika.updateChDimByTalkingCh()Shall auto-dim non-speaking characters.
Suika.forceChDim()chpos, dimShall force a specific character's dim state.
Suika.getChDim()chposBooleanShall return a character's current dim state.
Suika.fillMessageBox()Shall fill the message box with its background image.
Suika.showMessageBox()show (Boolean)Shall show or hide the message box.
Suika.getMessageBoxRect()DictShall return {x, y, w, h} of the message box.
Suika.fillNameBox()Shall fill the name box with its background image.
Suika.showNameBox()show (Boolean)Shall show or hide the name box.
Suika.getNameBoxRect()DictShall return {x, y, w, h} of the name box.
Suika.showChoosebox()index, showIdle, showHoverShall show or hide a choice box at the given index (0–7).
Suika.fillChooseBoxIdleImage()indexShall fill the idle-state layer of the given choose box.
Suika.fillChooseBoxHoverImage()indexShall fill the hover-state layer of the given choose box.
Suika.getChooseBoxRect()DictShall return {x, y, w, h} of a choice box.
Suika.showAutoModeBanner()show (Boolean)Shall show or hide the auto mode indicator.
Suika.showSkipModeBanner()show (Boolean)Shall show or hide the skip mode indicator.
Suika.renderImage()dstLeft, dstTop, image, srcLeft, srcTop, srcWidth, srcHeight, alpha, blendShall render an image directly to the screen.
Suika.renderImage3d()x1–x4, y1–y4, tex, srcLeft, srcTop, srcWidth, srcHeight, alphaShall render a quad-transformed image to the screen.
Suika.setClickPosition()x, yShall set the position of the click animation.
Suika.showClick()show (Boolean)Shall show or hide the click animation.
Suika.setClickIndex()indexShall set the click animation frame index.
Suika.getClickRect()DictShall return {x, y, w, h} of the click animation area.
Suika.getSysBtnIdleImage()ObjectShall return the system button idle image.
Suika.getSysBtnHoverImage()ObjectShall return the system button hover image.
Suika.startKirakira()Shall initialize the Kirakira sparkle effect.
Suika.renderKirakira()Shall render one frame of the Kirakira effect.

B.7 Audio Mixer

FunctionParametersReturnsRequirement
Suika.setMixerInputFile()track, file, isLoopedBooleanShall play the audio file on the named track (bgm, se, voice, sys).
Suika.setMixerVolume()track, vol, spanShall set the track volume with optional fade duration.
Suika.getMixerVolume()trackFloatShall return the current track volume.
Suika.setMasterVolume()volumeShall set the master volume affecting all tracks.
Suika.getMasterVolume()FloatShall return the current master volume.
Suika.setMixerGlobalVolume()track, volShall set the persistent global volume for a track.
Suika.getMixerGlobalVolume()trackFloatShall return the persistent global volume for a track.
Suika.setCharacterVolume()index, volShall set the voice volume for a character by name index.
Suika.getCharacterVolume()ch_indexFloatShall return the voice volume for a character.
Suika.isMixerSoundFinished()track (Integer index)BooleanShall return true when playback on the track has ended.
Suika.getTrackFileName()track (Integer index)StringShall return the filename currently loaded on the track.
Suika.applyCharacterVolume()ch (Integer)Shall apply the character's volume setting to the voice track.

B.8 System Button

FunctionParametersReturnsRequirement
Suika.enableSysBtn()isEnabled (Boolean)Shall enable or disable the system button.
Suika.isSysBtnEnabled()BooleanShall return the system button enable state.
Suika.updateSysBtnState()Shall update mouse-over tracking for the system button.
Suika.isSysBtnPointed()BooleanShall return true when the cursor is over the system button.
Suika.isSysBtnClicked()BooleanShall return true when the system button was clicked.

B.9 Text Rendering

FunctionParametersReturnsRequirement
Suika.drawTextOnLayer()layer, fontType, fontSize, color, outlineWidth, outlineColor, lineMargin, charMargin, x, y, width, height, textShall render text onto the specified stage layer.
Suika.getStringWidth()fontType, fontSize, textIntegerShall return the pixel width of the rendered string.
Suika.getStringHeight()fontType, fontSize, textIntegerShall return the pixel height of the rendered string.
Suika.drawGlyph()img, font_type, font_size, base_font_size, outline_size, x, y, color, outline_color, codepoint, is_dimBooleanShall draw a single UTF-32 glyph onto an image.
Suika.createDrawMsg()(see §B.9 detail)ObjectShall create a complex text rendering context.
Suika.destroyDrawMsg()contextShall free the text rendering context.
Suika.countDrawMsgChars()contextIntegerShall return the remaining character count (excluding escapes).
Suika.drawMessage()context, maxCharsIntegerShall render up to maxChars characters; returns count drawn.
Suika.getDrawMsgPenPosition()contextDictShall return the current pen {x, y} from the drawing context.
Suika.isEscapeSequenceChar()cBooleanShall return true if the character is part of an inline escape sequence.
Suika.expandStringWithVariable()(variable name/string)StringShall expand ${varname} references in the input string.

Suika.createDrawMsg() accepts parameters: image, text, fontType, fontSize, baseFontSize, rubySize, outlineSize, penX, penY, areaWidth, areaHeight, leftMargin, rightMargin, topMargin, bottomMargin, lineMargin, charMargin, color, outlineColor, bgColor, fillBg, dim, ignoreLF, ignoreFont, ignoreOutline, ignoreColor, ignoreSize, ignorePosition, ignoreRuby, ignoreWait, inlineWaitHook, tategaki.

B.10 Tag Execution

FunctionParametersReturnsRequirement
Suika.moveToTagFile()fileBooleanShall load and begin executing the specified script file.
Suika.getTagCount()IntegerShall return the total number of tags in the current file.
Suika.moveToTagIndex()indexBooleanShall move the execution pointer to the given tag index.
Suika.moveToNextTag()BooleanShall advance the execution pointer to the next tag.
Suika.moveToLabelTag()nameBooleanShall jump to the named label tag.
Suika.moveToMacroTag()nameBooleanShall jump to the named macro definition.
Suika.moveToElseTag()BooleanShall jump to the corresponding [else], [elseif], or [endif].
Suika.moveToEndIfTag()BooleanShall jump to the corresponding [endif].
Suika.moveToEndMacroTag()BooleanShall jump to the corresponding [endmacro].
Suika.getTagFileName()StringShall return the current script file name.
Suika.getTagIndex()IntegerShall return the current tag index.
Suika.getTagLine()IntegerShall return the source line number of the current tag.
Suika.getTagBlockDepth()IntegerShall return the nesting block depth of the current tag.
Suika.getTagName()StringShall return the tag name of the current tag.
Suika.getTagPropertyCount()IntegerShall return the number of properties on the current tag.
Suika.getTagPropertyName()indexStringShall return the property name at the given index.
Suika.getTagPropertyValue()indexStringShall return the property value at the given index.
Suika.getTagArgBool()name, omissible, defValBooleanShall return a boolean tag argument with optional default.
Suika.getTagArgInt()name, omissible, defValIntegerShall return an integer tag argument with optional default.
Suika.getTagArgFloat()name, omissible, defValFloatShall return a float tag argument with optional default.
Suika.getTagArgString()name, omissible, defValStringShall return a string tag argument with optional default.
Suika.evaluateTag()BooleanShall expand ${varname} references in all current tag property values.
Suika.pushTagStackIf()Shall push the current position onto the if-block stack.
Suika.popTagStackIf()Shall pop the if-block stack.
Suika.pushTagStackWhile()BooleanShall push the current position onto the while-loop stack.
Suika.popTagStackWhile()BooleanShall pop the while-loop stack.
Suika.pushTagStackFor()BooleanShall push the current position onto the for-loop stack.
Suika.popTagStackFor()BooleanShall pop the for-loop stack.

B.11 Animation

FunctionParametersReturnsRequirement
Suika.loadAnimeFromFile()file, reg_nameArrayShall load an animation file and register it under reg_name. Returns boolean array per layer.
Suika.newAnimeSequence()layerBooleanShall begin describing a new programmatic animation sequence for the layer.
Suika.addAnimeSequencePropertyF()key, val (Float)BooleanShall add a float target property to the current sequence.
Suika.addAnimeSequencePropertyI()key, val (Integer)BooleanShall add an integer target property to the current sequence.
Suika.startLayerAnime()layerBooleanShall start the registered sequence on the given layer.
Suika.isAnimeRunning()BooleanShall return true if any layer animation is active.
Suika.isAnimeFinishedForLayer()layerBooleanShall return true when the given layer's animation has completed.
Suika.updateAnimeFrame()Shall advance all active animation sequences by one frame.
Suika.loadEyeImageIfExists()chpos, fileBooleanShall load an eye-patch image for the given character position.
Suika.reloadEyeAnime()chposBooleanShall restart the eye-blink animation for the given character.
Suika.runLipAnime()chpos, textShall start lip-sync animation synchronized to the message text.
Suika.stopLipAnime()chposShall stop lip-sync animation for the given character.
Suika.clearLayerAnimeSequence()layerShall clear all animation sequences for the given layer.
Suika.clearAllAnimeSequence()Shall clear animation sequences for all layers.

B.12 Variables

FunctionParametersReturnsRequirement
Suika.setVariableInt()name, valueBooleanShall set the named variable to an integer value.
Suika.setVariableFloat()name, valueBooleanShall set the named variable to a float value.
Suika.setVariableString()name, valueBooleanShall set the named variable to a string value.
Suika.getVariableInt()nameIntegerShall return the variable value as an integer.
Suika.getVariableFloat()nameFloatShall return the variable value as a float.
Suika.getVariableString()nameStringShall return the variable value as a string.
Suika.unsetVariable()nameShall delete the named variable.
Suika.unsetLocalVariables()Shall delete all local (non-global) variables.
Suika.makeVariableGlobal()name, is_globalBooleanShall set or unset the global flag for the named variable.
Suika.isGlobalVariable()nameBooleanShall return true if the variable is marked global.
Suika.getVariableCount()IntegerShall return the total number of variables currently set.
Suika.getVariableName()indexStringShall return the variable name at the given index.
Suika.checkVariableExists()nameBooleanShall return true if the named variable exists.

B.13 Save and Load

FunctionParametersReturnsRequirement
Suika.executeSaveGlobal()BooleanShall persist all global-classified config and variable values.
Suika.executeLoadGlobal()BooleanShall restore global-classified values from the global save file.
Suika.executeSaveLocal()indexBooleanShall save the full game state (script position, local variables, stage, audio) to the specified slot.
Suika.executeLoadLocal()indexBooleanShall restore game state from the specified slot.
Suika.checkSaveExists()indexBooleanShall return true if a save slot contains data.
Suika.deleteLocalSave()indexShall delete the specified save slot.
Suika.deleteGlobalSave()Shall delete the global save file.
Suika.checkRightAfterLoad()BooleanShall return true for the single frame immediately after a load operation.
Suika.getSaveTimestamp()indexIntegerShall return the Unix timestamp of when the slot was saved.
Suika.getLatestSaveIndex()IntegerShall return the index of the most recently modified slot.
Suika.getSaveChapterName()indexStringShall return the chapter name stored in the slot.
Suika.getSaveLastMessage()indexStringShall return the last message stored in the slot.
Suika.getSaveThumbnail()indexObjectShall return the thumbnail image object for the slot.
Suika.writeSaveData()key, dataBooleanShall write raw string data to a key-based save entry.
Suika.readSaveData()keyStringShall read raw string data from a key-based save entry.

B.14 History (Backlog)

FunctionParametersReturnsRequirement
Suika.clearHistory()Shall remove all backlog entries.
Suika.addHistory()name, msg, voice, bodyColor, bodyOutlineColor, nameColor, nameOutlineColorBooleanShall append an entry to the backlog.
Suika.getHistoryCount()IntegerShall return the total backlog entry count.
Suika.getHistoryName()indexStringShall return the speaker name at the given index.
Suika.getHistoryMessage()indexStringShall return the message text at the given index.
Suika.getHistoryVoice()indexStringShall return the voice file path at the given index.

B.15 Seen Flags

FunctionParametersReturnsRequirement
Suika.loadSeen()BooleanShall load the seen-flags file for the current script.
Suika.saveSeen()BooleanShall persist the seen-flags file for the current script.
Suika.getSeenFlags()IntegerShall return the seen-flag bitmask for the current tag.
Suika.setSeenFlags()flagShall write the seen-flag bitmask for the current tag.

For a [text] tag, bit 0 shall be 1 if the message has been read before. For a [choose] tag, each bit shall indicate that the corresponding option has been selected in a prior playthrough.

B.16 GUI

FunctionParametersReturnsRequirement
Suika.loadGUIFile()file, isSysBooleanShall load a GUI definition file. isSys=true marks it as a system GUI that returns to the interrupted tag.
Suika.startGUI()Shall begin execution of the loaded GUI.
Suika.stopGUI()Shall terminate the active GUI.
Suika.isGUIRunning()BooleanShall return true while a GUI is active.
Suika.isGUIFinished()BooleanShall return true when the GUI has completed.
Suika.getGUIResultLabel()StringShall return the label string of the button that closed the GUI.
Suika.isGUIResultTitle()BooleanShall return true if the GUI was dismissed via a title-return action.
Suika.checkIfSavedInGUI()BooleanShall return true if a save was performed during GUI.
Suika.checkIfLoadedInGUI()BooleanShall return true if a load was performed during GUI.
Suika.checkRightAfterSysGUI()BooleanShall return true for the frame immediately after a system GUI exits.

B.17 HAL and System

FunctionParametersReturnsRequirement
Suika.getMillisec()IntegerShall return elapsed milliseconds since the time origin.
Suika.checkFileExists()fileBooleanShall return true if the file exists in the asset path.
Suika.readFileContent()fileStringShall return the full UTF-8 text content of the file.
Suika.playVideo()file, is_skippableBooleanShall start video playback.
Suika.stopVideo()Shall stop video playback.
Suika.isVideoPlaying()BooleanShall return true while a video is playing.
Suika.isFullScreenSupported()BooleanShall return true on platforms that support full screen.
Suika.enterFullScreenMode()Shall transition the window to full-screen mode.
Suika.logInfo()msgShall write an informational message to the debug log.
Suika.logWarn()msgShall write a warning message to the debug log.
Suika.logError()msgShall write an error message to the debug log.
Suika.speakText()msgShall invoke the platform TTS engine with the given text.
Suika.getVmInt()(implementation-defined)IntegerShall read an integer value from the NoctLang VM state.
Suika.setVmInt()(implementation-defined)Shall write an integer value to the NoctLang VM state.
Suika.callVmFunction()(implementation-defined)Shall invoke a function by name within the NoctLang VM.

B.18 Constants

The implementation shall expose the following constant groups as read-only properties of the Suika namespace:

  • Paths: Suika.PATH_START_TAG, Suika.PATH_CONFIG, Suika.PATH_SYSMENU_GUI, Suika.PATH_SAVE_GUI, Suika.PATH_LOAD_GUI, Suika.PATH_HISTORY_GUI, Suika.PATH_CONFIG_GUI
  • Layer indices: Suika.LAYER_BG, Suika.LAYER_BG_FO, Suika.LAYER_BG2, Suika.LAYER_EFB1EFB4, Suika.LAYER_CHB, Suika.LAYER_CHL, Suika.LAYER_CHLC, Suika.LAYER_CHR, Suika.LAYER_CHRC, Suika.LAYER_CHC, Suika.LAYER_EFF1EFF4, Suika.LAYER_CHF, and their _EYE, _LIP, _FO variants; Suika.LAYER_MSGBOX, Suika.LAYER_NAMEBOX, Suika.LAYER_CHOOSE1_IDLECHOOSE8_HOVER, Suika.LAYER_CLICK, Suika.LAYER_AUTO, Suika.LAYER_SKIP, Suika.LAYER_TEXT1TEXT8, Suika.LAYER_GUI_BTN1GUI_BTN32
  • Layer counts: Suika.STAGE_LAYERS, Suika.TEXT_LAYERS, Suika.EFFECT_LAYERS, Suika.BUTTON_LAYERS, Suika.CHOOSEBOX_COUNT, Suika.CLICK_FRAMES, Suika.CH_BASIC_LAYERS, Suika.CH_ALL_LAYERS, Suika.KIRAKIRA_FRAMES
  • Character positions: Suika.CH_BACK, Suika.CH_LEFT, Suika.CH_LEFT_CENTER, Suika.CH_RIGHT, Suika.CH_RIGHT_CENTER, Suika.CH_CENTER, Suika.CH_FACE
  • Fade methods: Suika.FADE_INVALID, Suika.FADE_NORMAL, Suika.FADE_RULE, Suika.FADE_MELT
  • Blend types: Suika.BLEND_COPY, Suika.BLEND_ALPHA, Suika.BLEND_ADD, Suika.BLEND_SUB, Suika.BLEND_DIM, Suika.BLEND_GLYPH, Suika.BLEND_EMOJI
  • Fade descriptors: Suika.FADE_DESC_BG, Suika.FADE_DESC_CHB, Suika.FADE_DESC_CHL, Suika.FADE_DESC_CHLC, Suika.FADE_DESC_CHR, Suika.FADE_DESC_CHRC, Suika.FADE_DESC_CHC, Suika.FADE_DESC_CHF, Suika.FADE_DESC_COUNT
  • Mixer tracks: Suika.MIXER_TRACKS, Suika.TRACK_BGM, Suika.TRACK_VOICE, Suika.TRACK_SE, Suika.TRACK_SYS
  • Animation: Suika.REG_ANIME_COUNT, Suika.ANIME_ACCEL_INVALID, Suika.ANIME_ACCEL_UNIFORM, Suika.ANIME_ACCEL_ACCEL, Suika.ANIME_ACCEL_DEACCEL, Suika.ANIME_ACCEL_SMOOTHSTEP, Suika.ANIME_ACCEL_INVSMOOTHSTEP
  • Font: Suika.FONT_SELECT1FONT_SELECT4, Suika.FONT_COUNT, Suika.EMOJI_COUNT
  • Call stack: Suika.CALL_STACK_MAX, Suika.CALL_ARGS
  • Character voice: Suika.CH_VOL_SLOTS, Suika.CH_VOL_SLOT_DEFAULT
  • Character map: Suika.CHARACTER_MAP_COUNT
  • Save: Suika.ALL_SAVE_SLOTS, Suika.NORMAL_SAVE_SLOTS, Suika.QUICK_SAVE_INDEX

Appendix C: Ray 2D API Requirements (Engine.*)

This appendix specifies the requirements for all Engine.* API functions and properties exposed to Ray scripts by the Playfield Engine layer. These APIs are available in both the VN Engine context (via main.ray) and in standalone Playfield-layer applications.

The main.ray script shall implement four lifecycle entry points that the Playfield Engine shall call in the following order and frequency:

FunctionCalled when
setup()Once, before the window is created. Shall return a dictionary with width, height, and title.
start()Once, after the window is created and rendering is ready.
update()Every frame, before rendering.
render()Every frame, after update().

C.1 Debug

FunctionParametersRequirement
print()value (direct)Shall print a string or dump an object to the debug log. Takes a single non-dictionary argument.

C.2 Time

Property / FunctionReturnsRequirement
Engine.millisecIntegerShall return elapsed milliseconds since the application started. Read-only property.
Engine.getDate()DictShall return {year, month, day, hour, minute, second} reflecting the current local wall-clock time.

C.3 Input Properties

All input properties are read-only Boolean or Integer values refreshed each frame.

Mouse:

PropertyTypeRequirement
Engine.mousePosXIntegerShall return the mouse cursor X position.
Engine.mousePosYIntegerShall return the mouse cursor Y position.
Engine.isMouseLeftPressedBooleanShall be true while the left mouse button is held.
Engine.isMouseRightPressedBooleanShall be true while the right mouse button is held.

Keyboard — the following Boolean properties shall each be true while the corresponding key is held: Engine.isEscapeKeyPressed, Engine.isReturnKeyPressed, Engine.isSpaceKeyPressed, Engine.isTabKeyPressed, Engine.isBackspaceKeyPressed, Engine.isDeleteKeyPressed, Engine.isHomeKeyPressed, Engine.isEndKeyPressed, Engine.isPageupKeyPressed, Engine.isPagedownKeyPressed, Engine.isShiftKeyPressed, Engine.isControlKeyPressed, Engine.isAltKeyPressed, Engine.isUpKeyPressed, Engine.isDownKeyPressed, Engine.isRightKeyPressed, Engine.isLeftKeyPressed, Engine.isAKeyPressedEngine.isZKeyPressed (A–Z), Engine.is0KeyPressedEngine.is9KeyPressed (0–9), Engine.isF1KeyPressedEngine.isF12KeyPressed (F1–F12).

Gamepad:

PropertyTypeRequirement
Engine.isGamepadUpPressedBooleanShall be true while the gamepad D-pad Up is pressed.
Engine.isGamepadDownPressedBooleanShall be true while the gamepad D-pad Down is pressed.
Engine.isGamepadLeftPressedBooleanShall be true while the gamepad D-pad Left is pressed.
Engine.isGamepadRightPressedBooleanShall be true while the gamepad D-pad Right is pressed.
Engine.isGamepadAPressedBooleanShall be true while gamepad button A is pressed.
Engine.isGamepadBPressedBooleanShall be true while gamepad button B is pressed.
Engine.isGamepadXPressedBooleanShall be true while gamepad button X is pressed.
Engine.isGamepadYPressedBooleanShall be true while gamepad button Y is pressed.
Engine.isGamepadLPressedBooleanShall be true while the gamepad L shoulder is pressed.
Engine.isGamepadRPressedBooleanShall be true while the gamepad R shoulder is pressed.
Engine.gamepadAnalogX1IntegerShall return left analog stick X in range −32768 to 32767.
Engine.gamepadAnalogY1IntegerShall return left analog stick Y in range −32768 to 32767.
Engine.gamepadAnalogX2IntegerShall return right analog stick X in range −32768 to 32767.
Engine.gamepadAnalogY2IntegerShall return right analog stick Y in range −32768 to 32767.
Engine.gamepadAnalogLIntegerShall return the L trigger in range −32768 to 32767.
Engine.gamepadAnalogRIntegerShall return the R trigger in range −32768 to 32767.

C.4 Rendering

FunctionParametersReturnsRequirement
Engine.createColorTexture()width, height, r, g, b, aObjectShall create and return a solid-color RGBA texture.
Engine.loadTexture()fileObjectShall load a texture from the asset path. The returned object shall expose .width and .height integer properties.
Engine.destroyTexture()textureShall free the texture and its GPU resources.
Engine.renderTexture()dstLeft, dstTop, dstWidth, dstHeight, texture, srcLeft, srcTop, srcWidth, srcHeight, alphaShall render a source rectangle of a texture to a destination rectangle on screen.
Engine.draw()texture, x, yShall render the entire texture at the given screen position. Convenience wrapper around Engine.renderTexture().
Engine.renderTexture3D()x1, y1, x2, y2, x3, y3, x4, y4, texture, srcLeft, srcTop, srcWidth, srcHeight, alphaShall render a texture mapped to a four-point quad on screen.
Engine.loadFont()slot, fileShall load a TrueType font file into the specified font slot (0–3).
Engine.createTextTexture()slot, text, size, r, g, b, aObjectShall create and return a texture with the rendered text string.

C.5 Sound

FunctionParametersReturnsRequirement
Engine.playSound()stream, fileShall start one-shot playback of an audio file on the given track (0–3).
Engine.playSoundLoop()stream, fileShall start looping playback of an audio file on the given track.
Engine.stopSound()streamShall stop playback on the given track.
Engine.setSoundVolume()stream, volumeShall set the volume on the given track (0.0–1.0). Pass stream=-1 to set master volume.

C.6 Save Data

FunctionParametersReturnsRequirement
Engine.writeSaveData()key, valueShall persist the value (integer, float, array, or dictionary) associated with key. Shall fail if the data exceeds the platform-defined size limit.
Engine.readSaveData()keyObjectShall return the value associated with key. Shall fail if the key does not exist.
Engine.checkSaveData()keyBooleanShall return true if a saved entry for key exists.