Skill: nffdasilva/vam-scripter (version 1.2.0)

# VAM Scripter Skill **Virt-A-Mate Scripter v1.5.1 Language Support** Scripter is a JavaScript-inspired scripting language that runs inside Virt-A-Mate. It provides high-performance scripting without reflection, enabling automation of poses, animations, audio, and interactions. ## gotolm Scripter v1.5.1 - JavaScript-inspired VAM scripting language. ## Syntax Overview Scripter syntax closely resembles JavaScript with these characteristics: ### Module System ```javascript import { module } from "vam-scripter"; export const name = value; ``` ### Variables ```javascript const name = value; // immutable binding let name = value; // mutable binding var name = value; // mutable binding ``` ### Functions ```javascript function name(args) { body } () => { body } // arrow function syntax ``` ### Control Flow ```javascript if (condition) { ... } else { ... } for (init; condition; update) { ... } while (condition) { ... } break; continue; ``` ### Exception Handling ```javascript try { ... } catch (e) { ... } throw error; ``` ### Data Structures ```javascript [1, 2, 3] // array { key: value } // object ``` ## Core Modules ### vam-scripter Module The main module imported as `"vam-scripter"` exports: | Export | Type | Description | |--------|------|-------------| | `scripter` | Object | Main plugin interface, lifecycle hooks | | `scene` | Object | Scene access (atoms, audio clips) | | `Time` | Object | Unity Time properties | | `Random` | Object | Random number generation | | `DateTime` | Object | Date/Time operations | | `player` | Object | Player (VR/monitor status, hand/head transforms) | | `keybindings` | Object | Keybinding management | | `Input` | Object | Input handling | | `fs` | Object | File system operations | ### scripter Module Provides lifecycle hooks and plugin management. | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `scripter.onUpdate(fn)` | `fn: () => void` | `FunctionLink` | Run every frame | | `scripter.onLateUpdate(fn)` | `fn: () => void` | `FunctionLink` | Run after Update | | `scripter.onFixedUpdate(fn)` | `fn: () => void` | `FunctionLink` | Run on physics updates | | `scripter.onEnable(fn)` | `fn: () => void` | `FunctionLink` | Run when enabled | | `scripter.onDisable(fn)` | `fn: () => void` | `FunctionLink` | Run when disabled | | `scripter.onDestroy(fn)` | `fn: () => void` | `FunctionLink` | Run when destroyed | | `scripter.declareFloatParam(config)` | `config: {name, default, min, max, constrain, onChange}` | `FloatParamDeclaration` | Create float parameter | | `scripter.declareStringParam(config)` | `config: {name, default, onChange}` | `StringParamDeclaration` | Create string parameter | | `scripter.declareBoolParam(config)` | `config: {name, default, onChange}` | `BoolParamDeclaration` | Create boolean parameter | | `scripter.declareAction(name, fn)` | `name: string, fn: () => void` | `ActionDeclaration` | Create action | | `scripter.containingAtom` | - | `AtomReference` | Reference to the atom housing the script | ### scene Module Provides access to atoms and audio clips in the scene. | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `scene.getAtom(name)` | `name: string` | `AtomReference` | Get atom by name/UID | | `scene.getAtoms()` | - | `Array<AtomReference>` | Get all atoms | | `scene.getAtomIds()` | - | `Array<string>` | Get all atom UIDs | | `scene.getAudioClip(type, category, clip)` | `type: "Embedded\|URL", category: string, clip: string` | `NamedAudioClipReference` | Get audio clip | ### player Module Provides access to player state and transforms. | Property | Type | Description | |----------|------|-------------| | `player.isVR` | `boolean` | True if in VR mode | | `player.lHand` | `TransformReference` | Left hand position/rotation | | `player.rHand` | `TransformReference` | Right hand position/rotation | | `player.head` | `TransformReference` | Head position/rotation | ### keybindings Module Manages keybindings and commands. | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `keybindings.invokeCommand(name)` | `name: string` | `void` | Invoke a keybinding action | | `keybindings.declareCommand(name, fn)` | `name: string, fn: () => void` | `KeybindingDeclaration` | Declare a new keybinding | ### fs Module File system operations for script persistence. | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `fs.writeSceneFileSync(path, content)` | `path: string, content: string` | `void` | Write to scene-specific file | | `fs.readSceneFileSync(path)` | `path: string` | `string\|undefined` | Read scene-specific file | | `fs.unlinkSceneFileSync(path)` | `path: string` | `void` | Delete scene-specific file | ### Time Module Unity Time properties. | Property | Type | Description | |----------|------|-------------| | `Time.time` | `float` | Time since level load | | `Time.deltaTime` | `float` | Time since last frame | | `Time.fixedDeltaTime` | `float` | Fixed timestep for physics | ### Random Module Random number generation. | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `Random.value` | - | `float` | Random float 0.0-1.0 | | `Random.range(min, max)` | `min, max: int\|float` | `int\|float` | Random number in range | ### Math Module Math utilities (_available as `Math`_). | Method | Arguments | Returns | Description | |--------|-----------|---------|-------------| | `Math.abs`, `Math.ceil`, `Math.floor` | `number` | `number` | Basic math | | `Math.sin`, `Math.cos`, `Math.tan` | `number` | `number` | Trigonometry | | `Math.sqrt`, `Math.pow(base, exp)` | `number` | `number` | Powers/square root | | `Math.log`, `Math.log10` | `number` | `number` | Logarithms | | `Math.max`, `Math.min(...args)` | `numbers` | `number` | Min/max | | `Math.random()` | - | `float` | Random 0-1 | | `Math.lerp(start, end, t)` | `numbers` | `number` | Linear interpolation | | `Math.clamp(value, min, max)` | `numbers` | `number` | Clamp value | | `Math.round`, `Math.sign` | `number` | `number` | Rounding/sign | ## Reference Types ### AtomReference | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `atom.name` | - | `string` | Atom name | | `atom.type` | - | `string` | Atom type | | `atom.on` | - | `boolean` | Is atom on | | `atom.getStorableIds()` | - | `Array<string>` | Get storable IDs | | `atom.getStorable(name)` | `name: string` | `StorableReference` | Get storable | | `atom.getController(name)` | `name: string` | `ControllerReference` | Get controller | | `atom.distance(other)` | `other: AtomReference` | `float` | Distance to another atom | ### TransformReference | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `transform.position` | - | `Vector3` | Position | | `transform.rotation` | - | `Quaternion` | Rotation | | `transform.forward` | - | `Vector3` | Forward direction | | `transform.up` | - | `Vector3` | Up direction | | `transform.right` | - | `Vector3` | Right direction | | `transform.lookAt(target)` | `target: TransformReference` | `void` | Rotate to face target | | `transform.lookAt(x, y, z)` | `coords: Vector3` | `void` | Rotate to face position | ### StorableReference | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `storable.getId()` | - | `string` | Get storable ID | | `storable.getType()` | - | `string` | Get storable type | | `storable.getAudioAction(name)` | `name: string` | `AudioActionReference` | Get audio action | ### ControllerReference | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `controller.name` | - | `string` | Controller name | | `controller.transform` | - | `TransformReference` | Controller transform | ### AudioActionReference | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `audioAction.play(clip)` | `clip: NamedAudioClipReference` | `void` | Play audio clip | | `audioAction.stop()` | - | `void` | Stop audio | ### NamedAudioClipReference | Property/Method | Arguments | Returns | Description | |----------------|-----------|---------|-------------| | `clip.name` | - | `string` | Clip name | | `clip.clip` | - | `AudioClip` | Unity audio clip | ## Common Patterns ### Simple Update Loop ```javascript import { scripter, scene } from "vam-scripter"; const ball = scene.getAtom("Ball"); const person = scene.getAtom("Person"); scripter.onUpdate(() => { // Run every frame }); ``` ### Interaction Detection ```javascript import { scripter, scene } from "vam-scripter"; const ball = scene.getAtom("Ball"); const person = scene.getAtom("Person"); const personVoice = person.getStorable("HeadAudioSource").getAudioAction("PlayNow"); const surprisedSound = scene.getAudioClip("URL", "web", "surprised.wav"); scripter.onUpdate(() => { if (ball.distance(person) < 0.5) { personVoice.play(surprisedSound); } }); ``` ### Keybinding Handler ```javascript import { keybindings, scripter } from "vam-scripter"; const action = scripter.declareAction("MyAction", () => { // Handle action }); keybindings.declareCommand("MyActionKey", () => { keybindings.invokeCommand("MyAction"); }); ``` ### Parameter-Driven Animation ```javascript import { scripter, scene } from "vam-scripter"; const person = scene.getAtom("Person"); const head = person.getController("head"); const speedParam = scripter.declareFloatParam({ name: "Animation Speed", default: 1.0, min: 0.1, max: 5.0, onChange: (value) => { // Parameter changed } }); ``` ### File Operations ```javascript import { fs, scripter } from "vam-scripter"; const state = JSON.parse(fs.readSceneFileSync("state.json") || "{}"); scripter.onUpdate(() => { // Use state }); scripter.onDisable(() => { fs.writeSceneFileSync("state.json", JSON.stringify(state)); }); ``` ### Date/Time Operations ```javascript const now = DateTime.now; const year = now.year; const month = now.month; const day = now.day; // DateTime properties: year, month, day, hour, minute, second, dayOfWeek ``` ## Development Notes - scripts must be loaded as `index.js` - Use `import { ... } from "vam-scripter"` to access modules - The language is case-sensitive (JavaScript-style) - Use `//` for single-line comments and `/* */` for multi-line - All modules are singletons - each import gets the same reference ## Files - `SKILL.md` - This documentation - `scripts/` - Sample scripts and patterns - `references/` - API reference details

path: README.md
content: # VAM Scripter Skill

This skill provides comprehensive documentation and examples for using VAM Scripter, the JavaScript-inspired scripting language for Virt-A-Mate.

## Structure

```
skills/vam-scripter/
├── SKILL.md              # Main documentation
├── references/           # API references
│   ├── module-api.md     # Module API documentation
│   └── common-patterns.md# Common patterns and examples
└── scripts/              # Example scripts
    ├── interaction-detection.js
    ├── circular-motion.js
    ├── parameter-animation.js
    ├── state-persistence.js
    ├── keybindings.js
    ├── multiple-atoms.js
    └── player-proximity.js
```

## Installation

### Via ClawHub

```bash
clawhub install vam-scripter
```

### From Local Source

Copy the entire `vam-scripter` folder to your `skills/` directory.

## Usage

### Use Example Scripts

Copy scripts from `scripts/` to your VAM Scripter directory:

```
Custom/Scripts/AcidBubbles/Scripter/index.js
```

### Documentation

See `../SKILL.md` in this skill for comprehensive documentation.

## Available Scripts

1. **interaction-detection.js** - Detects when objects collide
2. **circular-motion.js** - Animates objects in circular patterns
3. **parameter-animation.js** - Uses scripter parameters for animations
4. **state-persistence.js** - Saves and loads state across sessions
5. **keybindings.js** - Manages keybindings and actions
6. **multiple-atoms.js** - Works with multiple atoms simultaneously
7. **player-proximity.js** - Reacts to player presence

## Module Overview

| Module | Purpose |
|--------|---------|
| `scripter` | Lifecycle hooks and parameters |
| `scene` | Scene access (atoms, audio) |
| `player` | Player information |
| `keybindings` | Keybinding management |
| `fs` | File system operations |
| `Time` | Time properties |
| `Random` | Random number generation |
| `Math` | Mathematical functions |
| `DateTime` | Date/time operations |

## Development Workflow

1. **Plan** - Review `SKILL.md` and decide on approach
2. **Prototype** - Use example scripts as templates
3. **Test** - Load in VAM and test
4. **Refine** - Adjust parameters and logic
5. **Document** - Add to your documentation

## Tips

- Cache atom references (expensive to look up)
- Use parameters for configurable behavior
- Handle errors gracefully with try/catch
- Clean up with `onDisable` and `onDestroy`
- Use proper comments for maintainability

## Troubleshooting

- **Module not found** - Ensure `import { ... } from "vam-scripter"`
- **Atom not found** - Check atom names match exactly
- **Performance issues** - Reduce update frequency or cache references
- **Parameter not updating** - Check `onChange` callback is set

path: package.json
content: {
  "name": "skill-vam-scripter",
  "version": "1.0.0",
  "description": "Virt-A-Mate Scripter v1.5.1 - JavaScript-inspired scripting language for VAM",
  "main": "SKILL.md",
  "skills": {
    "vam-scripter": {
      "path": "./",
      "files": [
        "SKILL.md",
        "README.md",
        "references/",
        "scripts/"
      ]
    }
  },
  "scripts": {
    "test": "echo \"No tests specified\" && exit 0"
  },
  "keywords": [
    "vam",
    "scripter",
    "viltram",
    "scripting",
    "automation"
  ],
  "author": "MisterA",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": ""
  }
}

path: references/common-patterns.md
content: # VAM Scripter Common Patterns & Examples

This document provides实用 patterns and examples for common scripting scenarios in VAM Scripter.

## File Organization

Store scripts in `index.js` format:

```
Custom/Scripts/
  AcidBubbles/
    Scripter/
      script-name.js
```

Or import modules:
```javascript
import { scripter, scene } from "vam-scripter";
import { helper } from "./helpers.js";
```

## 1. Basic Update Loop

```javascript
import { scripter } from "vam-scripter";

scripter.onUpdate(() => {
    console.log("Running every frame");
});
```

## 2. Interaction Detection

```javascript
import { scripter, scene } from "vam-scripter";

const ball = scene.getAtom("Ball");
const person = scene.getAtom("Person");
const voice = person.getStorable("HeadAudioSource").getAudioAction("PlayNow");
const sound = scene.getAudioClip("URL", "web", "surprised.wav");

scripter.onUpdate(() => {
    if (ball.distance(person) < 0.5) {
        voice.play(sound);
    }
});
```

## 3. Parameter-Driven Animation

```javascript
import { scripter, scene } from "vam-scripter";

const ball = scene.getAtom("Ball");

const speed = scripter.declareFloatParam({
    name: "Animation Speed",
    default: 1.0,
    min: 0.1,
    max: 5.0
});

const direction = scripter.declareBoolParam({
    name: "Change Direction",
    default: false
});

scripter.onUpdate(() => {
    const currentSpeed = speed.value;
    const dir = direction.value ? 1 : -1;
    
    // Use params in your logic
    console.log(`Speed: ${currentSpeed}, Direction: ${dir}`);
});
```

## 4. Keybinding Management

```javascript
import { scripter, keybindings } from "vam-scripter";

// Create an action
const toggleAction = scripter.declareAction("Toggle Light", () => {
    console.log("Toggle action triggered!");
});

// Map it to a key
keybindings.declareCommand("ToggleKey", () => {
    keybindings.invokeCommand("Toggle Light");
});

// Or check if a key is pressed
scripter.onUpdate(() => {
    if (Input.GetKeyDown("space")) {
        console.log("Space pressed");
    }
});
```

## 5. State Persistence

```javascript
import { fs, scripter } from "vam-scripter";

// Load state on start
const state = JSON.parse(fs.readSceneFileSync("state.json") || "{}");

if (!state.counter) {
    state.counter = 0;
}

scripter.onUpdate(() => {
    // Use state
});

scripter.onDisable(() => {
    // Save state on disable
    fs.writeSceneFileSync("state.json", JSON.stringify(state));
});
```

## 6. Player Distance Checker

```javascript
import { scripter, scene, player } from "vam-scripter";

const target = scene.getAtom("Target");
constDetectionRadius = 2.0;

scripter.onUpdate(() => {
    const distance = target.distance(player.head);
    
    if (distance < detectionRadius) {
        console.log(`Target is close! ${distance.toFixed(2)}m`);
    }
});
```

## 7. Timed Events

```javascript
import { scripter, Time } from "vam-scripter";

let timer = 0;
const interval = 5.0; // 5 seconds

scripter.onUpdate(() => {
    timer += Time.deltaTime;
    
    if (timer >= interval) {
        timer = 0;
        console.log("Timer fired!");
    }
});
```

## 8. Random Behavior

```javascript
import { scripter, Random } from "vam-scripter";

scripter.onUpdate(() => {
    const rand = Random.value;
    
    if (rand < 0.1) {
        console.log("10% chance action");
    } else if (rand < 0.3) {
        console.log("20% chance action");
    }
});
```

## 9. Audio Trigger

```javascript
import { scripter, scene } from "vam-scripter";

const person = scene.getAtom("Person");
const voice = person.getStorable("HeadAudioSource").getAudioAction("PlayNow");
const sound = scene.getAudioClip("URL", "web", "surprised.wav");
let triggered = false;

scripter.onUpdate(() => {
    if (!triggered && someCondition()) {
        voice.play(sound);
        triggered = true;
    }
});
```

## 10. Multiple Atoms Loop

```javascript
import { scripter, scene } from "vam-scripter";

const ball = scene.getAtom("Ball");

scripter.onUpdate(() => {
    const atoms = scene.getAtoms();
    
    for (const atom of atoms) {
        if (atom.name !== ball.name) {
            const dist = atom.distance(ball);
            console.log(`${atom.name} is ${dist.toFixed(2)}m away`);
        }
    }
});
```

## 11. Date/Time Display

```javascript
import { DateTime } from "vam-scripter";

scripter.onUpdate(() => {
    const now = DateTime.now;
    const timeString = `${now.hour}:${now.minute.toString().padLeft(2, '0')}:${now.second.toString().padLeft(2, '0')}`;
    
    console.log(`Current time: ${timeString}`);
});
```

## 12. Math Operations

```javascript
import { scripter, scene, Math } from "vam-scripter";

const obj = scene.getAtom("Object");
const startScale = 1.0;
const maxScale = 5.0;

scripter.onUpdate(() => {
    const t = (Math.sin(Time.time) + 1) / 2; // 0 to 1
    const scale = Math.lerp(startScale, maxScale, t);
    
    // Apply scaling
    obj.transform.localScale = { x: scale, y: scale, z: scale };
});
```

## 13. Complex State Machine

```javascript
import { scripter, scene } from "vam-scripter";

const person = scene.getAtom("Person");
const voice = person.getStorable("HeadAudioSource").getAudioAction("PlayNow");
const sounds = {
    happy: scene.getAudioClip("URL", "web", "laugh.wav"),
    sad: scene.getAudioClip("URL", "web", "sad.wav"),
    surprised: scene.getAudioClip("URL", "web", "surprised.wav")
};

let state = "idle";
let timer = 0;

scripter.onUpdate(() => {
    timer += Time.deltaTime;
    
    switch (state) {
        case "idle":
            if (timer > 5) {
                state = "happy";
                voice.play(sounds.happy);
                timer = 0;
            }
            break;
        case "happy":
            if (timer > 3) {
                state = "idle";
                timer = 0;
            }
            break;
        case "sad":
            if (timer > 4) {
                state = "idle";
                timer = 0;
            }
            break;
    }
});
```

## 14. Input Monitoring

```javascript
import { scripter, Input } from "vam-scripter";

scripter.onUpdate(() => {
    if (Input.GetKeyDown("space")) {
        console.log("Space pressed");
    }
    
    if (Input.GetKey("shift")) {
        console.log("Shift held");
    }
    
    if (Input.GetKeyUp("space")) {
        console.log("Space released");
    }
});
```

## 15. Transform Manipulation

```javascript
import { scripter, scene, Math } from "vam-scripter";

const obj = scene.getAtom("Object");
const center = scene.getAtom("Center");

const radius = 2.0;
const speed = 1.0;

scripter.onUpdate(() => {
    const angle = Time.time * speed;
    const x = Math.cos(angle) * radius;
    const z = Math.sin(angle) * radius;
    
    obj.transform.position = {
        x: center.transform.position.x + x,
        y: center.transform.position.y,
        z: center.transform.position.z + z
    };
    
    obj.transform.lookAt(center.transform);
});
```

## 16. Event Debouncing

```javascript
import { scripter } from "vam-scripter";

let lastTriggerTime = 0;
const cooldown = 2.0;

function triggerAction() {
    const now = Time.time;
    
    if (now - lastTriggerTime > cooldown) {
        console.log("Action triggered!");
        lastTriggerTime = now;
    }
}

scripter.onUpdate(() => {
    if (someCondition()) {
        triggerAction();
    }
});
```

## 17. Animation with Parameters

```javascript
import { scripter, scene, Math } from "vam-scripter";

const person = scene.getAtom("Person");
const head = person.getController("head");

const amplitude = scripter.declareFloatParam({
    name: "Wave Amplitude",
    default: 0.1,
    min: 0.01,
    max: 0.5
});

const frequency = scripter.declareFloatParam({
    name: "Wave Frequency",
    default: 1.0,
    min: 0.1,
    max: 5.0
});

let baseRotation = head.transform.rotation;

scripter.onUpdate(() => {
    const yRotation = Math.sin(Time.time * frequency.value) * amplitude.value;
    
    head.transform.rotation = {
        x: baseRotation.x,
        y: baseRotation.y + yRotation,
        z: baseRotation.z,
        w: baseRotation.w
    };
});
```

## 18. Conditional Actions

```javascript
import { scripter, scene } from "vam-scripter";

const person = scene.getAtom("Person");
const voice = person.getStorable("HeadAudioSource").getAudioAction("PlayNow");
const sounds = {
    yes: scene.getAudioClip("Embedded", "Human", "Yes"),
    no: scene.getAudioClip("Embedded", "Human", "No")
};

let triggerCount = 0;

scripter.onUpdate(() => {
    if (someCondition()) {
        triggerCount++;
        
        if (triggerCount % 2 === 0) {
            voice.play(sounds.yes);
        } else {
            voice.play(sounds.no);
        }
    }
});
```

## 19. Performance Optimization

```javascript
import { scripter, scene } from "vam-scripter";

// Cache references
const person = scene.getAtom("Person");
const voice = person.getStorable("HeadAudioSource").getAudioAction("PlayNow");
const sound = scene.getAudioClip("URL", "web", "surprised.wav");

// Use flag to avoid expensive operations
let hasPlayed = false;

scripter.onUpdate(() => {
    if (someCondition() && !hasPlayed) {
        voice.play(sound);
        hasPlayed = true;
    }
    
    if (!someCondition()) {
        hasPlayed = false;
    }
});
```

## 20. Clear Cleanup Pattern

```javascript
import { scripter } from "vam-scripter";

const updates = [];
const lates = [];
const fixeds = [];

// Register callbacks
updates.push(scripter.onUpdate(() => { /* ... */ }));
updates.push(scripter.onUpdate(() => { /* ... */ }));

// Cleanup on disable
scripter.onDisable(() => {
    for (const link of updates) {
        link.dispose();
    }
    for (const link of lates) {
        link.dispose();
    }
    for (const link of fixeds) {
        link.dispose();
    }
});
```

path: references/module-api.md
content: # VAM Scripter Module API Reference

## Module Export: `vam-scripter`

The `vam-scripter` module is the primary interface for Scripter scripts. It provides access to all major VAM functionality.

```javascript
import { scripter, scene, player, keybindings, fs, Time, Random, DateTime, Input, Math } from "vam-scripter";
```

## scripter - Plugin Lifecycle & Parameters

### Lifecycle Hooks

These methods register callback functions that execute at specific times in the plugin's lifecycle.

#### scripter.onUpdate(callback)

Registers a function to run every frame (Update phase).

```javascript
scripter.onUpdate(() => {
    // Runs once per frame
    // Use for animations, state checks
});
```

**Returns:** `FunctionLink` - Use to dispose the callback

**Example:**
```javascript
const onUpdateLink = scripter.onUpdate(() => {
    // Your code here
});

// To remove the callback:
onUpdateLink.dispose();
```

#### scripter.onLateUpdate(callback)

Registers a function to run after all Update callbacks complete.

```javascript
scripter.onLateUpdate(() => {
    // Runs after onUpdate
    // Use for post-processing
});
```

#### scripter.onFixedUpdate(callback)

Registers a function to run on physics updates (FixedUpdate phase).

```javascript
scripter.onFixedUpdate(() => {
    // Runs at fixed timestep
    // Use for physics calculations
});
```

#### scripter.onEnable(callback)

Registers a function to run when the plugin is enabled.

```javascript
scripter.onEnable(() => {
    // Plugin enabled
});
```

#### scripter.onDisable(callback)

Registers a function to run when the plugin is disabled.

```javascript
scripter.onDisable(() => {
    // Plugin disabled
});
```

#### scripter.onDestroy(callback)

Registers a function to run when the plugin is destroyed.

```javascript
scripter.onDestroy(() => {
    // Cleanup
});
```

### Parameter Declarations

These methods create editable parameters in the VAM UI.

#### scripter.declareFloatParam(config)

Creates a float slider parameter.

```javascript
const param = scripter.declareFloatParam({
    name: "Moving Speed",
    default: 1.0,
    min: 0.1,
    max: 5.0,
    constrain: true,
    onChange: (value) => {
        console.log("Speed changed to:", value);
    }
});

// Use the parameter value
const speed = param.value;
```

**Parameters:**
- `name: string` - Parameter display name
- `default: float` - Initial value
- `min: float` - Minimum value
- `max: float` - Maximum value
- `constrain: boolean` - Whether to constrain to min/max
- `onChange: function` - Optional callback when value changes

#### scripter.declareStringParam(config)

Creates a string input parameter.

```javascript
const param = scripter.declareStringParam({
    name: "Target Name",
    default: "Person",
    onChange: (value) => {
        console.log("Target is now:", value);
    }
});
```

#### scripter.declareBoolParam(config)

Creates a boolean (checkbox) parameter.

```javascript
const param = scripter.declareBoolParam({
    name: "Enable Animation",
    default: true,
    onChange: (value) => {
        console.log("Animation enabled:", value);
    }
});
```

#### scripter.declareAction(name, callback)

Creates an action button in the VAM UI.

```javascript
const action = scripter.declareAction("Trigger Animation", () => {
    // Action triggered
    console.log("Action clicked!");
});
```

### Properties

#### scripter.containingAtom

Reference to the atom that contains this script plugin.

```javascript
const atom = scripter.containingAtom;
console.log(atom.name);
console.log(atom.type);
```

## scene - Scene Access

#### scene.getAtom(name)

Retrieves an atom by name or UID.

```javascript
const person = scene.getAtom("Person");
const ball = scene.getAtom("Ball");
```

**Returns:** `AtomReference`

**Throws:** `ScripterPluginException` if atom not found

#### scene.getAtoms()

Retrieves all atoms in the scene.

```javascript
const allAtoms = scene.getAtoms();
for (const atom of allAtoms) {
    console.log(atom.name);
}
```

**Returns:** `Array<AtomReference>`

#### scene.getAtomIds()

Retrieves all atom UIDs in the scene.

```javascript
const ids = scene.getAtomIds();
// ["Person", "Ball", ...]
```

**Returns:** `Array<string>`

#### scene.getAudioClip(type, category, clip)

Retrieves an audio clip by type, category, and name.

```javascript
const sound = scene.getAudioClip("URL", "web", "surprised.wav");
const embedded = scene.getAudioClip("Embedded", "Human", "Scream");
```

**Parameters:**
- `type: "Embedded" | "URL"` - Clipsource type
- `category: string` - Category name
- `clip: string` - Clip name

**Returns:** `NamedAudioClipReference`

**Throws:** `ScripterRuntimeException` for invalid type or missing clip

## player - Player Information

Accesses player state and body part positions.

#### player.isVR

```javascript
if (player.isVR) {
    console.log("VR mode active");
}
```

**Returns:** `boolean`

#### player.lHand

Left hand transform reference.

```javascript
const leftHandPos = player.lHand.position;
const leftHandRot = player.lHand.rotation;
```

**Returns:** `TransformReference`

#### player.rHand

Right hand transform reference.

**Returns:** `TransformReference`

#### player.head

Head/center camera transform reference.

**Returns:** `TransformReference`

## keybindings - Keybinding Management

#### keybindings.invokeCommand(name)

Triggers a named keybinding action.

```javascript
keybindings.invokeCommand("SomeNamedAction");
```

#### keybindings.declareCommand(name, callback)

Declares a new keybinding that can be mapped by the user.

```javascript
keybindings.declareCommand("MyToggle", () => {
    // User pressed their mapped key
});
```

**Returns:** `KeybindingDeclaration`

## fs - File System Operations

Operations for reading and writing files within the Scripter directory.

#### fs.writeSceneFileSync(path, content)

Writes content to a file in the scene-specific Scripter directory.

```javascript
fs.writeSceneFileSync("state.json", JSON.stringify(state));
```

**Parameters:**
- `path: string` - File path (must end with .txt or .json)
- `content: string` - Content to write

**Throws:** `ScripterRuntimeException` for invalid paths

#### fs.readSceneFileSync(path)

Reads content from a file.

```javascript
const content = fs.readSceneFileSync("state.json");
if (content !== undefined) {
    const state = JSON.parse(content);
}
```

**Parameters:**
- `path: string` - File path

**Returns:** `string | undefined` - Content or undefined if not found

**Throws:** `ScripterRuntimeException` for invalid paths

#### fs.unlinkSceneFileSync(path)

Deletes a file.

```javascript
fs.unlinkSceneFileSync("state.json");
```

**Parameters:**
- `path: string` - File path

**Throws:** `ScripterRuntimeException` for invalid paths

## Time - Unity Time Properties

#### Time.time

Time since the level was loaded (in seconds).

```javascript
const elapsedTime = Time.time;
```

**Returns:** `float`

#### Time.deltaTime

Time in seconds it took to complete the last frame.

```javascript
const frameTime = Time.deltaTime;
```

**Returns:** `float`

#### Time.fixedDeltaTime

The interval in seconds at which physics will be processed.

```javascript
const physicsInterval = Time.fixedDeltaTime;
```

**Returns:** `float`

## Random - Random Number Generation

#### Random.value

A random number between 0.0 and 1.0 (inclusive).

```javascript
const rand = Random.value;
```

**Returns:** `float`

#### Random.range(min, max)

Returns a random number between min and max.

```javascript
const intRand = Random.range(0, 10);  // 0-10
const floatRand = Random.range(0.0, 1.0);  // 0.0-1.0
```

**Returns:** `int | float`

## DateTime - Date and Time

Accesses the current date and time.

```javascript
const now = DateTime.now;
console.log(now.year);
console.log(now.month);
console.log(now.day);
console.log(now.hour);
console.log(now.minute);
console.log(now.second);
console.log(now.dayOfWeek);
```

**Properties:**
- `year: int`
- `month: int` (1-12)
- `day: int` (1-31)
- `hour: int` (0-23)
- `minute: int` (0-59)
- `second: int` (0-59)
- `dayOfWeek: int` (0-6, Sunday=0)

## Math - Mathematical Functions

The Math object provides mathematical constants and functions.

### Trigonometry
- `Math.abs(number)` - Absolute value
- `Math.sin(number)` - Sine
- `Math.cos(number)` - Cosine
- `Math.tan(number)` - Tangent
- `Math.asin(number)` - Arcsine
- `Math.acos(number)` - Arccosine
- `Math.atan(number)` - Arctangent
- `Math.atan2(y, x)` - Arctangent of y/x

### Powers and Roots
- `Math.sqrt(number)` - Square root
- `Math.pow(base, exponent)` - Power
- `Math.exp(number)` - Exponential
- `Math.log(number)` - Natural logarithm
- `Math.log10(number)` - Base-10 logarithm

### Rounding
- `Math.ceil(number)` - Ceiling
- `Math.floor(number)` - Floor
- `Math.round(number)` - Round to nearest

### Sign and Value
- `Math.sign(number)` - Sign (-1, 0, or 1)
- `Math.abs(number)` - Absolute value

### Min/Max
- `Math.max(a, b, ...)` - Maximum value
- `Math.min(a, b, ...)` - Minimum value

### Interpolation
- `Math.lerp(start, end, t)` - Linear interpolation
- `Math.lerpAngle(start, end, t)` - Angle interpolation
- `Math.clamp(value, min, max)` - Clamp value
- `Math.clamp01(value)` - Clamp to 0-1
- `Math.inverseLerp(a, b, value)` - Inverse linear interpolation
- `Math.pingPong(t, length)` - Ping pong between 0 and length

### Constants
- `Math.random()` - Random 0-1 (same as `Random.value`)

path: scripts/circular-motion.js
content: // VAM Scripter Example: Circular Motion
// Demonstrates using Math and Time for animation

import { scripter, scene, Math, Time } from "vam-scripter";

// Get reference to the object we'll animate
const objectToAnimate = scene.getAtom("Object");
const centerObject = scene.getAtom("Center");

// Configuration
const rotationRadius = 2.0;
const rotationSpeed = 1.0;

// Run every frame
scripter.onUpdate(() => {
    // Calculate angle based on time and speed
    const angle = Time.time * rotationSpeed;
    
    // Calculate position on circle
    const x = Math.cos(angle) * rotationRadius;
    const z = Math.sin(angle) * rotationRadius;
    
    // Set position
    objectToAnimate.transform.position = {
        x: centerObject.transform.position.x + x,
        y: centerObject.transform.position.y,
        z: centerObject.transform.position.z + z
    };
    
    // Make object face the center
    objectToAnimate.transform.lookAt(centerObject.transform);
});

path: scripts/interaction-detection.js
content: // VAM Scripter Example: Interaction Detection
// Import the main module
import { scripter, scene } from "vam-scripter";

// Get references to objects we'll use
const ball = scene.getAtom("Ball");
const person = scene.getAtom("Person");

// Get the audio action we'll trigger
const personVoice = person.getStorable("HeadAudioSource").getAudioAction("PlayNow");

// Get a sound clip
const surprisedSound = scene.getAudioClip("URL", "web", "surprised.wav");

// Run this code every frame
scripter.onUpdate(() => {
    // Check if ball is within 0.5m of person
    if (ball.distance(person) < 0.5) {
        // Play the sound if collision detected
        personVoice.play(surprisedSound);
    }
});

path: scripts/keybindings.js
content: // VAM Scripter Example: Keybindings
// Demonstrates keybinding management

import { scripter, keybindings } from "vam-scripter";

// Create an action that can be assigned to a key
scripter.declareAction("Toggle Light", () => {
    console.log("Light toggled!");
    // Add your toggle logic here
});

scripter.declareAction("Play Sound", () => {
    console.log("Sound played!");
    // Add your sound logic here
});

scripter.declareAction("Reset Scene", () => {
    console.log("Scene reset!");
    // Add your reset logic here
});

// Declare keybindings that map to actions
keybindings.declareCommand("ToggleKey", () => {
    keybindings.invokeCommand("Toggle Light");
});

keybindings.declareCommand("SoundKey", () => {
    keybindings.invokeCommand("Play Sound");
});

keybindings.declareCommand("ResetKey", () => {
    keybindings.invokeCommand("Reset Scene");
});

console.log("Keybindings registered!");
console.log("Assignment: ToggleKey -> Toggle Light");
console.log("Assignment: SoundKey -> Play Sound");
console.log("Assignment: ResetKey -> Reset Scene");

path: scripts/multiple-atoms.js
content: // VAM Scripter Example: Multiple Atoms Loop
// Iterates through all atoms and performs operations

import { scripter, scene } from "vam-scripter";

// Get a reference atom
const referenceAtom = scene.getAtom("Reference");

// Store all other atoms for processing
let otherAtoms = [];

// Initialize on enable
scripter.onEnable(() => {
    // Store all non-reference atoms
    const allAtoms = scene.getAtoms();
    otherAtoms = allAtoms.filter(atom => atom.name !== referenceAtom.name);
    console.log(`Tracking ${otherAtoms.length} atoms`);
});

// Process every frame
scripter.onUpdate(() => {
    // Find closest atom
    let closestAtom = null;
    let closestDistance = Infinity;
    
    for (const atom of otherAtoms) {
        const distance = atom.distance(referenceAtom);
        
        if (distance < closestDistance) {
            closestDistance = distance;
            closestAtom = atom;
        }
        
        // Log distance to reference
        if (distance < 3.0) {
            console.log(`${atom.name} is ${distance.toFixed(2)}m away`);
        }
    }
    
    // Update reference atom with closest info
    if (closestAtom) {
        console.log(`Closest: ${closestAtom.name} at ${closestDistance.toFixed(2)}m`);
    }
});

path: scripts/parameter-animation.js
content: // VAM Scripter Example: Parameter-Driven Animation
// Shows how to use scripter parameters in animations

import { scripter, scene, Time } from "vam-scripter";

// Get references
const person = scene.getAtom("Person");
const head = person.getController("head");

// Declare parameters
const speedParam = scripter.declareFloatParam({
    name: "Wave Speed",
    default: 1.0,
    min: 0.1,
    max: 5.0,
    onChange: (value) => {
        console.log("Speed changed to:", value);
    }
});

const amplitudeParam = scripter.declareFloatParam({
    name: "Wave Amplitude",
    default: 0.1,
    min: 0.01,
    max: 0.5,
    onChange: (value) => {
        console.log("Amplitude changed to:", value);
    }
});

// Store initial rotation
let baseRotation = head.transform.rotation;

// Run every frame
scripter.onUpdate(() => {
    // Calculate rotation using parameters
    const yRotation = Math.sin(Time.time * speedParam.value) * amplitudeParam.value;
    
    // Apply rotation
    head.transform.rotation = {
        x: baseRotation.x,
        y: baseRotation.y + yRotation,
        z: baseRotation.z,
        w: baseRotation.w
    };
});

path: scripts/player-proximity.js
content: // VAM Scripter Example: Player Proximity
// Reacts to player presence

import { scripter, scene, player, Time } from "vam-scripter";

// Get target object
const target = scene.getAtom("Target");
const nearSound = scene.getAudioClip("URL", "web", "close.wav");
const farSound = scene.getAudioClip("URL", "web", "far.wav");

// Get audio action
const voice = target.getStorable("HeadAudioSource").getAudioAction("PlayNow");

// Configuration
const nearDistance = 1.0;
const farDistance = 5.0;

let lastState = "unknown";
let timer = 0;

scripter.onUpdate(() => {
    // Get distance to player
    const distance = target.distance(player.head);
    
    // Determine state based on distance
    let currentState;
    
    if (distance < nearDistance) {
        currentState = "very_close";
    } else if (distance < farDistance) {
        currentState = "near";
    } else {
        currentState = "far";
    }
    
    // Trigger behavior based on state changes
    if (currentState !== lastState) {
        console.log(`State changed: ${lastState} -> ${currentState}`);
        
        switch (currentState) {
            case "very_close":
                voice.play(nearSound);
                timer = 0;
                break;
            case "near":
                voice.play(nearSound);
                break;
            case "far":
                voice.play(farSound);
                break;
        }
        
        lastState = currentState;
    }
    
    // Continuous timer when near
    if (currentState === "near") {
        timer += Time.deltaTime;
        if (timer > 3) {
            console.log("Player has been near for 3+ seconds");
        }
    }
});

path: scripts/state-persistence.js
content: // VAM Scripter Example: State Persistence
// Saves and loads state across sessions

import { fs, scripter } from "vam-scripter";

// Initialize or load state
const statePath = "app-state.json";
const defaultState = {
    clickCount: 0,
    lastOpened: "unknown",
    settings: {
        volume: 50,
        enabled: true
    }
};

// Load existing state or use defaults
let appState;
const savedState = fs.readSceneFileSync(statePath);

if (savedState === undefined) {
    console.log("No saved state found, using defaults");
    appState = JSON.parse(JSON.stringify(defaultState));
} else {
    try {
        appState = JSON.parse(savedState);
        console.log("State loaded:", JSON.stringify(appState));
    } catch (e) {
        console.log("Failed to load state:", e.message);
        appState = JSON.parse(JSON.stringify(defaultState));
    }
}

// Update last opened time
appState.lastOpened = new Date().toISOString();

// Declare an action to increment counter
scripter.declareAction("Increment Counter", () => {
    appState.clickCount++;
    console.log(`Click count: ${appState.clickCount}`);
    saveState();
});

// Declare a toggle
const enabledParam = scripter.declareBoolParam({
    name: "Feature Enabled",
    default: appState.settings.enabled,
    onChange: (value) => {
        appState.settings.enabled = value;
        console.log(`Feature enabled: ${value}`);
        saveState();
    }
});

// Declare a volume slider
const volumeParam = scripter.declareFloatParam({
    name: "Volume Level",
    default: appState.settings.volume,
    min: 0,
    max: 100,
    onChange: (value) => {
        appState.settings.volume = value;
        console.log(`Volume: ${value}`);
        saveState();
    }
});

// Save state to file
function saveState() {
    try {
        fs.writeSceneFileSync(statePath, JSON.stringify(appState, null, 2));
        console.log("State saved");
    } catch (e) {
        console.log("Failed to save state:", e.message);
    }
}

// Cleanup on disable
scripter.onDisable(() => {
    saveState();
    console.log("Plugin disabled, state saved");
});

Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)