Jump to content

Grimwar/VSig

From Vellocet Developer Community
Version target. This guide covers the VSig compiler and standard library included with Vellocet SDK 1.0.3.

VSig is Grimwar's map-logic language. A VSig program listens to entity or game outputs, changes state, calls entity inputs, schedules work, and exchanges bounded signals with VMods. The Map Exporter validates and compiles it into the map package.

Use VSig for map orchestration. Use an entity marker when the thing has a position, volume, renderer, collider, or native game behavior. Use VMod for server rules that need players, commands, persistent storage, permissions, or game-mode control.

Create the map script

A map scene and its VSig source share a name and folder:

Assets/CustomContent/Maps/Range/Range.unity
Assets/CustomContent/Maps/Range/Range.vsig

Create a script with Assets > Create > Vellocet > SDK > VSig Script. A marker Inspector can also create or open the current scene's file and insert refs for selected entities.

Install syntax support from SDK Workbench > Project > VSig Editor Support. Unity still performs the authoritative parse and validation.

First working script

scene "Example"

ref trigger: trigger_multiple match Triggers/Entrance
ref door: func_door match Doors/MainDoor

var door_start: DoorEntityState = null

once on map.ready:
    door_start = door.state

on trigger.start_touch:
    door.open

on trigger.end_touch_all:
    door.restore(door_start)

The two ref declarations bind authored markers to short aliases. map.ready runs after server-side map entities are bound. The trigger's start_touch output calls the door's open input. The script captures the starting door state once and restores it after the last toucher leaves.

Marker Inspectors list the inputs, outputs, state type, and access rules for that entity class. Use that contract instead of guessing a signal name.

Refs and groups

A ref can match one hierarchy path or a wildcard:

ref main_door: func_door match BuildingA/Door
ref spawn_doors: func_door match SpawnDoors/Door*

group exits:
    main_door
    spawn_doors

on round.post_round:
    exits.close

An input sent to a wildcard ref or group broadcasts to every match. Property reads and state capture require exactly one entity.

Stable GameObject names make refs readable and keep wildcard matches predictable. A marker's hidden runtime ID is managed by the SDK; VSig authoring uses the object hierarchy and entity class.

Values and expressions

Primitive types are bool, int, float, and string. Entity state uses the state type declared by its marker contract. Durations accept seconds or milliseconds, including 1.5s and 250ms.

seed 1847

const max_score: int = 10
var score: int = 0
var title: string = "Arena"

on button.pressed:
    score = clamp(score + 1, 0, max_score)
    label.set_text("{0}: {1}/{2}", title, score, max_score)

Strings must be quoted. Declare and initialize map variables and module state before use. Handler parameters and procedure parameters are immutable; assign mutable data to a map var or module state.

Operators include arithmetic, comparisons, and, or, and not. Built-in functions include:

  • min, max, clamp, abs, floor, ceil, and round
  • lerp and remap
  • random, random_int, shuffle, and choose
  • time, format, and convar
  • bool, int, float, and string conversions

Without seed, each map runtime starts with fresh random entropy. A declared seed makes random sequences repeatable. shuffle(self, count) gives each module instance its own shuffle bag.

Conditions and matching

on counter.changed(value: float):
    if value >= 10:
        door.open
    elif value > 0:
        light.on
    else:
        light.off

    match int(value):
        case 1:
            label.set_text("one")
        case 2:
            label.set_text("two")
        default:
            label.set_text("many")

Indentation defines the statement block. A type mismatch, unknown name, invalid call, missing ref, or wrong handler payload is a compile error.

Procedures and schedules

def announce(message: string, delay: float = 0s):
    wait delay
    label.set_text(message)

on round.live:
    call announce("Fight!", 0.25s)
    schedule heartbeat every 1s times 10:
        light.toggle

on round.post_round:
    cancel heartbeat

wait suspends the current handler or procedure. schedule name after duration runs once. schedule name every duration repeats, and times limits the count. Starting a named schedule replaces the existing task with that name. Cancelling a task that is not running is safe.

Cancel round-owned repeating work on round.post_round or another lifecycle event that always closes the activity.

Reusable modules

A module contains parameters, private state, inputs, outputs, procedures, and handlers:

module counter(start: float = 0, max_value: float = 10):
    state value: float = start
    output changed(value: float)
    output reached(value: float)

    input add(amount: float = 1):
        value = min(value + amount, max_value)
        emit changed(value)
        if value >= max_value:
            emit reached(value)

    input reset:
        value = start
        emit changed(value)

use counter as captures(max_value = 3)

ref capture_point: game_capture_point
ref exit: func_door

on capture_point.captured:
    captures.add

on captures.reached(value: float):
    exit.open

Module parameters are immutable configuration. State is private mutable data and can be inspected as instance.state_name. Inputs are typed procedures. Outputs carry typed payloads and work like entity outputs.

Put shared modules in .vsiglib files and import only the required module:

from "Packages/com.vellocet.sdk/Runtime/VSig/Libraries/Logic.vsiglib" use counter
use counter as captures(max_value = 5)

The standard logic library contains counter, accumulator, relay, timer, selector, random_selector, sequence, latch, toggle, state_machine, debounce, and cooldown.

These modules are compiled logic. They do not create GameObjects or network objects.

Spatial modules and logic_script

A spatial module declares local refs and binds each instance to a scene hierarchy:

module gate:
    ref trigger: trigger_multiple
    ref door: func_door

    on trigger.start_touch:
        door.open

    input reset:
        door.close

use gate as exits match Gates/Exit_*

A wildcard use creates one module instance per matching root and a broadcast group named by the authored alias.

Place a logic_script marker when a designer should choose a module and its arguments in the scene Inspector. Select the .vsiglib, module name, alias, arguments, and local-ref binding mode. The compiler expands the marker into the same form as a source-authored use. It does not become a runtime entity.

Map and round lifecycle

Lifecycle sources need no scene marker:

once on map.ready:
    doors.close

on round.warmup:
    warmup_lights.enable

on round.pre_round:
    round_timer.reset

on round.live:
    round_timer.start

on round.overtime:
    overtime_alarm.play

on round.post_round:
    cancel heartbeat

on round.intermission:
    doors.open

Available lifecycle sources are map.ready, round.warmup, round.pre_round, round.live, round.overtime, round.post_round, and round.intermission.

Use once on map.ready to capture an authored entity's initial state or perform setup that must run once per map load.

Safe server commands

server_command() sends one command through the server's content-accessible command boundary:

once on map.ready:
    server_command("mp_roundtime 3")
    server_command("sv_gravity -6")
    server_command("say Map rules loaded")

on door.open:
    server_command("gm_message major DOOR HAS BEEN OPENED")

The server allows a curated set of match timing, chat or HUD messages, round completion, friendly fire, movement tuning, class enforcement, and weapon-slot rules. It rejects semicolons, newlines, secret-like identifiers, and commands without the ContentAccessible flag. Any changed console variable is restored when the map unloads.

Server operators can disable map commands with sv_allow_content_commands 0. convar() reads use the same allowlist.

Signals shared with VMod

VMods can emit a source under mod:<mod-id>. Handle that signal like any other output:

on mod:community.hello.round_started(mode_id: string):
    status_label.set_text("Mode: {0}", mode_id)

on mod:community.hello.stage.opened(index: int, gate_name: string):
    gate_counter.set(index)

The first handler receives output round_started from source mod:community.hello. The second receives output opened from child source mod:community.hello.stage.

A VMod can subscribe to a map entity or module output by source alias and output name. This exchange carries scalar values only. It does not expose scene objects to VMod code.

Compile and debug

Press Play for quick testing. The compiled VSig runtime Inspector shows validation state, live module state, scheduled tasks, and a bounded recent-event trace. Marker Inspectors can show signal routes and add selected refs or a group to the paired script.

Before export:

  1. Save the scene and VSig file.
  2. Resolve parser, type, ref, module, and entity-contract diagnostics.
  3. Check wildcard refs against the final hierarchy.
  4. Exercise each round-phase handler more than once.
  5. Confirm repeating schedules stop at the intended lifecycle boundary.
  6. Run Compile Scene in the Map Exporter before building the addon package.

The exporter expands imported modules, validates the final program, and embeds a compact compiled program in the map. A Release export refuses incomplete required bakes or invalid VSig.