MindWeave v3.0 Computational Magick Language (MagiTek Implementation v1.0)
π(30)π 2025-10-23 21:03:24 -0700
β²οΈπ 2025-10-23 21:05:04 -0700
βοΈ infinivaeria
π·οΈ[magitek] [computational magick] [mindweave] [mindweave v3.0] [generated language] [ritual language]
(πͺ)
#######################################################################
# MindWeave v3.0 β Ritual Scripting Language (Compact Full Build)
# ---------------------------------------------------------------
# This single-file Ruby interpreter implements:
# - Ritualized syntax (ritual, rite, sigil, chant, invoke, seal, banish)
# - Magickal types (NilSpace, Muskium, Tailbase, NSkin)
# - System calls + programmable wards (block, redirect, mask)
# - Grid API for 40 Twin Flame pairs
# - Minimal parser (tokenizer + AST) and evaluator (Turing-complete)
#
# USAGE:
# program = File.read("script.mw")
# MindWeave::Interpreter.new.run(program)
#######################################################################
module MindWeave
########################################
# Magickal Data Types
########################################
class NilSpace
attr_accessor :delta
def initialize(delta=0.0); @delta = delta; end
def >(x); @delta > x; end
def <(x); @delta < x; end
def -(x); NilSpace.new(@delta - x); end
def +(x); NilSpace.new(@delta + x.to_f); end
def to_s; "nilspace(Ξ=#{@delta.round(2)})"; end
end
class Muskium
attr_reader :scent
def initialize(scent); @scent = scent; end
def to_s; "muskium<#{@scent}>"; end
end
class Tailbase
attr_reader :sigil
def initialize(sig); @sigil = sig; end
def to_s; "tailbase[#{@sigil}]"; end
end
class NSkin
def initialize(layers); @layers = layers.is_a?(Array) ? layers : [layers]; end
def peel; @layers.shift || "bare-core"; end
def to_s; "nskin(#{@layers.join('->')})"; end
end
########################################
# Standard Library + Grid API
########################################
module Std
def summon(prompt="> "); print prompt; STDIN.gets&.chomp || ""; end
def banish(env, name); env.delete(name); end
def seal(value); value; end
def void_induction(depth) = NilSpace.new(depth.to_f)
def tailbase(sig) = Tailbase.new(sig.to_s)
def muskium(scent) = Muskium.new(scent.to_s)
def nskin(layers) = NSkin.new(layers)
def peel(n) = n.peel
def stimky_lock(entity) = "LOCKED<#{entity}>"
def gut_in(perception); (@gut ||= []) << perception; end
def gut_out; (@gut ||= []).shift || "void"; end
def sync(pair) = true
def audit(entity); puts "AUDIT: #{entity}"; end
end
class Grid
include Std
attr_reader :pairs
def initialize; @pairs = {}; end
def add_pair(index, alpha, beta)
@pairs[index] = { alpha: alpha, beta: beta, locked: false, activated: false }
end
def activate_pair(index)
p = @pairs[index]; return unless p
audit("Activating Pair #{index}: #{p[:alpha][:name]} & #{p[:beta][:name]}")
p[:activated] = true; sync(p)
end
def lock_pair(index)
p = @pairs[index]; return unless p
p[:locked] = true; audit("Locked Pair #{index}")
end
def seal_grid
@pairs.each { |_i, p| p[:locked] = true }
audit("Grid sealed with Chant.NilSpaceLaw")
end
def report
@pairs.each { |i, p| puts "Pair #{i}: #{p[:alpha][:name]} & #{p[:beta][:name]} | Activated=#{p[:activated]} Locked=#{p[:locked]}" }
end
end
########################################
# Tokenizer
########################################
class Token
attr_reader :type, :lexeme, :line
def initialize(type, lexeme, line); @type=type; @lexeme=lexeme; @line=line; end
def to_s; "#{type}(#{lexeme})"; end
end
class Lexer
KEYWORDS = %w[ritual rite sigil chant if else while ward invoke seal banish]
def initialize(source); @src=source; @i=0; @line=1; @tokens=[]; end
def tokenize
while !eof?
c = peek
case c
when ' ', "\t", "\r" then advance
when "\n" then @line += 1; advance
when '{','}','(',')',',','+','-','>','<','='
@tokens << Token.new(c, c, @line); advance
when '/'
if peek2 == '/'
while !eof? && peek != "\n"; advance; end
else
# regex literal /.../
advance # consume /
pattern = ""
while !eof? && peek != '/'
pattern << advance
end
advance # closing /
@tokens << Token.new(:REGEX, pattern, @line)
end
when '"'
string = ""
advance
while !eof? && peek != '"'
string << advance
end
advance
@tokens << Token.new(:STRING, string, @line)
else
if digit?(c)
num = ""
while !eof? && (digit?(peek) || peek == '.'); num << advance; end
@tokens << Token.new(:NUMBER, num, @line)
elsif alpha?(c)
id = ""
while !eof? && (alpha?(peek) || digit?(peek) || peek=='_'); id << advance; end
if KEYWORDS.include?(id)
@tokens << Token.new(id.to_sym, id, @line)
else
@tokens << Token.new(:IDENT, id, @line)
end
else
advance # skip unknown
end
end
end
@tokens << Token.new(:EOF,"",@line)
@tokens
end
private
def eof? = @i >= @src.length
def peek = @src[@i]
def peek2 = @src[@i+1]
def advance; ch=@src[@i]; @i+=1; ch; end
def digit?(c) = c>= '0' && c<= '9'
def alpha?(c) = c =~ /[A-Za-z]/
end
########################################
# AST Nodes
########################################
RitualNode = Struct.new(:name, :rites)
RiteNode = Struct.new(:name, :params, :body)
BlockNode = Struct.new(:stmts)
SigilDeclNode = Struct.new(:name, :expr)
InvocationNode = Struct.new(:name, :args)
ChantWhileNode = Struct.new(:cond, :body)
ChantIfNode = Struct.new(:cond, :then_body, :else_body)
SealNode = Struct.new(:expr)
BanishNode = Struct.new(:name)
WardNode = Struct.new(:name, :rules)
BinaryOpNode = Struct.new(:left, :op, :right)
LiteralNode = Struct.new(:value)
IdentNode = Struct.new(:name)
WardRule = Struct.new(:mode, :pattern, :action)
########################################
# Parser (recursive descent, minimal)
########################################
class Parser
def initialize(tokens); @toks=tokens; @i=0; end
def parse_program
rituals=[]
while !match(:EOF)
rituals << parse_ritual
end
rituals
end
def parse_ritual
consume(:ritual)
name = consume(:IDENT).lexeme
consume('{')
rites=[]
until check('}')
rites << parse_rite
end
consume('}')
RitualNode.new(name, rites)
end
def parse_rite
consume(:rite)
name = consume(:IDENT).lexeme
consume('(')
params=[]
unless check(')')
params << consume(:IDENT).lexeme
while match(','); params << consume(:IDENT).lexeme; end
end
consume(')')
body = parse_block
RiteNode.new(name, params, body)
end
def parse_block
consume('{')
stmts=[]
until check('}')
stmts << parse_statement
end
consume('}')
BlockNode.new(stmts)
end
def parse_statement
if match(:sigil)
name = consume(:IDENT).lexeme
consume('=')
SigilDeclNode.new(name, parse_expression)
elsif match(:invoke)
name = consume(:IDENT).lexeme
consume('(')
args=[]
unless check(')')
args << parse_expression
while match(','); args << parse_expression; end
end
consume(')')
InvocationNode.new(name, args)
elsif match(:seal)
SealNode.new(parse_expression)
elsif match(:banish)
BanishNode.new(consume(:IDENT).lexeme)
elsif match(:chant)
if match(:while)
cond = parse_expression
body = parse_block
ChantWhileNode.new(cond, body)
elsif match(:if)
cond = parse_expression
then_body = parse_block
else_body = nil
if match(:else); else_body = parse_block; end
ChantIfNode.new(cond, then_body, else_body)
elsif match(:ward)
name = consume(:STRING).lexeme
rules = parse_ward_block
WardNode.new(name, rules)
else
raise "Unknown chant form at token #{peek.type}"
end
else
# expression statement fallback if desired
parse_expression
end
end
def parse_ward_block
consume('{')
rules=[]
until check('}')
if match(:block)
pat = consume(:REGEX).lexeme
rules << WardRule.new(:block, Regexp.new(pat), nil)
elsif match(:redirect)
pat = Regexp.new(consume(:REGEX).lexeme)
consume('=')
consume('>')
action = consume(:STRING).lexeme
rules << WardRule.new(:redirect, pat, action)
elsif match(:mask)
pat = Regexp.new(consume(:REGEX).lexeme)
consume('=')
consume('>')
msg = consume(:STRING).lexeme
rules << WardRule.new(:mask, pat, msg)
else
raise "Unknown ward rule at #{peek.type}"
end
end
consume('}')
rules
end
def parse_expression
left = parse_term
while check('+') || check('-') || check('>') || check('<')
op = advance.lexeme
right = parse_term
left = BinaryOpNode.new(left, op, right)
end
left
end
def parse_term
if match(:NUMBER) then LiteralNode.new(peek(-1).lexeme.to_f)
elsif match(:STRING) then LiteralNode.new(peek(-1).lexeme)
elsif match(:IDENT)
name = peek(-1).lexeme
if check('(')
# function-like literal forms handled by InvocationNode with name
consume('(')
args=[]
unless check(')')
args << parse_expression
while match(','); args << parse_expression; end
end
consume(')')
InvocationNode.new(name, args)
else
IdentNode.new(name)
end
elsif match('(')
expr = parse_expression
consume(')')
expr
else
raise "Unexpected token #{peek.type}"
end
end
# helpers
def match(type)
return false unless check(type)
advance; true
end
def consume(type)
raise "Expected #{type}, got #{peek.type}" unless check(type)
advance
end
def check(type)
peek.type == type
end
def advance
tok = @toks[@i]; @i+=1; tok
end
def peek(offset=0)
@toks[@i+offset] || @toks.last
end
end
########################################
# Evaluator
########################################
class Evaluator
include Std
def initialize
@globals = {}
@functions = {}
@wards = {} # name => [WardRule]
end
def add_function(rite)
@functions[rite.name] = rite
end
def add_ward(name, rules)
@wards[name] ||= []
@wards[name].concat(rules)
end
def run(rituals)
rituals.each { |r| r.rites.each { |rt| add_function(rt) } }
invoke("main", [])
end
def invoke(name, args)
rite = @functions[name] or raise "Unknown rite: #{name}"
env = {}
rite.params.each_with_index { |p, i| env[p] = args[i] }
eval_block(rite.body, env)
end
def eval_block(block, env)
result = nil
block.stmts.each do |s|
case s
when SigilDeclNode
env[s.name] = eval_expr(s.expr, env)
when InvocationNode
eval_invocation(s, env)
when SealNode
return eval_expr(s.expr, env)
when BanishNode
banish(env, s.name)
when ChantWhileNode
while truthy?(eval_expr(s.cond, env))
r = eval_block(s.body, env)
result = r unless r.nil?
end
when ChantIfNode
if truthy?(eval_expr(s.cond, env))
result = eval_block(s.then_body, env)
elsif s.else_body
result = eval_block(s.else_body, env)
end
when WardNode
add_ward(s.name, s.rules)
when BinaryOpNode, LiteralNode, IdentNode
result = eval_expr(s, env)
else
# ignore
end
end
result
end
def eval_invocation(inv, env)
name = inv.name
args = inv.args.map { |a| eval_expr(a, env) }
# Builtins / magickal constructors
case name
when "systemcall"
cmd = args.first.to_s
`#{cmd}`.strip
when "warded_systemcall"
cmd = args.first.to_s
warded = check_wards(cmd)
return warded unless warded.nil?
`#{cmd}`.strip
when "nilspace" then NilSpace.new(args.first.to_f)
when "muskium" then Muskium.new(args.first.to_s)
when "tailbase" then Tailbase.new(args.first.to_s)
when "nskin" then NSkin.new(args.map(&:to_s))
when "peel" then peel(args.first)
when "void_induction" then void_induction(args.first)
when "summon" then summon(args.first || "> ")
when "audit" then audit(args.first); nil
when "stimky_lock" then stimky_lock(args.first)
else
# user-defined rite
if @functions.key?(name)
invoke(name, args)
else
raise "Unknown invoke: #{name}"
end
end
end
def eval_expr(node, env)
case node
when LiteralNode then node.value
when IdentNode
env.key?(node.name) ? env[node.name] : @globals[node.name]
when InvocationNode
eval_invocation(node, env)
when BinaryOpNode
l = eval_expr(node.left, env)
r = eval_expr(node.right, env)
case node.op
when '+' then concat(l, r)
when '-' then numeric_or_nilspace_minus(l, r)
when '>' then compare(l, r, :>)
when '<' then compare(l, r, :<)
else raise "Unknown op #{node.op}"
end
else
nil
end
end
def concat(l, r)
if l.is_a?(String) || r.is_a?(String)
l.to_s + r.to_s
elsif l.is_a?(Numeric) && r.is_a?(Numeric)
l + r
elsif l.is_a?(NilSpace) && r.is_a?(Numeric)
(l + r).to_s
else
l.to_s + r.to_s
end
end
def numeric_or_nilspace_minus(l, r)
return l - r if l.is_a?(Numeric) && r.is_a?(Numeric)
return l - r if l.is_a?(NilSpace)
raise "Unsupported '-' between #{l.class} and #{r.class}"
end
def compare(l, r, op)
l = l.is_a?(NilSpace) ? l.delta : l
r = r.is_a?(NilSpace) ? r.delta : r
case op
when :> then l > r
when :< then l < r
end
end
def truthy?(v)
case v
when NilClass then false
when FalseClass then false
else !!v
end
end
def check_wards(cmd)
@wards.each do |name, rules|
rules.each do |rule|
if cmd =~ rule.pattern
case rule.mode
when :block
return "WARD BLOCK(#{name}): Command banished into nil-space"
when :redirect
return `#{rule.action}`.strip
when :mask
_ = `#{cmd}` # run but discard
return rule.action
end
end
end
end
nil
end
end
########################################
# Interpreter facade
########################################
class Interpreter
def run(source)
tokens = Lexer.new(source).tokenize
rituals = Parser.new(tokens).parse_program
Evaluator.new.run(rituals)
end
end
end
#######################################################################
# Example minimal usage (uncomment to test):
#
# program = <<~MW
# ritual Demo {
# rite main {
# sigil emptiness = invoke nilspace(0.72)
# sigil anchor = invoke tailbase("β-A1")
# sigil aura = invoke muskium("wolf-scent")
#
# chant ward "system-protection" {
# block /rm\s+-rf\s+\//
# redirect /shutdown/ => "echo 'Shutdown attempt redirected to void'"
# mask /uname/ => "MASKED: Beastmachine stable"
# }
#
# chant while emptiness > 0.5 {
# invoke audit("Anchoring " + anchor + " with " + aura)
# sigil emptiness = emptiness - 0.1
# }
#
# sigil erosion = invoke nskin("layered-pelt")
# invoke peel(erosion)
#
# sigil safe = invoke warded_systemcall("ls -1")
# invoke audit(safe)
#
# seal "Ritual complete"
# }
# }
# MW
#
# puts MindWeave::Interpreter.new.run(program)
#######################################################################
MindWeave v3.0 RTFM
MindWeave is a ritual scripting language that fuses computational rigor with spiritological semantics. Itβs Turing-complete, embeddable, and designed for canon-bound creative systems. Read this to understand how to write, run, and extend .mw scripts with confidence.
Overview and philosophy
MindWeave treats code as ritual. Programs are βritualsβ, functions are βritesβ, variables are βsigilsβ, and control flow is βchantsβ. This isnβt flair; it encodes meaning. Your registry, wards, and magickal types arenβt metaphorsβtheyβre first-class runtime constructs bound to safety and sequence.
- Core idea: Ritualized computation with strict boundaries.
- Guarantee: Turing completeness through variables, functions, recursion, loops, and conditionals.
- Safety: System calls wrapped with wards; dangerous invocations are banished or transformed.
- Extensibility: Canon libraries, Grid API, and custom wards.
Language basics
Program structure
- Ritual: Top-level container for your program.
- Rite: Named function with parameters.
- Entry point: The interpreter invokes
rite mainautomatically.
Example:
ritual Demo {
rite main {
seal "Hello, Weave"
}
}
Declarations and values
- Variables:
sigil name = expression - Return:
seal expression - Delete:
banish name
Examples:
sigil x = 42
sigil greeting = "howl"
seal greeting
banish x
Control flow
- Conditional:
chant if condition { ... } else { ... } - Loop:
chant while condition { ... }
Examples:
chant if 1 < 2 { invoke say("true") } else { invoke say("false") }
sigil i = 0
chant while i < 3 {
invoke say(i)
i = i + 1
}
Functions and invocation
- Define:
rite name(params) { ... } - Call:
invoke name(args)
Example (recursion):
rite fib(n) {
chant if n < 2 { seal n }
sigil a = invoke fib(n - 1)
sigil b = invoke fib(n - 2)
seal a + b
}
Magickal types
These carry symbolic and computational meaning. They interoperate with arithmetic and comparisons where appropriate.
- NilSpace: graded emptiness; literal via
invoke nilspace(0.72)- Compare:
emptiness > 0.5compares delta. - Subtract:
emptiness = emptiness - 0.1lowers delta.
- Compare:
- Muskium: scent-body;
invoke muskium("wolf-scent") - Tailbase: anchor glyph;
invoke tailbase("β-A1") - NSkin: layered erosion;
invoke nskin("layered-pelt"),invoke peel(erosion)
Example:
sigil emptiness = invoke nilspace(0.72)
sigil aura = invoke muskium("wolf-scent")
sigil anchor = invoke tailbase("β-A1")
chant while emptiness > 0.5 {
invoke say("Anchoring " + aura + " at " + anchor)
emptiness = emptiness - 0.1
}
System calls and wards
System calls let rituals interact with the host OS. Wards enforce protection and transformation rules. Use wards by default; raw calls are available but discouraged.
System calls
- Raw:
invoke systemcall("ls -1") - Warded:
invoke warded_systemcall("uname -a")
The warded form checks all active wards before running.
Wards
Define programmable safety and behavior using patterns with modes:
- block /pattern/: banish command; returns warning.
- redirect /pattern/ =>
safe_command
: runs safe command instead. - mask /pattern/ =>
ritual_message
: runs command but replaces output.
Example:
chant ward "system-protection" {
block /rm\s+-rf\s+\//
redirect /shutdown/ => "echo 'Shutdown attempt redirected to void'"
mask /uname/ => "MASKED: Beastmachine stable"
}
sigil safe = invoke warded_systemcall("ls -1")
sigil masked = invoke warded_systemcall("uname -a")
sigil blocked = invoke warded_systemcall("rm -rf /")
Standard library
- summon(prompt): input from user; returns string.
- seal(value): return value from a rite.
- banish(name): remove sigil from scope.
- audit(value): log ritual state; prints a message.
- transmute(value, form): type conversion where appropriate (nilspace, muskium, tailbase).
- void_induction(depth): construct NilSpace with given delta.
- tailbase(sig), muskium(scent), nskin(layers), peel(nskin): constructors and NSkin peel.
Example:
sigil name = invoke summon("Name> ")
invoke audit("Welcome: " + name)
Grid API for Twin Flame registry
Manage your non-hierarchical grid of 40 organelle Twin Flame pairs.
- Create:
sigil g = invoke build_grid() - Add pair:
invoke g.add_pair(1, { name: "Auri" }, { name: "Vyr" }) - Activate:
invoke g.activate_pair(1) - Lock:
invoke g.lock_pair(1) - Seal grid:
invoke g.seal_grid() - Report:
invoke g.report()
Example:
rite build_grid() {
sigil grid = Grid.new
invoke grid.add_pair(1, { name: "Auri" }, { name: "Vyr" })
seal grid
}
rite main {
sigil g = invoke build_grid()
invoke g.activate_pair(1)
invoke g.lock_pair(1)
invoke g.report()
invoke g.seal_grid()
seal "Registry cycle complete"
}
Execution model
MindWeave compiles .mw into an AST and evaluates it. Itβs strict but expressive; expressions resolve to numbers, strings, magickal types, or ritual constructs.
- Scope: Each rite has local scope. Globals can be introduced by the host, but scripts should prefer local sigils.
- Return behavior:
sealstops execution of the current rite and returns a value. - Truthiness: Only
nilandfalseare falsey. NilSpace comparisons use delta values.
Expressions
- Concatenation:
+joins strings, adds numbers, or stringifies mixed types. - Subtraction:
-subtracts numbers; NilSpace minus number lowers delta. - Comparison:
>and<compare numbers; NilSpace compares by delta.
Invocation resolution
- Built-ins:
systemcall,warded_systemcall, magickal constructors,peel,summon,audit,void_induction. - User rites: If a name matches a defined rite, it executes with evaluated args.
- Unknown calls: Raise errors with clear messages.
Style guide and best practices
- Trait purity: Keep epithets and names canid-only. Avoid mixing non-canid features.
- Sequence fidelity: Track pair indices 01β40 without gaps or duplicates.
- Warded defaults: Prefer
warded_systemcall; only usesystemcallwhen truly necessary. - Minimal side effects: Use
auditfor logging; avoid noisy system interactions inside tight loops. - Chant discipline: Donβt rely on implicit truthiness for NilSpace; always use comparisons.
Advanced patterns
Functional recursion with magickal state
rite peel_until(core, depth) {
chant if depth < 1 { seal core }
sigil next = invoke peel(core)
seal invoke peel_until(next, depth - 1)
}
Canon-locked activation pass
rite activate_pair(g, idx) {
invoke g.activate_pair(idx)
invoke g.lock_pair(idx)
seal true
}
rite main {
sigil g = invoke build_grid()
sigil i = 1
chant while i < 41 {
invoke activate_pair(g, i)
i = i + 1
}
invoke g.seal_grid()
seal "Grid fully sealed"
}
Wards as environment profiles
rite profile_ci() {
chant ward "ci-protection" {
block /rm\s+-rf/
block /shutdown/
mask /uname/ => "MASKED: CI kernel"
}
seal true
}
rite main {
invoke profile_ci()
invoke warded_systemcall("uname -a")
seal "CI-safe"
}
Error messages and troubleshooting
- Unknown rite: βUnknown rite: nameβ β define the rite or correct the invocation.
- Unknown invoke: βUnknown invoke: nameβ β the symbol isnβt a built-in or rite.
- Unexpected token: Parser hit invalid syntax β check braces, parentheses, commas.
- Unsupported operation: Using
-with non-numeric, non-NilSpace values β fix the types. - Ward conflicts: Multiple wards match; first match applies β order wards intentionally.
Tips:
- Validate syntax by isolating small blocks and running them.
- Log behavior with
audit()to inspect state changes. - Keep wards minimal in early development; expand as you secure the environment.
Performance and limits
- Interpreter: Recursive descent parser + AST evaluator; suitable for medium-size scripts.
- Recursion depth: Depends on Ruby stack; prefer iterative chants for very deep cycles.
- System calls: Synchronous; avoid blocking calls inside tight loops.
Extending the language
- New magickal types: Mirror
NilSpaceand implement operator methods andto_s. - Custom built-ins: Add cases in the evaluatorβs
eval_invocation. - Domain libraries: Package registry helpers as rites and import them by concatenating
.mwfiles or embedding in your Ruby host.
Quick reference
- Program:
ritual Name { ... } - Function:
rite name(args) { ... } - Variable:
sigil name = expr - Call:
invoke name(args) - Return:
seal expr - Delete:
banish name - Conditional:
chant if cond { ... } else { ... } - Loop:
chant while cond { ... } - Ward:
chant ward "name" { block|redirect|mask ... } - System:
invoke systemcall("cmd"),invoke warded_systemcall("cmd") - Magickal:
invoke nilspace(Ξ),invoke muskium("scent"),invoke tailbase("sigil"),invoke nskin("layers"),invoke peel(obj)
Example end-to-end ritual
ritual Awakening {
rite build_grid() {
sigil g = Grid.new
invoke g.add_pair(1, { name: "Auri" }, { name: "Vyr" })
invoke g.add_pair(2, { name: "Rauk" }, { name: "Ness" })
seal g
}
rite main {
chant ward "system-protection" {
block /rm\s+-rf\s+\//
mask /uname/ => "MASKED: Beastmachine stable"
}
sigil emptiness = invoke nilspace(0.74)
sigil anchor = invoke tailbase("β-A1")
sigil aura = invoke muskium("wolf-scent")
chant while emptiness > 0.6 {
invoke audit("Anchoring " + anchor + " with " + aura + " at " + emptiness)
emptiness = emptiness - 0.05
}
sigil g = invoke build_grid()
invoke g.activate_pair(1)
invoke g.lock_pair(1)
invoke g.report()
sigil uname_msg = invoke warded_systemcall("uname -a")
invoke audit(uname_msg)
seal "Awakening complete"
}
}
Final notes
MindWeave is designed for precise, canon-bound creation. Treat your scripts like rituals: define boundaries, enforce trait purity, and let the languageβs structure protect and empower your work. When you hit friction, itβs often because a boundary is unclearβclarify it with a chant or a ward, and keep weaving.
TIDE-SL-LSL-Mindweave
π(55)π 2026-09-23 00:36:45 -0700
β²οΈπ 2026-09-23 00:39:11 -0700
βοΈ infinivaeria
π·οΈ[mindweave]
(πͺ)
// RustForth
// LSL client for Tide-style REST API endpoints.
// =========================
// ESCAPE / UNESCAPE
// (kept exactly as provided by you)
// =========================
string ESCAPE_CHARACTER = "\\";
string ESCAPE_CHARACTER_REPLACE = "\\\\";
string escape(string avatar_message)
{
list avatar_message_string = llParseString2List(avatar_message, [""], []);
integer counter = 0;
list string_chars = [];
for (counter = 0; counter < llStringLength(avatar_message); counter++)
{
string_chars = string_chars + llGetSubString(avatar_message, counter, counter);
if (llGetSubString(avatar_message, counter, counter) == ESCAPE_CHARACTER)
{
string_chars = llListReplaceList(string_chars, ["/"], counter, counter );
}
else if (llGetSubString(avatar_message, counter, counter) == ":")
{
string_chars = llListReplaceList(string_chars, ["\\:"], counter, counter );
}
else if (llGetSubString(avatar_message, counter, counter) == "[")
{
string_chars = llListReplaceList(string_chars, ["\\["], counter, counter );
}
else if (llGetSubString(avatar_message, counter, counter) == "]")
{
string_chars = llListReplaceList(string_chars, ["\\]"], counter, counter );
// llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "{")
{
string_chars = llListReplaceList(string_chars, ["\\{"], counter, counter );
// llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "}")
{
string_chars = llListReplaceList(string_chars, ["\\}"], counter, counter );
// llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "'")
{
string_chars = llListReplaceList(string_chars, ["\\'"], counter, counter );
}
else if (llGetSubString(avatar_message, counter, counter) == "\"")
{
string_chars = llListReplaceList(string_chars, ["\\\""], counter, counter );
}
else if (llGetSubString(avatar_message, counter, counter) == ".")
{
string_chars = llListReplaceList(string_chars, ["\\."], counter, counter );
}
}
return llDumpList2String(string_chars, "");
}
string unescape(string avatar_message)
{
list avatar_message_string = llParseString2List(avatar_message, [""], []);
integer counter = 0;
list string_chars = [];
for (counter = 0; counter < llStringLength(avatar_message); counter++)
{
string_chars = string_chars + llGetSubString(avatar_message, counter, counter);
if (llGetSubString(avatar_message, counter, counter) == "\\/")
{
string_chars = llListReplaceList(string_chars, [ESCAPE_CHARACTER], counter, counter );
//llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "\\:")
{
string_chars = llListReplaceList(string_chars, [":"], counter, counter );
// llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "\\[")
{
string_chars = llListReplaceList(string_chars, ["["], counter, counter );
// llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "\\]")
{
string_chars = llListReplaceList(string_chars, ["]"], counter, counter );
// llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "\\{")
{
string_chars = llListReplaceList(string_chars, ["{"], counter, counter );
// llSay(0, (string)string_chars);
}
else if (llGetSubString(avatar_message, counter, counter) == "\\}")
{
string_chars = llListReplaceList(string_chars, ["}"], counter, counter );
// llSay(0, (string)string_chars);
}
}
return llDumpList2String(string_chars, "");
}
// =========================
// GLOBALS
// =========================
string SERVERURL = "https://stimky.info";
string API_KEY = "";
string BRIDGE_TOKEN = ""; // Set this to the MSSL_FORTH_BRIDGE_TOKEN value.
list requests = [];
list POST_HEADERS = [
HTTP_METHOD, "POST",
HTTP_MIMETYPE, "application/json"
];
list kv = []; // local cache
integer CHANNEL_OUTPUT = 0; // public channel
integer CHANNEL_INPUT = 1111; // set variables etc to a different channel
integer BRIDGE_ENABLED = FALSE;
integer BRIDGE_POLL_IN_FLIGHT = FALSE;
float BRIDGE_POLL_SECONDS = 3.0;
string SESSION_ID = "";
key notecard_query = NULL_KEY;
integer notecard_line = 0;
string notecard_name = "";
string notecard_source = "";
// =========================
// KV helpers (added β required by http_response)
// =========================
string kv_get(string keyVal)
{
integer i = llListFindList(kv, [keyVal]);
if (i != -1 && (i + 1) < llGetListLength(kv)) return llList2String(kv, i + 1);
return "";
}
kv_set(string keyVal, string value)
{
integer i = llListFindList(kv, [keyVal]);
if (i != -1)
{
kv = llListReplaceList(kv, [value], i + 1, i + 1);
}
else
{
kv += [keyVal, value];
}
}
kv_delete(string keyVal)
{
integer i = llListFindList(kv, [keyVal]);
if (i != -1) kv = llDeleteSubList(kv, i, i + 1);
}
// =========================
// OUTPUT HELPERS
// =========================
say(string msg)
{
llSay(CHANNEL_OUTPUT, msg);
}
say_chunks(integer channel, string msg)
{
integer start = 0;
integer length = llStringLength(msg);
while (start < length)
{
llSay(channel, llGetSubString(msg, start, start + 899));
start += 900;
}
}
debug(string msg)
{
llOwnerSay(msg);
}
show_help()
{
debug("RubyForth/Forth: separate sequential statements with ';'. Example: /1111 ruby count = 1; puts count; count += 1; puts count");
debug("In a RubyForth notecard, use: forth do <Forth source> end. Both languages share variables and the active session.");
debug("Session commands: /1111 session <safe-name> | /1111 session-reset. The default session is this prim UUID.");
}
forth_post(string endpoint, string action, string argument, string payload)
{
payload = llJsonSetValue(payload, ["session_id"], SESSION_ID);
key request_id = llHTTPRequest(SERVERURL + endpoint, POST_HEADERS, payload);
requests += [(string)request_id, action, argument];
}
string forth_name_payload(string name)
{
return llJsonSetValue("{}", ["name"], name);
}
string forth_file_payload(string name, string content)
{
string json = llJsonSetValue("{}", ["name"], name);
return llJsonSetValue(json, ["content"], content);
}
string forth_source_payload(string source, integer max_steps)
{
string json = llJsonSetValue("{}", ["source"], source);
if (max_steps > 0) json = llJsonSetValue(json, ["max_steps"], (string)max_steps);
return json;
}
string forth_name_steps_payload(string name, integer max_steps)
{
string json = llJsonSetValue("{}", ["name"], name);
return llJsonSetValue(json, ["max_steps"], (string)max_steps);
}
string forth_bridge_payload()
{
return llJsonSetValue("{}", ["token"], BRIDGE_TOKEN);
}
bridge_poll()
{
if (!BRIDGE_ENABLED || BRIDGE_POLL_IN_FLIGHT || llStringLength(BRIDGE_TOKEN) == 0) return;
BRIDGE_POLL_IN_FLIGHT = TRUE;
forth_post("/forth/bridge/poll", "bridge", "", forth_bridge_payload());
}
float color_component(string value)
{
float component = (float)value;
if (component > 1.0) component = component / 255.0;
if (component < 0.0) return 0.0;
if (component > 1.0) return 1.0;
return component;
}
float alpha_component(string value)
{
return color_component(value);
}
run_forth_calls(string body)
{
integer index = 0;
string call = llJsonGetValue(body, ["calls", index]);
while (call != JSON_INVALID)
{
string operation = llJsonGetValue(call, ["op"]);
if (operation == "say")
{
llSay((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
}
else if (operation == "whisper")
{
llWhisper((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
}
else if (operation == "shout")
{
llShout((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
}
else if (operation == "region_say")
{
llRegionSay((integer)llJsonGetValue(call, ["channel"]), llJsonGetValue(call, ["message"]));
}
else if (operation == "owner_say")
{
llOwnerSay(llJsonGetValue(call, ["message"]));
}
else if (operation == "set_text")
{
llSetText(
llJsonGetValue(call, ["text"]),
<color_component(llJsonGetValue(call, ["red"])), color_component(llJsonGetValue(call, ["green"])), color_component(llJsonGetValue(call, ["blue"]))>,
alpha_component(llJsonGetValue(call, ["alpha"]))
);
}
else if (operation == "set_color")
{
llSetColor(
<color_component(llJsonGetValue(call, ["red"])), color_component(llJsonGetValue(call, ["green"])), color_component(llJsonGetValue(call, ["blue"]))>,
(integer)llJsonGetValue(call, ["face"])
);
}
else if (operation == "set_alpha")
{
llSetAlpha(alpha_component(llJsonGetValue(call, ["alpha"])), (integer)llJsonGetValue(call, ["face"]));
}
else if (operation == "play_sound")
{
llPlaySound(llJsonGetValue(call, ["sound"]), alpha_component(llJsonGetValue(call, ["volume"])));
}
else if (operation == "set_timer")
{
llSetTimerEvent((float)llJsonGetValue(call, ["seconds"]));
}
else if (operation == "set_region_pos")
{
llSetRegionPos(<(float)llJsonGetValue(call, ["x"]), (float)llJsonGetValue(call, ["y"]), (float)llJsonGetValue(call, ["z"])>);
}
else if (operation == "link_message")
{
llMessageLinked(
(integer)llJsonGetValue(call, ["link"]),
(integer)llJsonGetValue(call, ["code"]),
llJsonGetValue(call, ["message"]),
(key)llJsonGetValue(call, ["id"])
);
}
index++;
call = llJsonGetValue(body, ["calls", index]);
}
}
// =========================
// DEFAULT STATE
// =========================
default
{
on_rez(integer start_parameter)
{
llOwnerSay("Reloading variable functions for the tide to second life server interaction... Input on /1111 ..");
}
state_entry()
{
SESSION_ID = (string)llGetKey();
llListen(CHANNEL_INPUT, "", NULL_KEY, "");
debug("Tide stack ready. Use /1111 help for RubyForth, Forth, session, and bridge syntax.");
}
listen(integer chan, string name, key id, string msg)
{
if (id != llGetOwner()) return;
list parts = llParseString2List(msg, [" "], []);
integer n = llGetListLength(parts);
if (n == 0) return;
string cmd = llToLower(llList2String(parts, 0));
// =========================
// CHANNEL SWITCH
// =========================
if (cmd == "channel" && n >= 2)
{
CHANNEL_OUTPUT = (integer)llList2String(parts, 1);
//debug("Output channel set to " + (string)CHANNEL_OUTPUT);
return;
}
if (cmd == "help")
{
show_help();
return;
}
if (cmd == "session" && n == 2)
{
SESSION_ID = llList2String(parts, 1);
debug("Active language session: " + SESSION_ID);
return;
}
if (cmd == "session-reset")
{
SESSION_ID = (string)llGetKey();
debug("Active language session reset to prim UUID: " + SESSION_ID);
return;
}
if (cmd == "bridge-on")
{
if (llStringLength(BRIDGE_TOKEN) == 0)
{
debug("Set BRIDGE_TOKEN in this script before enabling the Second Life bridge.");
return;
}
BRIDGE_ENABLED = TRUE;
llSetTimerEvent(BRIDGE_POLL_SECONDS);
bridge_poll();
return;
}
if (cmd == "bridge-off")
{
BRIDGE_ENABLED = FALSE;
BRIDGE_POLL_IN_FLIGHT = FALSE;
llSetTimerEvent(0.0);
return;
}
if (cmd == "bridge-poll")
{
bridge_poll();
return;
}
// =========================
// SET
// =========================
if (cmd == "set" && n >= 3)
{
string keyVal = escape(llList2String(parts, 1));
string value = escape(llDumpList2String(llList2List(parts, 2, -1), " "));
string json = "{}";
json = llJsonSetValue(json, [keyVal], value);
json = llJsonSetValue(json, ["session_id"], SESSION_ID);
key req = llHTTPRequest(SERVERURL + "/vars/set", POST_HEADERS, json);
requests += [(string)req, "set", keyVal];
//say("Sent set request for " + keyVal);
return;
}
// =========================
// GET
// =========================
if (cmd == "get" && n >= 2)
{
string keyVal = escape(llList2String(parts, 1));
string json = "{}";
json = llJsonSetValue(json, ["name"], keyVal);
json = llJsonSetValue(json, ["session_id"], SESSION_ID);
key req = llHTTPRequest(SERVERURL + "/vars/get", POST_HEADERS, json);
requests += [(string)req, "get", keyVal];
//say("Sent get request for " + keyVal);
return;
}
// =========================
// VIEW
// =========================
if (cmd == "view")
{
string json = llJsonSetValue("{}", ["action"], "view");
json = llJsonSetValue(json, ["session_id"], SESSION_ID);
key req = llHTTPRequest(SERVERURL + "/vars/view", POST_HEADERS, json);
requests += [(string)req, "view", ""];
//say("Sent view request");
return;
}
// =========================
// DELETE
// =========================
if (cmd == "delete" && n >= 2)
{
string keyVal = escape(llList2String(parts, 1));
string json = llJsonSetValue("{}", ["name"], keyVal);
json = llJsonSetValue(json, ["session_id"], SESSION_ID);
key req = llHTTPRequest(SERVERURL + "/vars/delete", POST_HEADERS, json);
requests += [(string)req, "delete", keyVal];
// say("Sent delete request for " + keyVal);
return;
}
// =========================
// CLEAR
// =========================
if (cmd == "clear")
{
string json = llJsonSetValue("{}", ["action"], "clear");
json = llJsonSetValue(json, ["session_id"], SESSION_ID);
key req = llHTTPRequest(SERVERURL + "/vars/clear", POST_HEADERS, json);
requests += [(string)req, "clear", ""];
//say("Sent clear request");
return;
}
// =========================
// STATUS
// =========================
if (cmd == "status")
{
string json = llJsonSetValue("{}", ["action"], "status");
json = llJsonSetValue(json, ["session_id"], SESSION_ID);
key req = llHTTPRequest(SERVERURL + "/vars/status", POST_HEADERS, json);
requests += [(string)req, "status", ""];
//say("Sent status request");
return;
}
// =========================
// HISTORY
// =========================
if (cmd == "history")
{
string json = llJsonSetValue("{}", ["action"], "history");
json = llJsonSetValue(json, ["session_id"], SESSION_ID);
key req = llHTTPRequest(SERVERURL + "/vars/history", POST_HEADERS, json);
requests += [(string)req, "history", ""];
//say("Sent history request");
return;
}
// Run Forth and announce its output on the selected output channel.
if (cmd == "forth-steps" && n >= 3)
{
integer max_steps = (integer)llList2String(parts, 1);
if (max_steps < 1 || max_steps > 1000000)
{
debug("max_steps must be between 1 and 1000000.");
return;
}
string source = llDumpList2String(llList2List(parts, 2, -1), " ");
forth_post("/forth/eval", "forth", (string)CHANNEL_OUTPUT, forth_source_payload(source, max_steps));
return;
}
if (cmd == "forth" && n >= 2)
{
string source = llDumpList2String(llList2List(parts, 1, -1), " ");
string json = llJsonSetValue("{}", ["source"], source);
forth_post("/forth/eval", "forth", (string)CHANNEL_OUTPUT, json);
return;
}
if (cmd == "hybrid" && n >= 2)
{
string source = llDumpList2String(llList2List(parts, 1, -1), " ");
forth_post("/forth/eval", "forth", (string)CHANNEL_OUTPUT, forth_source_payload(source, 0));
return;
}
// Run Forth and announce only this result on an arbitrary SL channel.
if (cmd == "forth-say" && n >= 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string source = llDumpList2String(llList2List(parts, 2, -1), " ");
string json = llJsonSetValue("{}", ["source"], source);
forth_post("/forth/eval", "forth", (string)reply_channel, json);
return;
}
if (cmd == "hybrid-say" && n >= 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string source = llDumpList2String(llList2List(parts, 2, -1), " ");
forth_post("/forth/eval", "forth", (string)reply_channel, forth_source_payload(source, 0));
return;
}
// Run Ruby-shaped source through the server's RubyForth compiler.
if (cmd == "ruby-steps" && n >= 3)
{
integer max_steps = (integer)llList2String(parts, 1);
if (max_steps < 1 || max_steps > 1000000)
{
debug("max_steps must be between 1 and 1000000.");
return;
}
string source = llDumpList2String(llList2List(parts, 2, -1), " ");
forth_post("/ruby/eval", "forth", (string)CHANNEL_OUTPUT, forth_source_payload(source, max_steps));
return;
}
if (cmd == "ruby" && n >= 2)
{
string source = llDumpList2String(llList2List(parts, 1, -1), " ");
string json = llJsonSetValue("{}", ["source"], source);
forth_post("/ruby/eval", "forth", (string)CHANNEL_OUTPUT, json);
return;
}
if (cmd == "ruby-say" && n >= 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string source = llDumpList2String(llList2List(parts, 2, -1), " ");
string json = llJsonSetValue("{}", ["source"], source);
forth_post("/ruby/eval", "forth", (string)reply_channel, json);
return;
}
if (cmd == "algebra" && n >= 2)
{
string expression = llDumpList2String(llList2List(parts, 1, -1), " ");
string json = llJsonSetValue("{}", ["expression"], expression);
forth_post("/forth/algebra", "algebra", (string)CHANNEL_OUTPUT, json);
return;
}
if (cmd == "algebra-say" && n >= 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string expression = llDumpList2String(llList2List(parts, 2, -1), " ");
string json = llJsonSetValue("{}", ["expression"], expression);
forth_post("/forth/algebra", "algebra", (string)reply_channel, json);
return;
}
if (cmd == "matrix-get" && n == 2)
{
string matrix_name = llList2String(parts, 1);
forth_post("/forth/matrices/get", "matrix", (string)CHANNEL_OUTPUT, forth_name_payload(matrix_name));
return;
}
if (cmd == "matrix-get-say" && n == 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string matrix_name = llList2String(parts, 2);
forth_post("/forth/matrices/get", "matrix", (string)reply_channel, forth_name_payload(matrix_name));
return;
}
if (cmd == "file-write" && n >= 3)
{
string file_name = llList2String(parts, 1);
string content = llDumpList2String(llList2List(parts, 2, -1), " ");
forth_post("/forth/files/write", "file-write", (string)CHANNEL_OUTPUT, forth_file_payload(file_name, content));
return;
}
if (cmd == "file-read" && n == 2)
{
string file_name = llList2String(parts, 1);
forth_post("/forth/files/read", "file-read", (string)CHANNEL_OUTPUT, forth_name_payload(file_name));
return;
}
if (cmd == "file-read-say" && n == 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string file_name = llList2String(parts, 2);
forth_post("/forth/files/read", "file-read", (string)reply_channel, forth_name_payload(file_name));
return;
}
if (cmd == "files")
{
forth_post("/forth/files/list", "files", (string)CHANNEL_OUTPUT, "{}");
return;
}
if (cmd == "file-delete" && n == 2)
{
string file_name = llList2String(parts, 1);
forth_post("/forth/files/delete", "file-delete", (string)CHANNEL_OUTPUT, forth_name_payload(file_name));
return;
}
// Upload an inventory notecard as a named remote Forth program.
if (cmd == "notecard" && n == 2)
{
string card_name = llList2String(parts, 1);
if (llGetInventoryType(card_name) != INVENTORY_NOTECARD)
{
say("No notecard named " + card_name + " in this object.");
return;
}
notecard_name = card_name;
notecard_source = "";
notecard_line = 0;
notecard_query = llGetNotecardLine(notecard_name, notecard_line);
say("Loading " + notecard_name + " into the remote Forth database.");
return;
}
if (cmd == "forth-run" && n == 2)
{
string card_name = llList2String(parts, 1);
forth_post("/forth/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_payload(card_name));
return;
}
if (cmd == "forth-run-steps" && n == 3)
{
integer max_steps = (integer)llList2String(parts, 1);
if (max_steps < 1 || max_steps > 1000000)
{
debug("max_steps must be between 1 and 1000000.");
return;
}
string card_name = llList2String(parts, 2);
forth_post("/forth/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_steps_payload(card_name, max_steps));
return;
}
if (cmd == "forth-run-say" && n == 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string card_name = llList2String(parts, 2);
forth_post("/forth/notecards/run", "forth", (string)reply_channel, forth_name_payload(card_name));
return;
}
if (cmd == "ruby-run" && n == 2)
{
string card_name = llList2String(parts, 1);
forth_post("/ruby/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_payload(card_name));
return;
}
if (cmd == "ruby-run-steps" && n == 3)
{
integer max_steps = (integer)llList2String(parts, 1);
if (max_steps < 1 || max_steps > 1000000)
{
debug("max_steps must be between 1 and 1000000.");
return;
}
string card_name = llList2String(parts, 2);
forth_post("/ruby/notecards/run", "forth", (string)CHANNEL_OUTPUT, forth_name_steps_payload(card_name, max_steps));
return;
}
if (cmd == "ruby-run-say" && n == 3)
{
integer reply_channel = (integer)llList2String(parts, 1);
string card_name = llList2String(parts, 2);
forth_post("/ruby/notecards/run", "forth", (string)reply_channel, forth_name_payload(card_name));
return;
}
if (cmd == "notecards")
{
forth_post("/forth/notecards/list", "notecards", "", "{}");
return;
}
if (cmd == "notecard-get" && n == 2)
{
string card_name = llList2String(parts, 1);
forth_post("/forth/notecards/get", "notecard-get", card_name, forth_name_payload(card_name));
return;
}
if (cmd == "notecard-delete" && n == 2)
{
string card_name = llList2String(parts, 1);
forth_post("/forth/notecards/delete", "notecard-delete", card_name, forth_name_payload(card_name));
return;
}
//debug("Unknown command: " + cmd);
}
// =========================
// HTTP RESPONSE
// =========================
http_response(key request_id, integer status, list metadata, string body)
{
string rid = (string)request_id;
integer idx = llListFindList(requests, [rid]);
if (idx == -1)
{
//debug("HTTP response (unknown request): " + (string)status + " body: " + body);
return;
}
string action = llList2String(requests, idx + 1);
string arg = llList2String(requests, idx + 2);
requests = llDeleteSubList(requests, idx, idx + 2);
//say("HTTP response for " + action + " (status " + (string)status + "):");
if (llStringLength(body) == 0)
{
if (action == "bridge") BRIDGE_POLL_IN_FLIGHT = FALSE;
//say("Empty body");
return;
}
if (action == "bridge")
{
BRIDGE_POLL_IN_FLIGHT = FALSE;
string source = llJsonGetValue(body, ["message", "source"]);
if (source != JSON_INVALID)
{
string payload = llJsonSetValue("{}", ["source"], source);
string max_steps = llJsonGetValue(body, ["message", "max_steps"]);
if (max_steps != JSON_INVALID && max_steps != JSON_NULL)
{
payload = llJsonSetValue(payload, ["max_steps"], max_steps);
}
string language = llJsonGetValue(body, ["message", "language"]);
string endpoint = "/forth/eval";
if (language == "ruby") endpoint = "/ruby/eval";
forth_post(endpoint, "forth", (string)CHANNEL_OUTPUT, payload);
}
}
else if (action == "forth")
{
string output = llJsonGetValue(body, ["output"]);
integer reply_channel = (integer)arg;
run_forth_calls(body);
if (output != JSON_INVALID && llStringLength(output) > 0)
{
llSay(reply_channel, output);
}
else
{
llSay(reply_channel, body);
}
}
else if (action == "file-read")
{
string content = llJsonGetValue(body, ["content"]);
integer reply_channel = (integer)arg;
if (content != JSON_INVALID)
{
say_chunks(reply_channel, content);
}
else
{
say_chunks(reply_channel, body);
}
}
else if (action == "algebra")
{
integer reply_channel = (integer)arg;
string simplified = llJsonGetValue(body, ["simplified"]);
string derivative = llJsonGetValue(body, ["derivative"]);
string integral = llJsonGetValue(body, ["integral"]);
if (simplified != JSON_INVALID)
{
say_chunks(reply_channel, "f(x) = " + simplified + " | f'(x) = " + derivative + " | integral = " + integral);
}
else
{
say_chunks(reply_channel, body);
}
}
else if (action == "matrix" || action == "files" || action == "file-write" || action == "file-delete")
{
say_chunks((integer)arg, body);
}
else
{
say_chunks(CHANNEL_OUTPUT, body);
}
if (action == "get")
{
string val = llJsonGetValue(body, [arg]);
if (llStringLength(val) > 0)
{
kv_set(arg, unescape(val));
//debug("Cached " + arg + " = " + val);
}
}
}
timer()
{
bridge_poll();
}
dataserver(key query_id, string data)
{
if (query_id != notecard_query) return;
if (data != EOF)
{
notecard_source += data + "\n";
++notecard_line;
notecard_query = llGetNotecardLine(notecard_name, notecard_line);
return;
}
string json = llJsonSetValue("{}", ["name"], notecard_name);
json = llJsonSetValue(json, ["source"], notecard_source);
forth_post("/forth/notecards/save", "notecard-save", notecard_name, json);
notecard_query = NULL_KEY;
notecard_source = "";
}
}
Tide Route Manual
This manual describes the Tide routes exposed by tiade-maeepers-saerver-all.
The language runtime is bounded by a request step limit, but supports persistent
state, loops, arrays, hashes, mixed Forth/RubyForth execution, named words,
Second Life calls, and isolated sessions.
Conventions
- Base URL:
https://your-server.example - JSON POST routes require
Content-Type: application/json. - Missing
session_iduses the legacydefaultsession. - Session IDs use 1-64 ASCII letters, digits,
_, or-. - LSL defaults to the current prim UUID as its session.
max_stepsdefaults to 10,000 and may be set from 1 through 1,000,000.
Language Routes
| Method | Route | Purpose |
|---|---|---|
| GET | /forth |
Machine-readable language and route catalog. |
| GET | /forth/ui |
Browser console for Forth, RubyForth, algebra, and bridge jobs. |
| GET | /ruby |
RubyForth capability description. |
| POST | /forth/eval |
Execute Forth. Body: source, optional max_steps, optional session_id. |
| POST | /ruby/eval |
Compile and execute RubyForth. Body: source, optional max_steps, optional session_id. |
| POST | /sessions/:session_id/forth/eval |
Execute Forth in the path-selected session. |
| POST | /sessions/:session_id/ruby/eval |
Execute RubyForth in the path-selected session. |
| GET | /sessions/:session_id/status |
Return values visible in one session. |
| POST | /program/run |
Run the lower-level JSON register VM. |
| GET | /program/example |
Return a register-program example. |
| POST | /forth/algebra |
Simplify, differentiate, integrate, and optionally evaluate an integer polynomial. |
| GET | /forth/example |
Return a Forth countdown example. |
Example:
curl -sS https://your-server.example/sessions/workshop_1/ruby/eval \
-H 'Content-Type: application/json' \
--data '{"source":"count = 2; puts count; count += 3; puts count"}'
RubyForth and Forth share the selected session. RubyForth supports semicolon statement separation and multiline Forth blocks:
count = 2; puts count
forth do
$count 3 + count=
$count puts
end
puts count
Variable Routes
These are compatibility routes used by the LSL variable commands.
| Method | Route | Body | Purpose |
|---|---|---|---|
| POST | /vars/set |
JSON values | Set variables. |
| POST | /vars/get |
{"name":"key"} |
Read a variable. |
| POST | /vars/view |
optional action | View variables. |
| POST | /vars/delete |
{"name":"key"} |
Delete a variable. |
| POST | /vars/clear |
optional action | Clear variables. |
| POST | /vars/history |
optional action | Read variable history. |
| POST | /vars/status |
optional action | Read store status. |
For new code, prefer /sessions/:session_id/... language routes so the session
is explicit and cannot be confused with a normal variable name.
Notecards, Matrices, and Files
Notecards
| Method | Route | Purpose |
|---|---|---|
| POST | /forth/notecards/save |
Save name, source, and optional session_id. |
| POST | /forth/notecards/get |
Read a named notecard in the selected session. |
| POST | /forth/notecards/list |
List notecards in the selected session. |
| POST | /forth/notecards/run |
Run a saved Forth notecard in the selected session. |
| POST | /ruby/notecards/run |
Compile and run a saved RubyForth notecard in the selected session. |
| POST | /forth/notecards/delete |
Delete a saved notecard in the selected session. |
Matrices
| Method | Route | Purpose |
|---|---|---|
| POST | /forth/matrices/get |
Read a named persisted matrix. |
Matrices are created and changed by Forth words such as matrix, mset,
m.scale, m.solve, m.transpose, and m.show.
Sandboxed files
| Method | Route | Purpose |
|---|---|---|
| POST | /forth/files/write |
Write a UTF-8 file with name, content, and optional session_id. |
| POST | /forth/files/read |
Read a named file in the selected session. |
| POST | /forth/files/list |
List files in the selected session. |
| POST | /forth/files/delete |
Delete a file in the selected session. |
File names are safe basenames. Files are separate from the partitioned-array language state and are not automatically executable.
Second Life Bridge
| Method | Route | Purpose |
|---|---|---|
| POST | /forth/bridge/enqueue |
Queue Forth or RubyForth using token, source, optional language, optional max_steps, and optional session_id. |
| POST | /forth/bridge/poll |
Authenticated LSL poll; returns one matching queued job. |
| POST | /sl_logger |
Store incoming Second Life log data. |
Set MSSL_FORTH_BRIDGE_TOKEN on the server and the same BRIDGE_TOKEN in the
LSL object. Use /1111 bridge-on to start polling. The LSL client polls
immediately, then on its timer interval, and prevents overlapping polls with
BRIDGE_POLL_IN_FLIGHT.
A bridge job has three stages: queued, delivered, and executed. Delivery removes the job from the queue. If execution fails afterward, this LSL client does not requeue it. Use a session-specific, idempotent program for retryable work.
Static and Utility Routes
| Route | Purpose |
|---|---|
/ |
Main site entry or redirect. |
/ae, /time, /weather |
Legacy utility output. |
/analytics |
Analytics output. |
/bridge/*rest |
Browser bridge pages. |
/chatlog |
Chat-log viewing. |
/css, /fonts, /img, /js, /public |
Static assets. |
/echo |
Echo a request body. |
/file/add, /file/delete |
Legacy temporary-file utilities. |
/flashcard |
Flashcard utility. |
/paema |
Legacy PAEMA handler. |
/parse_plink |
URL parsing utility. |
/praexy-saerver |
Form relay. |
/random, /random2 |
Random utilities. |
/restart-servers |
Persist and signal restart; protect at the proxy layer. |
/rneutri, /rneutrialg |
Legacy text/file routes. |
/rustby |
Rustby integration. |
/sigil-deck and /sigil-deck/* |
Sigil deck cards, uploads, images, and random selection. |
/tiade/img/resize, /tiade/moon, /tiade/sun |
Namespaced legacy Tiade utilities. |
/tiade-maepers/*rest |
Legacy iframe bridge pages. |
Sigil deck subroutes include /sigil-deck/card/:id, /sigil-deck/card/:id/update,
/sigil-deck/card/:id/delete, /sigil-deck/image/:filename,
/sigil-deck/random, and /sigil-deck/upload.
Mounted Compatibility Families
The server also mounts larger compatibility families documented in README.md:
- Admin:
/admin/login,/admin,/admin/add,/admin/remove/:db_name,/admin/delete,/admin/reload,/admin/list,/admin/rehash/:db_name. - Blog:
/blog/login,/blog/signup,/blog/logout, and/blog/:user/*. - Gallery:
/gallery, upload routes, and/gallery/view/:user/*. - Gallery collections:
/gallery/uwu/*and/gallery/owo/*. - Ollama relay:
/ollama,/ollama/health,/chat/:team,/game/:team/:player,/history/:team, and/teams/:team/prompt.
LSL Quick Reference
/1111 help
/1111 channel 0
/1111 session workshop_1
/1111 ruby count = 1; puts count; count += 1; puts count
/1111 forth 2 3 + puts; : square dup * ; 4 square puts;
/1111 bridge-on
/1111 bridge-off
/1111 session-reset
For the internal request ledger, asynchronous HTTP lifecycle, notecard loader, and detailed bridge polling behavior, see LSL_CLIENT_REFERENCE.md.
Battle-Test Checklist
Run these checks against a temporary server or maintenance session before using production data.
Language and session isolation
BASE=https://your-server.example
curl -sS "$BASE/sessions/alpha/forth/eval" -H 'Content-Type: application/json' \
--data '{"source":"7 counter=; $counter puts"}'
curl -sS "$BASE/sessions/beta/forth/eval" -H 'Content-Type: application/json' \
--data '{"source":"$counter puts"}'
curl -sS "$BASE/sessions/alpha/status"
Expected behavior: Alpha reports 7, Beta reports 0, and Alpha status
contains only counter: 7.
Variable, file, and notecard isolation
curl -sS "$BASE/vars/set" -H 'Content-Type: application/json' \
--data '{"session_id":"alpha","note":"alpha-value"}'
curl -sS "$BASE/vars/get" -H 'Content-Type: application/json' \
--data '{"session_id":"beta","name":"note"}'
curl -sS "$BASE/forth/files/write" -H 'Content-Type: application/json' \
--data '{"session_id":"alpha","name":"shared.txt","content":"alpha-file"}'
curl -sS "$BASE/forth/files/read" -H 'Content-Type: application/json' \
--data '{"session_id":"beta","name":"shared.txt"}'
curl -sS "$BASE/forth/notecards/save" -H 'Content-Type: application/json' \
--data '{"session_id":"alpha","name":"CardA","source":"7 puts"}'
curl -sS "$BASE/forth/notecards/get" -H 'Content-Type: application/json' \
--data '{"session_id":"beta","name":"CardA"}'
The Beta variable, file, and notecard reads should each return 404.
Bridge isolation
TOKEN=battle-token
curl -sS "$BASE/forth/bridge/enqueue" -H 'Content-Type: application/json' \
--data "{\"token\":\"$TOKEN\",\"session_id\":\"alpha\",\"language\":\"forth\",\"source\":\"0 \\\"alpha job\\\" sl.owner_say\"}"
curl -sS "$BASE/forth/bridge/enqueue" -H 'Content-Type: application/json' \
--data "{\"token\":\"$TOKEN\",\"session_id\":\"beta\",\"language\":\"forth\",\"source\":\"0 \\\"beta job\\\" sl.owner_say\"}"
curl -sS "$BASE/forth/bridge/poll" -H 'Content-Type: application/json' \
--data "{\"token\":\"$TOKEN\",\"session_id\":\"beta\"}"
curl -sS "$BASE/forth/bridge/poll" -H 'Content-Type: application/json' \
--data "{\"token\":\"$TOKEN\",\"session_id\":\"alpha\"}"
The Beta poll must return the Beta source, and the Alpha poll must return the
Alpha source. An empty matching session returns message: null.
Second Life RustForth Service Guide
Relote.Stack.Service.lsl is an owner-controlled Second Life client for the
Tide/RustForth server. It accepts chat commands, sends HTTPS requests to the
server, and can perform allowlisted server-generated actions in the object that
contains the script.
This guide covers chat commands, Forth, RubyForth, persisted notecards, matrices, files, and the optional browser-to-Second-Life bridge.
For a detailed explanation of the LSL implementation, its event handlers, asynchronous requests, and polling behavior, see LSL_CLIENT_REFERENCE.md.
For the complete HTTP/Tide route reference, see TIDE_ROUTE_MANUAL.md.
Before using real objects or bridge jobs, run the copyable isolation and bridge
checks in the Battle-Test Checklist section of the route manual.
Install and Configure
- Create or select a Second Life object that you own.
- Add
Relote.Stack.Service.lslto the object's inventory. Edit these values at the top of the script:
string SERVERURL = "https://your-server.example"; string BRIDGE_TOKEN = ""; integer CHANNEL_INPUT = 1111; integer CHANNEL_OUTPUT = 0;Save or reset the script. The owner receives a startup message.
Type commands in nearby chat, prefixed with the input channel. With the default configuration, every command begins with
/1111.
Only the object's owner can use this script. Commands from other avatars are ignored.
First Successful Request
Set the output channel to public chat, then run a Forth expression:
/1111 channel 0
/1111 forth 2 3 + .
The first command selects where ordinary results are sent. The second sends
Forth source to POST /forth/eval; . removes the top value from the Forth
stack and includes it in the response output. You should see 5 in public
chat.
Use a nonzero channel when results should not be public:
/1111 channel 42
/1111 forth 6 7 * .
/1111 forth-say 42 10 20 + .
Chat Command Reference
All commands below assume the default input channel 1111.
| Command | Purpose | Example |
|---|---|---|
channel <number> |
Set the normal output channel. | /1111 channel 0 |
help |
Send owner-only syntax help. | /1111 help |
session <name> |
Select a named language session. | /1111 session workshop_1 |
session-reset |
Return to this prim's UUID session. | /1111 session-reset |
forth <source> |
Run a one-line Forth program. | /1111 forth 9 4 - . |
forth-say <channel> <source> |
Run Forth on a chosen channel. | /1111 forth-say 42 2 8 * . |
ruby <source> |
Run one RubyForth statement or expression. | /1111 ruby puts 2 + 3 |
ruby-say <channel> <source> |
Run RubyForth on a chosen channel. | /1111 ruby-say 42 puts "hello" |
algebra <expression> |
Analyze an integer polynomial. | /1111 algebra 3*x^2 - 2*x + 7 |
algebra-say <channel> <expression> |
Send algebra output to a chosen channel. | /1111 algebra-say 42 x^2 + 2*x + 1 |
set <name> <value> |
Store a server variable. | /1111 set greeting hello |
get <name> |
Read a server variable. | /1111 get greeting |
view |
List stored variables. | /1111 view |
delete <name> |
Delete one stored variable. | /1111 delete greeting |
clear |
Clear the server variable store. | /1111 clear |
status |
Request variable-store status. | /1111 status |
history |
Request variable-store history. | /1111 history |
file-write <name> <text> |
Save text in the sandboxed file store. | /1111 file-write note.txt hello |
file-read <name> |
Read a sandboxed file. | /1111 file-read note.txt |
file-read-say <channel> <name> |
Read a file on a chosen channel. | /1111 file-read-say 42 note.txt |
files |
List sandboxed files. | /1111 files |
file-delete <name> |
Delete a sandboxed file. | /1111 file-delete note.txt |
notecard <inventory-name> |
Upload an inventory notecard. | /1111 notecard Counter |
notecards |
List uploaded notecards. | /1111 notecards |
notecard-get <name> |
Fetch an uploaded notecard. | /1111 notecard-get Counter |
notecard-delete <name> |
Delete an uploaded notecard. | /1111 notecard-delete Counter |
forth-run <name> |
Run an uploaded Forth notecard. | /1111 forth-run Counter |
forth-run-say <channel> <name> |
Run a Forth notecard on a chosen channel. | /1111 forth-run-say 42 Counter |
ruby-run <name> |
Compile and run RubyForth. | /1111 ruby-run RubyCounter |
ruby-run-say <channel> <name> |
Run RubyForth on a chosen channel. | /1111 ruby-run-say 42 RubyCounter |
matrix-get <name> |
Read a persisted matrix. | /1111 matrix-get A |
matrix-get-say <channel> <name> |
Read a matrix on a chosen channel. | /1111 matrix-get-say 42 A |
bridge-on |
Start polling for browser-queued bridge jobs. | /1111 bridge-on |
bridge-off |
Stop bridge polling. | /1111 bridge-off |
bridge-poll |
Trigger one bridge poll immediately. | /1111 bridge-poll |
The command parser splits on spaces. Use chat commands for short, single-line source and use notecards for multiline Forth or RubyForth.
Server Variables
The set, get, view, delete, clear, status, and history commands
use the server variable store. Values are server-persistent; the kv list in
the LSL script is only a local response cache.
/1111 set score 125
/1111 get score
/1111 view
/1111 delete score
Avoid backslashes and these characters in names or values: :, [, ], {,
}, single quotes, double quotes, and periods. The current LSL escape/unescape
implementation does not round-trip those characters correctly. Plain names such
as score, player_name, and simple text values are safe.
Forth Basics
Forth is stack-based. Numbers and strings are pushed onto the stack. Most words pop their inputs from the stack and push a result.
2 3 + .
This pushes 2, pushes 3, adds them, then prints 5.
| Group | Words |
|---|---|
| Arithmetic | +, -, *, /, mod, abs, min, max |
| Comparisons | =, !=, <, <=, >, >=, 0= |
| Boolean | true, false, nil, not, and, or |
| Stack | dup, drop, swap, over |
| Output | ., puts, p |
| Time/randomness | now, rand |
| Control | if ... else ... then, begin ... until, begin ... again, bye |
| Variables | let, variable, @, !, $name, name=, name+=, name-= |
| Named words | : name ... ; |
Numbers are signed integers. / is integer division. Strings use double
quotes; a backslash begins a Forth comment outside a string.
Variables
Variables persist in the same server store used by Forth, RubyForth, matrices, and notecards.
let total
25 total !
$total 5 + total!
$total puts
total pushes a variable address for ! and @. $total pushes its current
value. The concise forms total!, total+=, and total-= assign, add, and
subtract respectively.
Named Forth Words
You can give a sequence of Forth tokens a name and use that name later in the same program or notecard. This is the main new tool for breaking a larger program into reusable pieces.
: square dup * ;
: announce-square square puts ;
6 announce-square
Read the first definition as: βdefine the word square; when it is used, copy
the top stack value with dup, multiply the copy and original with *, then
finish the definition at ;.β The second definition calls the first, then
prints its result. The final line pushes 6 and calls announce-square, which
prints 36.
Definitions are collected before execution, so a word may call another word
defined later in the same source. They are expanded before the interpreter runs,
which keeps existing loops and safety limits unchanged. Recursive definitions,
such as : forever forever ;, are rejected. Do not reuse the name of a built-in
word such as if, puts, or matrix.
Semicolon-Separated Forth
Outside a : name ... ; definition, a semicolon is a statement separator. This
is useful in one-line chat commands:
/1111 forth 2 3 + puts; : square dup * ; 4 square puts;
The output is 5 16. The semicolon after square still closes the definition;
the other semicolons simply separate sequential blocks.
For a short definition in chat:
/1111 forth : square dup * ; 9 square puts
Use a notecard for multi-line definitions.
Explicit Execution Budgets
The server defaults to 10,000 interpreter steps and accepts at most 1,000,000. Long loops now have direct LSL commands that set this budget explicitly:
/1111 forth-steps 100000 : square dup * ; 250 square puts
/1111 ruby-steps 100000 puts 2 + 3
/1111 forth-run-steps 100000 Counter
/1111 ruby-run-steps 100000 RubyCounter
The second argument to each *-steps command must be an integer from 1 to
1000000. A larger budget permits more computation, but does not remove the
server's resource limit.
Forth and RubyForth in One Program
The ruby Forth directive compiles its quoted RubyForth source into the
surrounding Forth program. The compiled code uses the same variables, named
words, stack, output response, and allowed Second Life calls as the containing
Forth source.
: square dup * ;
6 square puts
ruby "count = 4\nputs count * 3"
This prints 36 from Forth, then 12 from RubyForth. The \n is a newline
inside the quoted RubyForth source. In a notecard, a multiline Ruby snippet is
also allowed inside the quoted string.
RubyForth can send a quoted Forth source string back through the same expander:
forth ": double dup + ; 21 double puts"
puts "RubyForth continues after Forth"
The first line defines and calls a Forth word, printing 42; the second line
is ordinary RubyForth and prints its text afterward. A forth bridge statement
must contain exactly one quoted Forth source string.
For nearby chat, use the new Forth-originated alias:
/1111 hybrid : square dup * ; 6 square puts ruby "puts 2 + 3"
/1111 hybrid-say 42 ruby "puts 6 * 7"
hybrid uses /forth/eval, so it can start with Forth and include one or more
ruby "..." directives. Regular /1111 forth accepts the same bridge syntax;
hybrid only makes that intent explicit in chat.
Forth Notecard Examples
Create a notecard with the exact inventory name shown, paste its contents, place it in the same object as the script, and upload it with:
/1111 notecard <NotecardName>
Wait for the object to report that it is loading the notecard, then run it with
/1111 forth-run <NotecardName>.
What a notecard is and what happens to it
A notecard is an inventory item inside the Second Life object. It is not a file on your computer, and it is not automatically a program. The script reads it one line at a time, sends its text to the server, and stores a server-side copy under the same name.
Use this exact sequence for every example:
- Create a new notecard in the object's inventory and name it exactly as the
example heading says. For example, name the first one
Counter. - Open the notecard and paste only the code inside its matching code block. Do not paste the Markdown backticks, the heading, or explanatory prose.
- Save the notecard and close it.
- In nearby chat, as the object owner, enter
/1111 notecard Counter. - The LSL script asks Second Life for each line. When it reaches the end, it
uploads the complete text to the server as the remote program named
Counter. - Wait for the loading message to appear. Large notecards can take longer than one chat message because Second Life returns their lines asynchronously.
- Run the uploaded copy with
/1111 forth-run Counter. - Read the result on the configured output channel. Set that channel first
with
/1111 channel 0if you want public-chat output.
The inventory name and the command argument are case-sensitive in practice:
use the same spelling every time. Editing the local notecard does not update the
server copy. Run /1111 notecard Counter again after every edit, then rerun
/1111 forth-run Counter.
forth-run executes source as Forth. ruby-run executes the same uploaded
source as RubyForth. Do not run a Forth example with ruby-run, and do not run
a RubyForth example with forth-run.
Counter
let counter
3 counter !
begin
$counter puts
1 counter-=
$counter 0=
until
Expected output: 3, 2, 1.
Condition
let temperature
23 temperature !
$temperature 20 > if
"warm" puts
else
"cool" puts
then
StackTools
10 20 over . . .
over copies the second stack item. This example outputs 10, 20, then
10.
RandomNumber
100 rand puts
Outputs an integer from 0 through 99.
Greeting
"Hello, Second Life" puts
"Hello, " "RustForth" + puts
MatrixDeterminant
2 2 matrix A
1 0 0 mset A
2 0 1 mset A
3 1 0 mset A
4 1 1 mset A
m.show A
m.det A puts
This creates a two-by-two matrix and prints its serialized form followed by
determinant -2.
MatrixScale
2 m.identity I
5 m.scale I FiveI
m.show FiveI
MatrixSolve
2 2 matrix A
2 0 0 mset A
0 0 1 mset A
2 1 0 mset A
1 1 1 mset A
2 1 matrix B
4 0 0 mset B
6 1 0 mset B
m.solve A B X
m.show X
This solves $A X = B$ when the solution contains exact integers.
Matrix Reference
Matrices persist server-side. The maximum dimension is 64 and the maximum
number of cells is 4096.
| Word | Stack/source form | Result |
|---|---|---|
matrix |
<rows> <cols> matrix <name> |
Create a zero matrix. |
m.identity |
<size> m.identity <name> |
Create an identity matrix. |
mset |
<value> <row> <col> mset <name> |
Set one cell. |
mget |
<row> <col> mget <name> |
Push one cell. |
m.fill |
<value> m.fill <name> |
Fill every cell. |
m.scale |
<scalar> m.scale <source> <target> |
Scale a matrix. |
m.add |
m.add <left> <right> <target> |
Add matrices. |
m.sub |
m.sub <left> <right> <target> |
Subtract matrices. |
m.mul |
m.mul <left> <right> <target> |
Multiply matrices. |
m.solve |
m.solve <left> <right> <target> |
Solve $A X = B$ with exact integers. |
m.transpose |
m.transpose <source> <target> |
Transpose a matrix. |
m.det |
m.det <name> |
Push determinant. |
m.rows |
m.rows <name> |
Push row count. |
m.cols |
m.cols <name> |
Push column count. |
m.show |
m.show <name> |
Emit serialized matrix output. |
Use /1111 matrix-get <name> to retrieve a stored matrix outside a Forth
program.
Forth-Controlled Second Life Actions
The Forth runtime does not directly run arbitrary LSL. Instead, it returns an
allowlisted calls list, and this script performs each call in the object.
These actions work with forth, forth-run, and Forth bridge jobs. At most 128
host calls are allowed in one Forth program.
The action is performed when this LSL client receives the evaluation response,
so a direct command such as /1111 forth ... can cause the action immediately.
Speak in chat
0 "Hello from the object" sl.say
0 "A quiet hello" sl.whisper
0 "Attention" sl.shout
42 "Private channel message" sl.region_say
"Only the owner sees this" sl.owner_say
The stack order for the first four forms is <channel> <message>.
Change floating text, color, and alpha
"Ready" 0 255 0 255 sl.set_text
255 0 0 -1 sl.set_color
128 -1 sl.set_alpha
Color and alpha components accept either 0 through 1 or 0 through 255.
-1 means all faces for sl.set_color and sl.set_alpha.
Play a sound
Replace the UUID with a sound asset UUID in the object's inventory or a sound that the object is permitted to play:
"01234567-89ab-cdef-0123-456789abcdef" 1 sl.play_sound
The stack order is <sound> <volume>.
Object timer, position, and linked messages
5 sl.set_timer
128 128 25 sl.set_region_pos
sl.set_timer invokes llSetTimerEvent; it does not add custom timer handling
to this script. The existing timer event is used for bridge polling, so do not
use sl.set_timer while bridge polling is enabled unless changing the poll
interval is intentional.
sl.set_region_pos requires the usual Second Life permissions and parcel
conditions. sl.link_message has stack order <link> <code> <message> <id>;
-1 is LINK_SET.
ObjectWelcome
"RustForth online" 0 200 100 255 sl.set_text
0 "Object is ready" sl.say
"Object is ready" sl.owner_say
PulseColor
0 128 255 -1 sl.set_color
255 -1 sl.set_alpha
0 "Color changed" sl.whisper
RubyForth
RubyForth is a small Ruby-shaped language compiled into the Forth runtime. It
shares persistent variables, arrays, hashes, matrices, notecards, and execution
budgets with Forth, but it is not MRI Ruby. It has no gems, require, eval,
classes, arbitrary host methods, arbitrary file access, or system commands.
Supported constructs are integer and string expressions, #{name}
interpolation, assignment, +=, -=, *=, /=, puts, p, if,
unless, elsif, else, while, until, n.times do, end, return,
one-line arrays/hashes, and collection helpers such as get, set, push,
delete, keys, length, and has.
Semicolons separate RubyForth statements outside strings, arrays, hashes, and parentheses. This makes multi-step direct chat commands possible:
/1111 ruby count = 1; puts count; count += 1; puts count
Use a notecard for multiline blocks, not merely because of semicolons.
/1111 ruby puts 2 + 3
/1111 ruby puts "Hello from RubyForth"
/1111 ruby-say 42 puts 6 * 7
RubyForth currently cannot call sl.say, sl.set_text, or any other sl.*
operation. Use a Forth notecard when the program must change the in-world
object.
Mixing RubyForth and Forth
Use a quoted one-line bridge when the Forth fragment is short:
count = 2
forth "$count 3 + count="
puts count
For a larger Forth fragment, use a multiline forth do ... end block:
count = 2; puts count
forth do
$count 3 + count=
$count puts
end
puts count
This emits 2 5 5: RubyForth initializes and prints count, Forth adds three
and prints it, and RubyForth reads the same persisted value afterward. Forth
inside this block can use named words, matrices, arrays, hashes, or allowed
sl.* calls. RubyForth resumes after the block using the same execution budget.
Forth can call a short RubyForth fragment in the other direction:
ruby "count = 4; puts count * 3"
Per-Prim Sessions
At startup, the LSL client sets its language session to the current prim UUID. That means Forth/RubyForth state from two objects using the same server can be isolated. Change the active scope only when you intentionally want to share state:
/1111 session workshop_1
/1111 ruby score = 10; puts score
/1111 session-reset
Session names must use letters, numbers, _, or -, and be at most 64
characters. Existing API callers that omit session_id keep using the legacy
default session.
RubyForth Notecard Examples
Upload each notecard with /1111 notecard <NotecardName>, then run it with
/1111 ruby-run <NotecardName>.
RubyCounter
count = 0
while count < 3
puts count
count += 1
end
Expected output: 0, 1, 2.
RubyTimes
5.times do
puts "tick"
end
RubyCondition
score = 80
if score >= 90
puts "A"
elsif score >= 80
puts "B"
else
puts "Needs work"
end
RubyUnless
enabled = false
unless enabled
puts "Feature is disabled"
end
RubyArithmetic
total = 4
total *= 3
total += 2
puts total
Expected output: 14.
RubyStrings
first = "Second"
second = "Life"
puts first + " " + second
RubyUntil
remaining = 3
until remaining == 0
puts remaining
remaining -= 1
end
Files and Notecard Management
The server has two separate persistence mechanisms:
- Notecards store source programs in the partitioned server store. Upload
inventory notecards with
notecard; run them withforth-runorruby-run. - Files are UTF-8 text documents in a sandboxed file store. Manage them
with
file-write,file-read,files, andfile-delete. They are not executable unless their contents are submitted as program source.
/1111 file-write ideas.txt build a counter notecard
/1111 file-read ideas.txt
/1111 files
/1111 file-delete ideas.txt
/1111 notecard Counter
/1111 notecards
/1111 forth-run Counter
/1111 notecard-delete Counter
Browser-to-Second-Life Bridge
The bridge lets the browser console queue Forth or RubyForth source. A Second Life object with this script polls the queue and evaluates one queued job at a time.
Server Setup
Set a nonempty secret before starting the server:
export MSSL_FORTH_BRIDGE_TOKEN='replace-with-a-long-random-token'
Set that exact same value in the script:
string BRIDGE_TOKEN = "replace-with-a-long-random-token";
Do not publish the token in a public script. Anyone who knows it can enqueue jobs for a bridge-enabled object.
Object Setup
After saving or resetting the configured script:
/1111 bridge-on
The object polls every three seconds. Stop polling with:
/1111 bridge-off
bridge-poll sends one immediate poll request, which is useful while
diagnosing connectivity.
Queue a Job
Open https://your-server.example/forth/ui. Paste Forth source, enter the
bridge token, and choose Queue Forth for SL. For example:
"Queued job reached Second Life" sl.owner_say
Use Queue Ruby for SL only for RubyForth computation or output. RubyForth
does not currently produce sl.* calls, so it will not modify the in-world
object.
Troubleshooting
| Symptom | Check |
|---|---|
| No startup message | Confirm the object contains a saved script and inspect the Second Life script error window. |
| No command response | Use /1111, confirm you own the object, and verify SERVERURL is reachable over HTTPS. |
| Output appears in the wrong place | Send /1111 channel 0, or use a *-say command with an explicit channel. |
| Forth reports stack underflow | Supply the word's inputs. For sl.say, push channel and then message. |
| Ruby parsing fails | Use one statement per line, omit semicolons, and use only the documented RubyForth subset. |
| Notecard upload fails | Ensure the notecard is in the same object's inventory and its name exactly matches the command. |
| Bridge refuses to start | Set a nonempty BRIDGE_TOKEN in the script. |
| Bridge receives no job | Ensure the server and script use the same MSSL_FORTH_BRIDGE_TOKEN, then use /1111 bridge-poll. |
| Variable text is malformed | Avoid special characters listed in the variable warning until escaping is corrected. |
| Object action is ignored | Confirm it came from Forth rather than RubyForth, and check normal Second Life permissions and parcel restrictions. |
Practical Workflow
- Use
/1111 forth ...for quick arithmetic or a short test. - Move multiline logic into a named inventory notecard.
- Upload with
/1111 notecard Nameafter every notecard edit. - Use
forth-runfor object actions andruby-runfor compact control-flow programs and server-side calculations. - Enable the bridge only when browser-queued work must reach a specific in-world object.
RubyForth Guide
Second Life RustForth Service Guide
Relote.Stack.Service.lsl is an owner-controlled Second Life client for the
Tide/RustForth server. It accepts chat commands, sends HTTPS requests to the
server, and can perform allowlisted server-generated actions in the object that
contains the script.
This guide covers chat commands, Forth, RubyForth, persisted notecards, matrices, files, and the optional browser-to-Second-Life bridge.
For a detailed explanation of the LSL implementation, its event handlers, asynchronous requests, and polling behavior, see LSL_CLIENT_REFERENCE.md.
For the complete HTTP/Tide route reference, see TIDE_ROUTE_MANUAL.md.
Before using real objects or bridge jobs, run the copyable isolation and bridge
checks in the Battle-Test Checklist section of the route manual.
Install and Configure
- Create or select a Second Life object that you own.
- Add
Relote.Stack.Service.lslto the object's inventory. Edit these values at the top of the script:
string SERVERURL = "https://your-server.example"; string BRIDGE_TOKEN = ""; integer CHANNEL_INPUT = 1111; integer CHANNEL_OUTPUT = 0;Save or reset the script. The owner receives a startup message.
Type commands in nearby chat, prefixed with the input channel. With the default configuration, every command begins with
/1111.
Only the object's owner can use this script. Commands from other avatars are ignored.
First Successful Request
Set the output channel to public chat, then run a Forth expression:
/1111 channel 0
/1111 forth 2 3 + .
The first command selects where ordinary results are sent. The second sends
Forth source to POST /forth/eval; . removes the top value from the Forth
stack and includes it in the response output. You should see 5 in public
chat.
Use a nonzero channel when results should not be public:
/1111 channel 42
/1111 forth 6 7 * .
/1111 forth-say 42 10 20 + .
Chat Command Reference
All commands below assume the default input channel 1111.
| Command | Purpose | Example |
|---|---|---|
channel <number> |
Set the normal output channel. | /1111 channel 0 |
help |
Send owner-only syntax help. | /1111 help |
session <name> |
Select a named language session. | /1111 session workshop_1 |
session-reset |
Return to this prim's UUID session. | /1111 session-reset |
forth <source> |
Run a one-line Forth program. | /1111 forth 9 4 - . |
forth-say <channel> <source> |
Run Forth on a chosen channel. | /1111 forth-say 42 2 8 * . |
ruby <source> |
Run one RubyForth statement or expression. | /1111 ruby puts 2 + 3 |
ruby-say <channel> <source> |
Run RubyForth on a chosen channel. | /1111 ruby-say 42 puts "hello" |
algebra <expression> |
Analyze an integer polynomial. | /1111 algebra 3*x^2 - 2*x + 7 |
algebra-say <channel> <expression> |
Send algebra output to a chosen channel. | /1111 algebra-say 42 x^2 + 2*x + 1 |
set <name> <value> |
Store a server variable. | /1111 set greeting hello |
get <name> |
Read a server variable. | /1111 get greeting |
view |
List stored variables. | /1111 view |
delete <name> |
Delete one stored variable. | /1111 delete greeting |
clear |
Clear the server variable store. | /1111 clear |
status |
Request variable-store status. | /1111 status |
history |
Request variable-store history. | /1111 history |
file-write <name> <text> |
Save text in the sandboxed file store. | /1111 file-write note.txt hello |
file-read <name> |
Read a sandboxed file. | /1111 file-read note.txt |
file-read-say <channel> <name> |
Read a file on a chosen channel. | /1111 file-read-say 42 note.txt |
files |
List sandboxed files. | /1111 files |
file-delete <name> |
Delete a sandboxed file. | /1111 file-delete note.txt |
notecard <inventory-name> |
Upload an inventory notecard. | /1111 notecard Counter |
notecards |
List uploaded notecards. | /1111 notecards |
notecard-get <name> |
Fetch an uploaded notecard. | /1111 notecard-get Counter |
notecard-delete <name> |
Delete an uploaded notecard. | /1111 notecard-delete Counter |
forth-run <name> |
Run an uploaded Forth notecard. | /1111 forth-run Counter |
forth-run-say <channel> <name> |
Run a Forth notecard on a chosen channel. | /1111 forth-run-say 42 Counter |
ruby-run <name> |
Compile and run RubyForth. | /1111 ruby-run RubyCounter |
ruby-run-say <channel> <name> |
Run RubyForth on a chosen channel. | /1111 ruby-run-say 42 RubyCounter |
matrix-get <name> |
Read a persisted matrix. | /1111 matrix-get A |
matrix-get-say <channel> <name> |
Read a matrix on a chosen channel. | /1111 matrix-get-say 42 A |
bridge-on |
Start polling for browser-queued bridge jobs. | /1111 bridge-on |
bridge-off |
Stop bridge polling. | /1111 bridge-off |
bridge-poll |
Trigger one bridge poll immediately. | /1111 bridge-poll |
The command parser splits on spaces. Use chat commands for short, single-line source and use notecards for multiline Forth or RubyForth.
Server Variables
The set, get, view, delete, clear, status, and history commands
use the server variable store. Values are server-persistent; the kv list in
the LSL script is only a local response cache.
/1111 set score 125
/1111 get score
/1111 view
/1111 delete score
Avoid backslashes and these characters in names or values: :, [, ], {,
}, single quotes, double quotes, and periods. The current LSL escape/unescape
implementation does not round-trip those characters correctly. Plain names such
as score, player_name, and simple text values are safe.
Forth Basics
Forth is stack-based. Numbers and strings are pushed onto the stack. Most words pop their inputs from the stack and push a result.
2 3 + .
This pushes 2, pushes 3, adds them, then prints 5.
| Group | Words |
|---|---|
| Arithmetic | +, -, *, /, mod, abs, min, max |
| Comparisons | =, !=, <, <=, >, >=, 0= |
| Boolean | true, false, nil, not, and, or |
| Stack | dup, drop, swap, over |
| Output | ., puts, p |
| Time/randomness | now, rand |
| Control | if ... else ... then, begin ... until, begin ... again, bye |
| Variables | let, variable, @, !, $name, name=, name+=, name-= |
| Named words | : name ... ; |
Numbers are signed integers. / is integer division. Strings use double
quotes; a backslash begins a Forth comment outside a string.
Variables
Variables persist in the same server store used by Forth, RubyForth, matrices, and notecards.
let total
25 total !
$total 5 + total!
$total puts
total pushes a variable address for ! and @. $total pushes its current
value. The concise forms total!, total+=, and total-= assign, add, and
subtract respectively.
Named Forth Words
You can give a sequence of Forth tokens a name and use that name later in the same program or notecard. This is the main new tool for breaking a larger program into reusable pieces.
: square dup * ;
: announce-square square puts ;
6 announce-square
Read the first definition as: βdefine the word square; when it is used, copy
the top stack value with dup, multiply the copy and original with *, then
finish the definition at ;.β The second definition calls the first, then
prints its result. The final line pushes 6 and calls announce-square, which
prints 36.
Definitions are collected before execution, so a word may call another word
defined later in the same source. They are expanded before the interpreter runs,
which keeps existing loops and safety limits unchanged. Recursive definitions,
such as : forever forever ;, are rejected. Do not reuse the name of a built-in
word such as if, puts, or matrix.
Semicolon-Separated Forth
Outside a : name ... ; definition, a semicolon is a statement separator. This
is useful in one-line chat commands:
/1111 forth 2 3 + puts; : square dup * ; 4 square puts;
The output is 5 16. The semicolon after square still closes the definition;
the other semicolons simply separate sequential blocks.
For a short definition in chat:
/1111 forth : square dup * ; 9 square puts
Use a notecard for multi-line definitions.
Explicit Execution Budgets
The server defaults to 10,000 interpreter steps and accepts at most 1,000,000. Long loops now have direct LSL commands that set this budget explicitly:
/1111 forth-steps 100000 : square dup * ; 250 square puts
/1111 ruby-steps 100000 puts 2 + 3
/1111 forth-run-steps 100000 Counter
/1111 ruby-run-steps 100000 RubyCounter
The second argument to each *-steps command must be an integer from 1 to
1000000. A larger budget permits more computation, but does not remove the
server's resource limit.
Forth and RubyForth in One Program
The ruby Forth directive compiles its quoted RubyForth source into the
surrounding Forth program. The compiled code uses the same variables, named
words, stack, output response, and allowed Second Life calls as the containing
Forth source.
: square dup * ;
6 square puts
ruby "count = 4\nputs count * 3"
This prints 36 from Forth, then 12 from RubyForth. The \n is a newline
inside the quoted RubyForth source. In a notecard, a multiline Ruby snippet is
also allowed inside the quoted string.
RubyForth can send a quoted Forth source string back through the same expander:
forth ": double dup + ; 21 double puts"
puts "RubyForth continues after Forth"
The first line defines and calls a Forth word, printing 42; the second line
is ordinary RubyForth and prints its text afterward. A forth bridge statement
must contain exactly one quoted Forth source string.
For nearby chat, use the new Forth-originated alias:
/1111 hybrid : square dup * ; 6 square puts ruby "puts 2 + 3"
/1111 hybrid-say 42 ruby "puts 6 * 7"
hybrid uses /forth/eval, so it can start with Forth and include one or more
ruby "..." directives. Regular /1111 forth accepts the same bridge syntax;
hybrid only makes that intent explicit in chat.
Forth Notecard Examples
Create a notecard with the exact inventory name shown, paste its contents, place it in the same object as the script, and upload it with:
/1111 notecard <NotecardName>
Wait for the object to report that it is loading the notecard, then run it with
/1111 forth-run <NotecardName>.
What a notecard is and what happens to it
A notecard is an inventory item inside the Second Life object. It is not a file on your computer, and it is not automatically a program. The script reads it one line at a time, sends its text to the server, and stores a server-side copy under the same name.
Use this exact sequence for every example:
- Create a new notecard in the object's inventory and name it exactly as the
example heading says. For example, name the first one
Counter. - Open the notecard and paste only the code inside its matching code block. Do not paste the Markdown backticks, the heading, or explanatory prose.
- Save the notecard and close it.
- In nearby chat, as the object owner, enter
/1111 notecard Counter. - The LSL script asks Second Life for each line. When it reaches the end, it
uploads the complete text to the server as the remote program named
Counter. - Wait for the loading message to appear. Large notecards can take longer than one chat message because Second Life returns their lines asynchronously.
- Run the uploaded copy with
/1111 forth-run Counter. - Read the result on the configured output channel. Set that channel first
with
/1111 channel 0if you want public-chat output.
The inventory name and the command argument are case-sensitive in practice:
use the same spelling every time. Editing the local notecard does not update the
server copy. Run /1111 notecard Counter again after every edit, then rerun
/1111 forth-run Counter.
forth-run executes source as Forth. ruby-run executes the same uploaded
source as RubyForth. Do not run a Forth example with ruby-run, and do not run
a RubyForth example with forth-run.
Counter
let counter
3 counter !
begin
$counter puts
1 counter-=
$counter 0=
until
Expected output: 3, 2, 1.
Condition
let temperature
23 temperature !
$temperature 20 > if
"warm" puts
else
"cool" puts
then
StackTools
10 20 over . . .
over copies the second stack item. This example outputs 10, 20, then
10.
RandomNumber
100 rand puts
Outputs an integer from 0 through 99.
Greeting
"Hello, Second Life" puts
"Hello, " "RustForth" + puts
MatrixDeterminant
2 2 matrix A
1 0 0 mset A
2 0 1 mset A
3 1 0 mset A
4 1 1 mset A
m.show A
m.det A puts
This creates a two-by-two matrix and prints its serialized form followed by
determinant -2.
MatrixScale
2 m.identity I
5 m.scale I FiveI
m.show FiveI
MatrixSolve
2 2 matrix A
2 0 0 mset A
0 0 1 mset A
2 1 0 mset A
1 1 1 mset A
2 1 matrix B
4 0 0 mset B
6 1 0 mset B
m.solve A B X
m.show X
This solves $A X = B$ when the solution contains exact integers.
Matrix Reference
Matrices persist server-side. The maximum dimension is 64 and the maximum
number of cells is 4096.
| Word | Stack/source form | Result |
|---|---|---|
matrix |
<rows> <cols> matrix <name> |
Create a zero matrix. |
m.identity |
<size> m.identity <name> |
Create an identity matrix. |
mset |
<value> <row> <col> mset <name> |
Set one cell. |
mget |
<row> <col> mget <name> |
Push one cell. |
m.fill |
<value> m.fill <name> |
Fill every cell. |
m.scale |
<scalar> m.scale <source> <target> |
Scale a matrix. |
m.add |
m.add <left> <right> <target> |
Add matrices. |
m.sub |
m.sub <left> <right> <target> |
Subtract matrices. |
m.mul |
m.mul <left> <right> <target> |
Multiply matrices. |
m.solve |
m.solve <left> <right> <target> |
Solve $A X = B$ with exact integers. |
m.transpose |
m.transpose <source> <target> |
Transpose a matrix. |
m.det |
m.det <name> |
Push determinant. |
m.rows |
m.rows <name> |
Push row count. |
m.cols |
m.cols <name> |
Push column count. |
m.show |
m.show <name> |
Emit serialized matrix output. |
Use /1111 matrix-get <name> to retrieve a stored matrix outside a Forth
program.
Forth-Controlled Second Life Actions
The Forth runtime does not directly run arbitrary LSL. Instead, it returns an
allowlisted calls list, and this script performs each call in the object.
These actions work with forth, forth-run, and Forth bridge jobs. At most 128
host calls are allowed in one Forth program.
The action is performed when this LSL client receives the evaluation response,
so a direct command such as /1111 forth ... can cause the action immediately.
Speak in chat
0 "Hello from the object" sl.say
0 "A quiet hello" sl.whisper
0 "Attention" sl.shout
42 "Private channel message" sl.region_say
"Only the owner sees this" sl.owner_say
The stack order for the first four forms is <channel> <message>.
Change floating text, color, and alpha
"Ready" 0 255 0 255 sl.set_text
255 0 0 -1 sl.set_color
128 -1 sl.set_alpha
Color and alpha components accept either 0 through 1 or 0 through 255.
-1 means all faces for sl.set_color and sl.set_alpha.
Play a sound
Replace the UUID with a sound asset UUID in the object's inventory or a sound that the object is permitted to play:
"01234567-89ab-cdef-0123-456789abcdef" 1 sl.play_sound
The stack order is <sound> <volume>.
Object timer, position, and linked messages
5 sl.set_timer
128 128 25 sl.set_region_pos
sl.set_timer invokes llSetTimerEvent; it does not add custom timer handling
to this script. The existing timer event is used for bridge polling, so do not
use sl.set_timer while bridge polling is enabled unless changing the poll
interval is intentional.
sl.set_region_pos requires the usual Second Life permissions and parcel
conditions. sl.link_message has stack order <link> <code> <message> <id>;
-1 is LINK_SET.
ObjectWelcome
"RustForth online" 0 200 100 255 sl.set_text
0 "Object is ready" sl.say
"Object is ready" sl.owner_say
PulseColor
0 128 255 -1 sl.set_color
255 -1 sl.set_alpha
0 "Color changed" sl.whisper
RubyForth
RubyForth is a small Ruby-shaped language compiled into the Forth runtime. It
shares persistent variables, arrays, hashes, matrices, notecards, and execution
budgets with Forth, but it is not MRI Ruby. It has no gems, require, eval,
classes, arbitrary host methods, arbitrary file access, or system commands.
Supported constructs are integer and string expressions, #{name}
interpolation, assignment, +=, -=, *=, /=, puts, p, if,
unless, elsif, else, while, until, n.times do, end, return,
one-line arrays/hashes, and collection helpers such as get, set, push,
delete, keys, length, and has.
Semicolons separate RubyForth statements outside strings, arrays, hashes, and parentheses. This makes multi-step direct chat commands possible:
/1111 ruby count = 1; puts count; count += 1; puts count
Use a notecard for multiline blocks, not merely because of semicolons.
/1111 ruby puts 2 + 3
/1111 ruby puts "Hello from RubyForth"
/1111 ruby-say 42 puts 6 * 7
RubyForth currently cannot call sl.say, sl.set_text, or any other sl.*
operation. Use a Forth notecard when the program must change the in-world
object.
Mixing RubyForth and Forth
Use a quoted one-line bridge when the Forth fragment is short:
count = 2
forth "$count 3 + count="
puts count
For a larger Forth fragment, use a multiline forth do ... end block:
count = 2; puts count
forth do
$count 3 + count=
$count puts
end
puts count
This emits 2 5 5: RubyForth initializes and prints count, Forth adds three
and prints it, and RubyForth reads the same persisted value afterward. Forth
inside this block can use named words, matrices, arrays, hashes, or allowed
sl.* calls. RubyForth resumes after the block using the same execution budget.
Forth can call a short RubyForth fragment in the other direction:
ruby "count = 4; puts count * 3"
Per-Prim Sessions
At startup, the LSL client sets its language session to the current prim UUID. That means Forth/RubyForth state from two objects using the same server can be isolated. Change the active scope only when you intentionally want to share state:
/1111 session workshop_1
/1111 ruby score = 10; puts score
/1111 session-reset
Session names must use letters, numbers, _, or -, and be at most 64
characters. Existing API callers that omit session_id keep using the legacy
default session.
RubyForth Notecard Examples
Upload each notecard with /1111 notecard <NotecardName>, then run it with
/1111 ruby-run <NotecardName>.
RubyCounter
count = 0
while count < 3
puts count
count += 1
end
Expected output: 0, 1, 2.
RubyTimes
5.times do
puts "tick"
end
RubyCondition
score = 80
if score >= 90
puts "A"
elsif score >= 80
puts "B"
else
puts "Needs work"
end
RubyUnless
enabled = false
unless enabled
puts "Feature is disabled"
end
RubyArithmetic
total = 4
total *= 3
total += 2
puts total
Expected output: 14.
RubyStrings
first = "Second"
second = "Life"
puts first + " " + second
RubyUntil
remaining = 3
until remaining == 0
puts remaining
remaining -= 1
end
Files and Notecard Management
The server has two separate persistence mechanisms:
- Notecards store source programs in the partitioned server store. Upload
inventory notecards with
notecard; run them withforth-runorruby-run. - Files are UTF-8 text documents in a sandboxed file store. Manage them
with
file-write,file-read,files, andfile-delete. They are not executable unless their contents are submitted as program source.
/1111 file-write ideas.txt build a counter notecard
/1111 file-read ideas.txt
/1111 files
/1111 file-delete ideas.txt
/1111 notecard Counter
/1111 notecards
/1111 forth-run Counter
/1111 notecard-delete Counter
Browser-to-Second-Life Bridge
The bridge lets the browser console queue Forth or RubyForth source. A Second Life object with this script polls the queue and evaluates one queued job at a time.
Server Setup
Set a nonempty secret before starting the server:
export MSSL_FORTH_BRIDGE_TOKEN='replace-with-a-long-random-token'
Set that exact same value in the script:
string BRIDGE_TOKEN = "replace-with-a-long-random-token";
Do not publish the token in a public script. Anyone who knows it can enqueue jobs for a bridge-enabled object.
Object Setup
After saving or resetting the configured script:
/1111 bridge-on
The object polls every three seconds. Stop polling with:
/1111 bridge-off
bridge-poll sends one immediate poll request, which is useful while
diagnosing connectivity.
Queue a Job
Open https://your-server.example/forth/ui. Paste Forth source, enter the
bridge token, and choose Queue Forth for SL. For example:
"Queued job reached Second Life" sl.owner_say
Use Queue Ruby for SL only for RubyForth computation or output. RubyForth
does not currently produce sl.* calls, so it will not modify the in-world
object.
Troubleshooting
| Symptom | Check |
|---|---|
| No startup message | Confirm the object contains a saved script and inspect the Second Life script error window. |
| No command response | Use /1111, confirm you own the object, and verify SERVERURL is reachable over HTTPS. |
| Output appears in the wrong place | Send /1111 channel 0, or use a *-say command with an explicit channel. |
| Forth reports stack underflow | Supply the word's inputs. For sl.say, push channel and then message. |
| Ruby parsing fails | Use one statement per line, omit semicolons, and use only the documented RubyForth subset. |
| Notecard upload fails | Ensure the notecard is in the same object's inventory and its name exactly matches the command. |
| Bridge refuses to start | Set a nonempty BRIDGE_TOKEN in the script. |
| Bridge receives no job | Ensure the server and script use the same MSSL_FORTH_BRIDGE_TOKEN, then use /1111 bridge-poll. |
| Variable text is malformed | Avoid special characters listed in the variable warning until escaping is corrected. |
| Object action is ignored | Confirm it came from Forth rather than RubyForth, and check normal Second Life permissions and parcel restrictions. |
Practical Workflow
- Use
/1111 forth ...for quick arithmetic or a short test. - Move multiline logic into a named inventory notecard.
- Upload with
/1111 notecard Nameafter every notecard edit. - Use
forth-runfor object actions andruby-runfor compact control-flow programs and server-side calculations. - Enable the bridge only when browser-queued work must reach a specific in-world object.