The Complete Trit Programming Language Theory in Ruby and Rust (MindWeave
🔗(33) | All📅2025-11-15 22:04:28 -0800
⏲️🔐2025-11-15 21:30:12 -0800 | ✍️infinivaeria | 🏷️[Game Development] [Trit Development] [SPECIAL] [VERY_IMPORTANT] [IT_AGREEMENT] [THE_STINKIEST_CODE_OF_ALL_TIME]
(🪟)
🖥️...⌨️
Extending Dirac’s Bra–Ket Notation to a 3‑State Computation System
Theoretical Framework
In quantum mechanics, Dirac’s bra–ket notation is a powerful formalism for representing states and operations using vector-like symbols. A ket such as ∣ψ⟩ ∣ψ⟩ denotes a state vector (e.g. ∣0⟩ ∣0⟩ or ∣1⟩ ∣1⟩ for a qubit), and the corresponding bra ⟨ψ∣ ⟨ψ∣ denotes its dual (the conjugate transpose row vector). An inner product between states appears as a “bra-ket” ⟨ϕ∣ψ⟩ ⟨ϕ∣ψ⟩, producing a scalar amplitude. Operators (transformations) are inserted between a bra and a ket: for example, ⟨x∣O∣z⟩ ⟨x∣ O ^ ∣z⟩ represents the matrix element of operator O^ O ^ mapping state ∣z⟩ ∣z⟩ to state ∣x⟩ ∣x⟩. Bra–ket notation concisely captures how quantum states and processes (operators) relate, something we aim to mirror in a new three-part form.
Extending to a 3-state system: We introduce a notation ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ as an analogue of Dirac’s bracket, but with three components: an initial state or input x x, a process or transformation y y, and a resulting state or output z z. This triple can be read as “ y y transforms x x into z z.” It echoes the structure of a quantum amplitude ⟨output∣O∣input⟩ ⟨output∣ O ^ ∣input⟩, except here we treat the transformation y y as an explicit part of the tuple rather than an operator between bra and ket. In classical computing terms, it parallels the fundamental input–process–output model of computation. Just as a classical program takes an input and produces output, our notation ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ encapsulates a computational step with x x as input, y y as the operation, and z z as the output. This structure has a strong resemblance to the Hoare triple in programming logic {P} C {Q} {P}C{Q}, where P P is a precondition, C C a command, and Q Q the postcondition. In fact, ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ can be seen as a computational state transformer: when the pre-state satisfies condition x x, executing process y y yields post-state z z. Unlike Hoare logic (which is typically propositional, describing conditions), our notation treats x,y,z x,y,z as data or states themselves, making it a more concrete “executable” representation.
Inspiration from quantum 3-state systems: The “3-state qubit” concept corresponds to a qutrit, a quantum system with three basis states (often ∣0⟩ ∣0⟩, ∣1⟩ ∣1⟩, ∣2⟩ ∣2⟩). A qutrit can exist in a superposition α∣0⟩+β∣1⟩+γ∣2⟩ α∣0⟩+β∣1⟩+γ∣2⟩, with complex amplitudes α,β,γ α,β,γ obeying ∣α∣2+∣β∣2+∣γ∣2=1 ∣α∣ 2 +∣β∣ 2 +∣γ∣ 2 =1. Our notation ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ is not exactly a quantum state, but it is inspired by the idea of a ternary basis. Conceptually, one might think of x x, y y, z z as inhabiting three different “spaces” or roles (input space, process space, output space), analogous to a triple tensor product of spaces. This is a departure from standard bra–ket which has only two spaces (bra and ket), but it opens up new possibilities. In quantum terms, we could interpret ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ as a kind of transition amplitude from ∣x⟩ ∣x⟩ to ∣z⟩ ∣z⟩ via an intermediate operator/state y y. Standard Dirac notation would write something like ⟨z∣U∣x⟩ ⟨z∣U∣x⟩ for the amplitude of obtaining z z from x x under operation U U. Here we elevate U U (the process) to the middle of our bracket as y y for symmetry and generality, treating it on par with initial and final states.
Directional arrows ( → →, ← ←): We extend the notation with arrows to indicate the direction of computation or inference. A forward arrow ⟨x∣y→z⟩ ⟨x∣y→z⟩ denotes that applying process y y to input x x yields output z z. On the other hand, a backward arrow ⟨x←y∣z⟩ ⟨x←y∣z⟩ would indicate that we are using process y y in reverse (or solving for the input) given output z z. This is analogous to the concept of reversible computing, where every computation step is invertible. In a reversible system, if x→yz x y z, then there exists an inverse process y−1 y −1 such that z→y−1x z y −1 x. Using arrows in the bracket makes the direction explicit: one can think of → → as a “time-forward” evolution and ← ← as a “time-reversed” or inverse operation. For example, if y y is a function (or quantum gate) that maps 3 to 9 (say, squaring: y(n)=n2 y(n)=n 2 ), we write ⟨3∣Square→9⟩ ⟨3∣Square→9⟩. The inverse would be ⟨3←Square∣9⟩ ⟨3←Square∣9⟩, signifying that from output 9, we deduce the original input 3 by the inverse operation (square root). This mirrors the bra–ket duality: in Dirac notation the “adjoint” (Hermitian conjugate) of an operator corresponds to running the operation in reverse. Here, swapping the arrow from → → to ← ← and exchanging x x and z z conceptually gives the adjoint triple ⟨z∣y−1∣x⟩ ⟨z∣y −1 ∣x⟩. This property aligns with quantum operations being reversible (unitary) transformations.
Data structure view: Crucially, we treat ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ as a computable data structure or algebraic object, not just a notation for abstract math. Each triple encapsulates a piece of computation (like a record with fields for input, process, output). Because it’s a structured entity, we can imagine manipulating these triples with computer code – combining them, transforming them, and executing them. This idea draws on the concept of arrows in computer science (as defined by John Hughes), which generalize functions to describe computations with both inputs and outputs in a composable way. In Haskell’s arrow framework, for instance, one can compose two computations f and g using an operator like >>> if the output type of f matches the input type of g. Similarly, with our triples, if we have ⟨a∣y∣b⟩ ⟨a∣y∣b⟩ and ⟨b∣y′∣c⟩ ⟨b∣y ′ ∣c⟩ (the output of the first matches the input of the second), we can concatenate or compose them to get ⟨a∣y;y′∣c⟩ ⟨a∣y;y ′ ∣c⟩. This composition behaves like function composition or matrix multiplication of operators, a key property for building complex computations from simpler ones. We will demonstrate such composition with Ruby code shortly, treating the triple as a first-class object.
Bridging quantum and classical paradigms: The triple notation provides a framework to compare different computational paradigms in a unified way. In classical computing, we usually consider input and algorithm as given, and we deterministically produce output. In machine learning, one often has input and output examples and tries to infer the model (the process) that maps them. Interestingly, in some formulations of quantum computing, one might view certain problems “backwards” – for instance, Grover’s algorithm can be seen as taking a known output condition and finding an input that satisfies it, with the quantum algorithm (process) guiding the search. Our ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ can, in principle, represent all these scenarios by leaving one of the components unknown and solving for it. With an arrow marking the direction, we could denote a quantum algorithm’s inversion as ⟨?←Grover∣solution⟩ ⟨?←Grover∣solution⟩ meaning “given the desired output solution, find the input that produces it via Grover’s process,” illustrating how quantum computing sometimes “takes the output and model as given and produces the input probabilistically”. This flexibility suggests philosophical parallels to how knowledge is represented: the triple encapsulates a relation among cause (input), effect (output), and transformation (law) – much like how physical laws relate initial and final states. Dirac notation itself was designed to seamlessly describe superpositions and transformations in physics, and by extending it, we inch toward a language that might describe not only quantum states but also computational processes in an integrated formalism.
Ternary logic and beyond: Considering practical computing, using a three-state notation resonates with the idea of ternary (base-3) computation, which has been studied as an alternative to binary. Ternary logic is theorized to be more efficient in certain hardware contexts – it’s been mathematically shown that a three-level signal can be the optimal encoding in terms of minimal energy or information density. In fact, engineers have built ternary computers (like the Soviet Setun in 1958) that showed potential advantages in speed and cost. The interest in “beyond binary” is growing, as three-state devices (e.g. multi-level memory cells, memristors, quantum qutrits) become feasible. Our notation ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ could serve as a conceptual tool for ternary computing models, since it inherently calls out three components. For example, one might use it to represent a balanced ternary operation with x∈{−1,0,1} x∈{−1,0,1}, z∈{−1,0,1} z∈{−1,0,1}, and y y describing some ternary logic gate. The notation “allows thinking beyond black and white” (beyond Boolean), as one researcher quipped, analogous to how a third truth value in logic (e.g. “unknown” or “indeterminate”) adds nuance to reasoning. While our primary interpretation is not limited to any specific values of x,y,z x,y,z, it’s encouraging that the form aligns with emerging hardware and logic paradigms that inherently use three states.
In summary, the ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ notation generalizes Dirac’s bracket to explicitly include the transformation alongside initial and final states. It aligns with quantum notation (where an operator connects bra to ket) but elevates the operator to equal footing as a middle “state” or label. By incorporating directionality and treating the entire triple as a manipulable entity, we get a formalism that is both mathematically inspired and suitable as a pseudocode-like language construct. Next, we demonstrate how one might implement and experiment with this concept in Ruby, treating ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ as a data structure with which we can perform operations.
Ruby Code Implementation of the 3-State System
We can simulate the ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ notation in Ruby by creating a custom class to hold the three components and define operations on them. Ruby is a flexible, dynamic language, which allows us to override operators and create domain-specific representations easily. Below is a step-by-step implementation and demonstration:
# ============================================================
# TritLang v1.3.0 (ASCII-Only Edition)
# Extensible Triadic + Quantum (Qubit/Qutrit) + Compiler Diagnostics
# - Modular architecture, plugin-ready registries
# - Arbitrary unit-circle parameterization for complex amplitudes
# - Gate abstractions with unitary checks, composition, codegen
# - Clean REPL commands for quantum and triadic workflows
# ============================================================
class TritLang
VERSION = "1.3.0"
# ----------------------------------------------------------
# Diagnostics and Config
# ----------------------------------------------------------
def self.info(msg) = puts("INFO: #{msg}")
def self.warn(msg) = puts("WARN: #{msg}")
def self.error(msg) = puts("ERROR: #{msg}")
def self.assert(cond, msg)
return true if cond
error(msg)
raise RuntimeError, msg
end
def self.cfg
@cfg ||= {
float_tol: 1e-9,
complex_fmt_digits: 3
}
end
# ----------------------------------------------------------
# Types
# ----------------------------------------------------------
INT = :Int
FLOAT = :Float
BOOL = :Bool
STRING = :String
ARRAY = :Array
ANY = :Any
def self.type_of(value)
case value
when Integer then INT
when Float then FLOAT
when TrueClass, FalseClass then BOOL
when String then STRING
when Array then ARRAY
when NilClass then ANY
else ANY
end
end
def self.types_compatible?(a, b)
return true if a == b || a == ANY || b == ANY
return true if a == INT && b == FLOAT # widening
false
end
# ----------------------------------------------------------
# Triad Core (Composable, Relabelable, Adjointable)
# ----------------------------------------------------------
class Triad
attr_accessor :input, :process_label, :output, :direction, :operation, :meta
def initialize(input, process_label, output, direction = :forward, meta: {}, &operation)
@input = input
@process_label = process_label.to_s
@output = output
@direction = direction # :forward or :backward
@operation = operation # Proc implementing y or y_inv
@meta = meta # extensible annotations
end
def to_s
case @direction
when :forward then "<#{@input} | #{@process_label} -> #{@output}>"
when :backward then "<#{@input} <- #{@process_label} | #{@output}>"
else "<#{@input} | #{@process_label} ? #{(@output.nil? ? '' : @output)}>"
end
end
def draw
arrow = (@direction == :forward ? "-->" : "<--")
i = @input.to_s; o = @output.to_s; p = @process_label.to_s
w = [i.size, o.size, p.size + 8].max + 8
box = "+" + "-" * (w - 2) + "+"
line = ->(txt) { "| #{txt.to_s.center(w - 4)} |" }
<<~DIAGRAM
#{box}
#{line.call(i)}
#{box}
#{arrow} #{p}
#{box}
#{line.call(o)}
#{box}
DIAGRAM
end
def types
{ input: TritLang.type_of(@input), output: TritLang.type_of(@output) }
end
def typecheck!
ti = types[:input]; to = types[:output]
if @operation
TritLang.info("Typecheck soft-pass: operation provided (input=#{ti}, output=#{to})")
return true
end
TritLang.assert(TritLang.types_compatible?(ti, to), "Typecheck failed: #{ti} !~ #{to}")
TritLang.info("Typecheck OK: input=#{ti}, output=#{to}")
true
end
def valid?
return true unless @operation
case @direction
when :forward
begin
res = @operation.call(@input)
ok = (res == @output)
ok ? TritLang.info("Execute y(x): got=#{res.inspect}, expected=#{@output.inspect}") :
TritLang.error("Mismatch: y(x)=#{res.inspect} != #{@output.inspect}")
ok
rescue => e
TritLang.error("Execution error: #{e.message}")
false
end
when :backward
TritLang.assert(@operation, "Backward triad requires inverse operation")
begin
res = @operation.call(@output)
ok = (res == @input)
ok ? TritLang.info("Execute y_inv(z): got=#{res.inspect}, expected=#{@input.inspect}") :
TritLang.error("Mismatch: y_inv(z)=#{res.inspect} != #{@input.inspect}")
ok
rescue => e
TritLang.error("Execution error: #{e.message}")
false
end
else
TritLang.error("Unknown direction")
false
end
end
def compose(other)
TritLang.info("Try compose: #{self.to_s} o #{other.to_s}")
TritLang.assert(self.direction == :forward, "Left triad must be forward")
TritLang.assert(other.direction == :forward, "Right triad must be forward")
TritLang.assert(self.output == other.input, "State mismatch: #{self.output.inspect} != #{other.input.inspect}")
composed_label = optimize_label("#{self.process_label}; #{other.process_label}")
composed_meta = (self.meta || {}).merge(other.meta || {})
composed_op =
if self.operation && other.operation
proc { |x| other.operation.call(self.operation.call(x)) }
else
nil
end
tri = Triad.new(self.input, composed_label, other.output, :forward, meta: composed_meta, &composed_op)
TritLang.info("Compose OK: #{tri.to_s}")
tri
end
def adjoint(&inverse_op)
tri = Triad.new(self.output, "#{@process_label}_adj", self.input, :backward, meta: @meta, &inverse_op)
TritLang.info("Adjoint built: #{tri.to_s}")
tri
end
def relabel(&block)
Triad.new(@input, block.call(@process_label), @output, @direction, meta: @meta, &@operation)
end
def codegen
case @direction
when :forward
step =
if process_label =~ /\AAdd(\d+)\z/
"ADD #{$1}"
elsif process_label =~ /\AMul(\d+)\z/
"MUL #{$1}"
else
"CALL #{process_label}"
end
"LOAD #{input.inspect}; #{step}; STORE #{output.inspect}"
when :backward
"LOAD #{output.inspect}; CALL INV(#{process_label}); STORE #{input.inspect}"
else
"NOP"
end
end
private
def optimize_label(label)
toks = label.split(";").map(&:strip)
if toks.all? { |t| t.start_with?("Add") && t.gsub("Add","") =~ /\A\d+\z/ }
sum = toks.sum { |t| t.gsub("Add","").to_i }
"Add#{sum}"
elsif toks.all? { |t| t.start_with?("Mul") && t.gsub("Mul","") =~ /\A\d+\z/ }
prod = toks.inject(1) { |acc, t| acc * t.gsub("Mul","").to_i }
"Mul#{prod}"
else
label
end
end
end
# ----------------------------------------------------------
# Plugin Registries (Functions and Gates)
# ----------------------------------------------------------
class Registry
def initialize
@funcs = {} # name => {label:, proc:, inverse:, meta:}
end
def define(name, label = nil, inverse_proc: nil, meta: {}, &proc_body)
TritLang.assert(block_given?, "Process body required")
@funcs[name.to_s] = { label: (label || name.to_s), proc: proc_body, inverse: inverse_proc, meta: meta }
TritLang.info("Defined process #{name}")
end
def get(name) = @funcs[name.to_s]
def list = @funcs.keys
def call(name, x) = (get(name) or raise "Unknown process: #{name}")[:proc].call(x)
def inverse(name, z)
f = get(name) or raise "Unknown process: #{name}"
inv = f[:inverse] or raise "No inverse for #{name}"
inv.call(z)
end
end
class GateRegistry
def initialize
@gates = {} # name => {U:, arity:, meta:}
end
def define(name, U:, arity:, meta: {})
TritLang.assert(arity == 1 || arity == 3, "Gate arity must be 1 (qubit) or 3 (qutrit)")
TritLang.assert(valid_unitary?(U), "Gate matrix must be unitary")
@gates[name.to_s] = { U: U, arity: arity, meta: meta }
TritLang.info("Defined gate #{name} (arity=#{arity})")
end
def get(name) = @gates[name.to_s]
def list = @gates.keys
private
def valid_unitary?(U)
# U is NxN complex matrix: each entry is [re, im]
TritLang.assert(U.is_a?(Array) && U.size > 0, "Unitary must be non-empty matrix")
n = U.size
TritLang.assert(U.all? { |r| r.is_a?(Array) && r.size == n && r.all? { |c| c.is_a?(Array) && c.size == 2 && c.all? { |v| v.is_a?(Numeric) } } }, "Invalid complex matrix shape")
# Check U*U^† ≈ I
I = identity_matrix(n)
UUdag = mat_mul(U, mat_conj_transpose(U))
near_equal_matrix(UUdag, I, TritLang.cfg[:float_tol])
end
def identity_matrix(n)
Array.new(n) { |i| Array.new(n) { |j| (i==j ? [1.0,0.0] : [0.0,0.0]) } }
end
def mat_conj_transpose(M)
n = M.size
Array.new(n) { |i|
Array.new(n) { |j|
c = M[j][i]
[c[0], -c[1]]
}
}
end
def c_add(a,b) = [a[0]+b[0], a[1]+b[1]]
def c_mul(a,b) = [a[0]*b[0]-a[1]*b[1], a[0]*b[1]+a[1]*b[0]]
def mat_mul(A,B)
n = A.size
Array.new(n) { |i|
Array.new(n) { |j|
s = [0.0,0.0]
n.times { |k| s = c_add(s, c_mul(A[i][k], B[k][j])) }
s
}
}
end
def near_equal_matrix(A,B,tol)
n = A.size
n.times do |i|
n.times do |j|
a = A[i][j]; b = B[i][j]
return false if (a[0]-b[0]).abs > tol || (a[1]-b[1]).abs > tol
end
end
true
end
end
# ----------------------------------------------------------
# Set Theory
# ----------------------------------------------------------
def self.set_union(a, b)
info("Set union: #{a.inspect} U #{b.inspect}")
Triad.new(a, "Union", a | b, :forward)
end
def self.set_intersection(a, b)
info("Set intersection: #{a.inspect} ^ #{b.inspect}")
Triad.new(a, "Intersection", a & b, :forward)
end
def self.set_difference(a, b)
info("Set difference: #{a.inspect} \\ #{b.inspect}")
Triad.new(a, "Difference", a - b, :forward)
end
def self.set_cartesian(a, b)
info("Cartesian product: #{a.inspect} x #{b.inspect}")
Triad.new([a,b], "CartesianProduct", a.product(b), :forward)
end
# ----------------------------------------------------------
# Trit Logic (-1, 0, 1)
# ----------------------------------------------------------
def self.trit_not(x)
assert([-1,0,1].include?(x), "Invalid trit: #{x}")
r = (x == -1 ? 1 : x == 0 ? 0 : -1)
info("Trit NOT #{x} => #{r}")
r
end
def self.trit_and(x,y)
assert([-1,0,1].include?(x) && [-1,0,1].include?(y), "Invalid trit AND args")
r = [x,y].min
info("Trit AND #{x},#{y} => #{r}")
r
end
def self.trit_or(x,y)
assert([-1,0,1].include?(x) && [-1,0,1].include?(y), "Invalid trit OR args")
r = [x,y].max
info("Trit OR #{x},#{y} => #{r}")
r
end
def self.trit_sum(x,y)
assert([-1,0,1].include?(x) && [-1,0,1].include?(y), "Invalid trit SUM args")
r = x + y
info("Trit SUM #{x}+#{y} => #{r}")
r
end
def self.trit_add2(a1,a0,b1,b0)
s0 = a0 + b0; carry = 0
if s0 > 1; s0 -= 3; carry = 1; elsif s0 < -1; s0 += 3; carry = -1; end
s1 = a1 + b1 + carry
if s1 > 1; s1 -= 3; carry = 1; elsif s1 < -1; s1 += 3; carry = -1; else carry = 0; end
info("Balanced trit add2 => [#{s1}, #{s0}, carry=#{carry}]")
[s1, s0, carry]
end
# ----------------------------------------------------------
# Algebra
# ----------------------------------------------------------
def self.identity(x)
t = Triad.new(x, "I", x, :forward) { |v| v }
info("Identity triad: #{t.to_s}")
t
end
def self.conditional(x, cond_proc, process_label, &block)
assert(block_given?, "Conditional requires process block")
if cond_proc.call(x)
z = block.call(x)
Triad.new(x, "if cond then #{process_label}", z, :forward) { |v| block.call(v) }
else
Triad.new(x, "if cond then #{process_label}", x, :forward) { |v| v }
end
end
def self.iterate(x, process_label, n, &block)
assert(block_given?, "Iterate requires process block")
result = x
n.times { result = block.call(result) }
composed_op = proc do |v|
acc = v
n.times { acc = block.call(acc) }
acc
end
Triad.new(x, "#{process_label}^#{n}", result, :forward, &composed_op)
end
# ----------------------------------------------------------
# Matrix Algebra (Real)
# ----------------------------------------------------------
def self.matrix_multiply(m1, m2)
assert(m1.is_a?(Array) && m2.is_a?(Array), "Matrix multiply requires arrays")
assert(!m1.empty? && !m2.empty?, "Empty matrix")
assert(m1.all? { |r| r.is_a?(Array) && !r.empty? } && m2.all? { |r| r.is_a?(Array) && !r.empty? }, "Matrices must be non-empty 2D arrays")
assert(m1.map(&:size).uniq.size == 1 && m2.map(&:size).uniq.size == 1, "Ragged matrix rows")
assert(m1[0].size == m2.size, "Matrix sizes incompatible")
rows = m1.size; cols = m2[0].size; inner = m1[0].size
result = Array.new(rows) { Array.new(cols, 0) }
rows.times do |i|
cols.times do |j|
inner.times do |k|
a = m1[i][k]; b = m2[k][j]
assert(a.is_a?(Numeric) && b.is_a?(Numeric), "Non-numeric matrix entry")
result[i][j] += a * b
end
end
end
info("Matrix multiply OK")
result
end
# ----------------------------------------------------------
# System/Eval Gates (Read-only shell, safe arithmetic)
# ----------------------------------------------------------
def self.system_call(label, command)
result = `#{command}`.strip
tri = Triad.new(nil, "sys:#{label}", result, :forward) { |_| result }
info("System call #{label}: #{result.inspect}")
tri
end
def self.safe_eval(expr)
allowed = expr =~ /\A[\d\s\+\-\*\/\(\)]+\z/
assert(!!allowed, "Disallowed code")
result = eval(expr)
tri = Triad.new(expr, "eval", result, :forward) { |code| eval(code) }
info("Eval: #{expr} => #{result}")
tri
end
# ----------------------------------------------------------
# DSL (ASCII brackets)
# ----------------------------------------------------------
RX_FWD = /\A[<]\s*(?<x>.+?)\s*\|\s*(?<y>.+?)\s*->\s*(?<z>.+?)\s*[>]\z/
RX_BWD = /\A[<]\s*(?<x>.+?)\s*<-\s*(?<y>.+?)\s*\|\s*(?<z>.+?)\s*[>]\z/
RX_NEU = /\A[<]\s*(?<x>.+?)\s*\|\s*(?<y>.+?)\s*\|\s*(?<z>.+?)\s*[>]\z/
def self.parse(str)
s = str.strip
if (m = RX_FWD.match(s))
Triad.new(coerce_literal(m[:x]), m[:y].strip, coerce_literal(m[:z]), :forward)
elsif (m = RX_BWD.match(s))
Triad.new(coerce_literal(m[:x]), m[:y].strip, coerce_literal(m[:z]), :backward)
elsif (m = RX_NEU.match(s))
Triad.new(coerce_literal(m[:x]), m[:y].strip, coerce_literal(m[:z]), :forward)
else
raise "Parse error: #{str}"
end
end
def self.coerce_literal(s)
return s if s.nil? || s.empty?
case s
when /\A-?\d+\z/ then s.to_i
when /\A-?\d+\.\d+\z/ then s.to_f
when /\Atrue\z/ then true
when /\Afalse\z/ then false
when /\Anil\z/ then nil
when /\A"(.*)"\z/ then $1
when /\A
\[(.*)\]
\z/
inner = $1.strip
return [] if inner.empty?
inner.split(",").map { |t| coerce_literal(t.strip) }
else
s
end
end
# ----------------------------------------------------------
# Quantum Primitives (ASCII, unit circle parameterization)
# ----------------------------------------------------------
def self.c_add(a,b) = [a[0]+b[0], a[1]+b[1]]
def self.c_mul(a,b) = [a[0]*b[0]-a[1]*b[1], a[0]*b[1]+a[1]*b[0]]
def self.c_conj(a) = [a[0], -a[1]]
def self.c_norm(a)
Math.sqrt(a[0]*a[0] + a[1]*a[1])
end
def self.unit_circle(theta)
# arbitrary qubit phase point on unit circle: e^{i theta} = cos(theta) + i sin(theta)
[Math.cos(theta), Math.sin(theta)]
end
def self.c_scale(a, s) = [a[0]*s, a[1]*s]
def self.c_fmt(a)
d = TritLang.cfg[:complex_fmt_digits]
re = (a[0] * (10**d)).round / (10**d).to_f
im = (a[1] * (10**d)).round / (10**d).to_f
"#{re}+#{im}i"
end
class Qubit
attr_reader :amp0, :amp1
def initialize(a0, a1)
[a0,a1].each { |c| TritLang.assert(c.is_a?(Array) && c.size == 2 && c.all? { |v| v.is_a?(Numeric) }, "Qubit amplitudes must be [re,im]") }
norm = Math.sqrt(a0[0]*a0[0]+a0[1]*a0[1] + a1[0]*a1[0]+a1[1]*a1[1])
TritLang.assert(norm > 0.0, "Zero qubit state")
@amp0 = [a0[0]/norm, a0[1]/norm]
@amp1 = [a1[0]/norm, a1[1]/norm]
end
def ket
"|q> = (#{TritLang.c_fmt(@amp0)}, #{TritLang.c_fmt(@amp1)})^T"
end
def apply(U2)
TritLang.assert(U2.is_a?(Array) && U2.size == 2 && U2.all? { |r| r.is_a?(Array) && r.size == 2 }, "Unitary must be 2x2 complex matrix")
new0 = [0.0,0.0]; new1 = [0.0,0.0]
new0 = TritLang.c_add(new0, TritLang.c_mul(U2[0][0], @amp0))
new0 = TritLang.c_add(new0, TritLang.c_mul(U2[0][1], @amp1))
new1 = TritLang.c_add(new1, TritLang.c_mul(U2[1][0], @amp0))
new1 = TritLang.c_add(new1, TritLang.c_mul(U2[1][1], @amp1))
Qubit.new(new0, new1)
end
end
class QutritState
attr_reader :amps
def initialize(a0, a1, a2)
[a0,a1,a2].each { |c| TritLang.assert(c.is_a?(Array) && c.size == 2 && c.all? { |v| v.is_a?(Numeric) }, "Qutrit amplitudes must be [re,im]") }
@amps = [a0,a1,a2]
norm = Math.sqrt(@amps.sum { |c| c[0]*c[0] + c[1]*c[1] })
TritLang.assert(norm > 0.0, "Zero state")
@amps = @amps.map { |c| [c[0]/norm, c[1]/norm] }
end
def ket
"|psi> = (#{fmt(amps[0])}, #{fmt(amps[1])}, #{fmt(amps[2])})^T"
end
def bra
"<psi| = (#{fmt_conj(amps[0])}, #{fmt_conj(amps[1])}, #{fmt_conj(amps[2])})"
end
def inner(other)
s = [0.0, 0.0]
3.times do |i|
s = TritLang.c_add(s, TritLang.c_mul(TritLang.c_conj(self.amps[i]), other.amps[i]))
end
s
end
def apply_unitary(u3)
TritLang.assert(u3.is_a?(Array) && u3.size == 3 && u3.all? { |r| r.is_a?(Array) && r.size == 3 }, "Unitary must be 3x3 complex matrix")
new = Array.new(3) { [0.0, 0.0] }
3.times do |i|
3.times do |k|
new[i] = TritLang.c_add(new[i], TritLang.c_mul(u3[i][k], amps[k]))
end
end
QutritState.new(new[0], new[1], new[2])
end
private
def fmt(c) = "#{round(c[0])}+#{round(c[1])}i"
def fmt_conj(c) = "#{round(c[0])}-#{round(c[1])}i"
def round(x) = (x.abs < 1e-10 ? 0.0 : (x * 1000).round / 1000.0)
end
def self.dirac_element(z, u, x)
ux = x.apply_unitary(u)
amp = z.inner(ux)
out = c_fmt(amp)
Triad.new("|x>", "<z| U |x>", out, :forward) { |_| out }
end
def self.demo_qutrit_hadamard
s = 1.0/Math.sqrt(3.0)
[
[[s,0.0],[s,0.0],[s,0.0]],
[[s,0.0],[0.0,0.0],[-s,0.0]],
[[s,0.0],[-s,0.0],[0.0,0.0]]
]
end
def self.qubit_phase_gate(theta)
# U = diag(1, e^{i theta})
eitheta = unit_circle(theta)
[
[[1.0,0.0],[0.0,0.0]],
[[0.0,0.0], eitheta]
]
end
# ----------------------------------------------------------
# REPL (Extended)
# ----------------------------------------------------------
class REPL
def initialize
@registry = TritLang::Registry.new
@gates = TritLang::GateRegistry.new
seed_stdlib
seed_quantum
end
def seed_stdlib
registry.define("Add1") { |x| x + 1 }
registry.define("Add3") { |x| x + 3 }
registry.define("Mul2") { |x| x * 2 }
registry.define("Square", inverse_proc: proc { |z| Math.sqrt(z) }) { |x| x * x }
registry.define("Neg") { |x| -x }
end
def seed_quantum
# Qubit gates
gates.define("Phase(theta)", U: TritLang.qubit_phase_gate(0.0), arity: 1, meta: {param: :theta})
# Qutrit demo gate
gates.define("QutritH", U: TritLang.demo_qutrit_hadamard, arity: 3)
end
def start
puts "TritLang v#{TritLang::VERSION} REPL (ASCII) - type :help, Ctrl+C to exit."
loop do
print "trit> "
line = $stdin.gets
break unless line
line = line.strip
next if line.empty?
begin
case
when line.start_with?(":help") then help
when line.start_with?(":version") then puts "Version: #{TritLang::VERSION}"
when line.start_with?(":list") then puts "Functions: #{registry.list.join(', ')}"
when line.start_with?(":def")
name, body = line.sub(":def", "").strip.split(" ", 2)
proc_obj = eval(body) # Proc literal expected
registry.define(name, &proc_obj)
when line.start_with?(":call")
_, name, val = line.split(" ", 3)
x = TritLang.coerce_literal(val)
TritLang.info("Call #{name}(#{x.inspect}) => #{registry.call(name, x).inspect}")
when line.start_with?(":sys")
_, rest = line.split(" ", 2)
label, command = rest.split("::", 2)
t = TritLang.system_call(label.strip, command.strip)
puts t.to_s
when line.start_with?(":eval")
expr = line.sub(":eval", "").strip
t = TritLang.safe_eval(expr)
puts t.to_s
when line.start_with?(":set")
_, op, a, b = line.split(" ", 4)
a = TritLang.coerce_literal(a); b = TritLang.coerce_literal(b)
t =
case op
when "union" then TritLang.set_union(a, b)
when "intersection" then TritLang.set_intersection(a, b)
when "difference" then TritLang.set_difference(a, b)
when "cartesian" then TritLang.set_cartesian(a, b)
else raise "Unknown set op: #{op}"
end
puts t.to_s
when line.start_with?(":trit")
_, op, *vals = line.split(" ")
nums = vals.map(&:to_i)
r =
case op
when "not" then TritLang.trit_not(nums[0])
when "and" then TritLang.trit_and(nums[0], nums[1])
when "or" then TritLang.trit_or(nums[0], nums[1])
when "sum" then TritLang.trit_sum(nums[0], nums[1])
else raise "Unknown trit op: #{op}"
end
puts "RESULT: #{r}"
when line.start_with?(":matrix")
_, expr = line.split(" ", 2)
left, right = expr.split("*").map { |s| eval(s.strip) }
puts TritLang.matrix_multiply(left, right).inspect
when line.start_with?(":compose")
_, rest = line.split(" ", 2)
t1_str, t2_str = rest.split("::", 2)
t1 = TritLang.parse(t1_str.strip)
t2 = TritLang.parse(t2_str.strip)
op1 = registry.get(t1.process_label)&.dig(:proc)
op2 = registry.get(t2.process_label)&.dig(:proc)
meta1 = registry.get(t1.process_label)&.dig(:meta) || {}
meta2 = registry.get(t2.process_label)&.dig(:meta) || {}
t1.operation = op1 if op1
t2.operation = op2 if op2
t1.meta = meta1
t2.meta = meta2
t1.typecheck!
t2.typecheck!
pipeline = t1.compose(t2)
pipeline.typecheck!
puts pipeline.to_s
puts "valid?: #{pipeline.valid?}"
puts pipeline.draw
puts "codegen: #{pipeline.codegen}"
when line.start_with?(":adjoint")
_, t_str = line.split(" ", 2)
t = TritLang.parse(t_str.strip)
inv = registry.get(t.process_label)&.dig(:inverse)
a = t.adjoint(&inv)
a.typecheck!
puts a.to_s
puts "valid?: #{a.valid?}"
puts a.draw
puts "codegen: #{a.codegen}"
when line.start_with?(":qutrit")
a0 = [1.0,0.0]; a1 = [0.0,0.0]; a2 = [0.0,0.0]
z0 = [0.577,0.0]; z1 = [0.577,0.0]; z2 = [0.577,0.0]
x = TritLang::QutritState.new(a0,a1,a2)
z = TritLang::QutritState.new(z0,z1,z2)
u = TritLang.demo_qutrit_hadamard
tri = TritLang.dirac_element(z,u,x)
puts x.ket
puts z.bra
puts "<z|U|x> => #{tri.output}"
puts tri.to_s
when line.start_with?(":qubit")
# :qubit theta => builds |q> = (1, e^{i theta}) normalized and applies Phase(theta)
_, theta_s = line.split(" ", 2)
theta = theta_s.to_f
eitheta = TritLang.unit_circle(theta)
q = TritLang::Qubit.new([1.0,0.0], eitheta)
puts q.ket
U = TritLang.qubit_phase_gate(theta)
q2 = q.apply(U)
puts "|q'> after Phase(theta): #{q2.ket}"
when line.start_with?(":gate")
# :gate name N [[...],[...]] define unitary gate
_, name, arity_s, rest = line.split(" ", 4)
U = eval(rest)
gates.define(name, U: U, arity: arity_s.to_i)
puts "Gate defined: #{name}"
when line.start_with?(":gls")
puts "Gates: #{gates.list.join(', ')}"
else
triad = TritLang.parse(line)
f = registry.get(triad.process_label)
if triad.direction == :forward && f
triad.operation = f[:proc]
triad.meta = f[:meta] || {}
triad.output = triad.operation.call(triad.input)
elsif triad.direction == :backward && f && f[:inverse]
triad.operation = f[:inverse]
triad.meta = f[:meta] || {}
triad.input = triad.operation.call(triad.output)
end
triad.typecheck!
puts triad.to_s
puts "valid?: #{triad.valid?}"
puts triad.draw
puts "codegen: #{triad.codegen}"
end
rescue => e
TritLang.error(e.message)
end
end
end
private
attr_reader :registry, :gates
def help
puts <<~HELP
Commands:
:help Show this help
:version Show version
:list List functions
:def Name ProcLiteral Define function, e.g., :def Add5 {|x| x+5}
:call Name value Call function with value
:sys label :: command Read-only system call
:eval expr Safe arithmetic eval
:set op A B union|intersection|difference|cartesian
:trit op args not|and|or|sum
:matrix [[...]] * [[...]] Multiply matrices
:compose triad :: triad Compose forward triads
:adjoint triad Build adjoint (uses inverse if available)
:qutrit Show qutrit Dirac bridge <z|U|x>
:qubit theta Build and phase a qubit with e^{i theta} on unit circle
:gate name arity U Define unitary gate (arity 1 or 3), U = complex matrix [[[re,im],...],...]
:gls List gates
Triad examples:
<2 | Add3 -> 5>
<3 <- Square | 9>
<1 | "Label" | 1>
HELP
end
end
# ----------------------------------------------------------
# Demo (single block output)
# ----------------------------------------------------------
def self.demo
info("=== TritLang v#{VERSION} Demo (ASCII) Begin ===")
registry = Registry.new
registry.define("Add3") { |x| x + 3 }
registry.define("Mul2") { |x| x * 2 }
registry.define("Square", inverse_proc: proc { |z| Math.sqrt(z) }) { |x| x * x }
add3_proc = registry.get("Add3")&.dig(:proc) or raise "Missing proc for Add3"
mul2_proc = registry.get("Mul2")&.dig(:proc) or raise "Missing proc for Mul2"
add3 = Triad.new(2, "Add3", 5, :forward, &add3_proc)
mul2 = Triad.new(5, "Mul2", 10, :forward, &mul2_proc)
add3.typecheck!
mul2.typecheck!
pipeline = add3.compose(mul2)
pipeline.typecheck!
puts pipeline.to_s
puts "valid?: #{pipeline.valid?}"
puts pipeline.draw
puts "codegen: #{pipeline.codegen}"
info("=== Sets ===")
puts set_union([1,2,3], [3,4]).to_s
puts set_cartesian([1,2], %w[a b]).to_s
info("=== Trit Logic ===")
puts "NOT(1) => #{trit_not(1)}"
puts "AND(1,0) => #{trit_and(1,0)}"
puts "SUM(1,-1) => #{trit_sum(1,-1)}"
info("=== Algebra ===")
puts identity("X").to_s
it = iterate(2, "Double", 3) { |x| x * 2 }
it.typecheck!
puts it.to_s
cond = conditional(5, proc { |x| x > 0 }, "Add1") { |x| x + 1 }
cond.typecheck!
puts cond.to_s
puts "valid?: #{cond.valid?}"
info("=== Matrix ===")
m1 = [[1,2],[3,4]]
m2 = [[2,0],[1,2]]
puts matrix_multiply(m1, m2).inspect
info("=== System/Eval ===")
uname = system_call("uname -s", "uname -s")
puts uname.to_s
puts safe_eval("1 + 2 * 3").to_s
info("=== Quantum: Qubit Unit Circle ===")
theta = Math::PI / 3.0
eitheta = unit_circle(theta)
q = Qubit.new([1.0,0.0], eitheta)
puts q.ket
Uphase = qubit_phase_gate(theta)
q2 = q.apply(Uphase)
puts q2.ket
info("=== Quantum Dirac Bridge (Qutrit) ===")
a0 = [1.0,0.0]; a1 = [0.0,0.0]; a2 = [0.0,0.0]
z0 = [0.577,0.0]; z1 = [0.577,0.0]; z2 = [0.577,0.0]
x = QutritState.new(a0,a1,a2)
z = QutritState.new(z0,z1,z2)
u = demo_qutrit_hadamard
tri = dirac_element(z,u,x)
puts x.ket
puts z.bra
puts tri.to_s
info("=== TritLang v#{VERSION} Demo (ASCII) End ===")
end
# ----------------------------------------------------------
# __main__
# ----------------------------------------------------------
if __FILE__ == $0
TritLang.demo
puts
TritLang::REPL.new.start
end
end
TritLang RTFM: No‑code game dev with Rust, Magnus (Ruby), and raylib
This manual turns TritLang’s triadic notation into a no‑code control surface for building games in Rust using raylib, with Ruby scripts embedded via the magnus crate. You write ⟨state | action | result⟩ triples; the engine binds them to raylib actions and executes them safely, with compiler‑style diagnostics. It’s deterministic, auditable, and playful — perfect for scene scripting, input handling, and rendering without traditional code.
Scope and philosophy
- No‑code interface: TritLang’s brackets act as your “program.” You declare scenes, sprites, inputs, and actions as readable triples.
- Rust core, Ruby extension: Rust runs the game loop and raylib; Ruby holds your TritLang scripts, hot‑reloadable via magnus.
- Determinism and safety: Execution is audited with type checks, validations, and safe‑eval gates, plus clear diagnostics and codegen visuals.
- Qutrit option: Quantum‑flavored modules are available for experimentation, but raylib gameplay uses classical pipelines.
Quick start
Add dependencies in Cargo.toml:
- raylib for graphics and windowing
- magnus for embedding Ruby
Initialize a window and game loop in Rust, then call into Ruby for actions each frame.
Example Cargo.toml:
- [dependencies]
- raylib =
5.7.0
- magnus =
0.6
Example Rust main:
- use raylib::prelude::*;
- fn main() {
- let (mut rl, thread) = raylib::init().size(800, 600).title(
TritLang Game
).build(); - while !rl.window_should_close() {
- let mut d = rl.begin_drawing(&thread);
- d.clear_background(Color::BLACK);
- d.draw_text(
Hello TritLang!
, 20, 20, 24, Color::WHITE); - }
- let (mut rl, thread) = raylib::init().size(800, 600).title(
- }
Architecture
- Rust engine: Owns the window, assets, and main loop. Exposes actions to Ruby and applies them to raylib.
- Magnus (Ruby in Rust): Loads TritLang scripts, evaluates triads, and returns structured commands to Rust.
- TritLang runtime (Ruby): Parses ⟨x | y -> z⟩, validates, and emits an executable plan (draw, move, input, audio) with diagnostics.
Data flow:
- Ruby defines triads as scene graph actions.
- Magnus calls Ruby functions to get actions for the current frame.
- Rust applies actions using raylib (draw textures, play sound, handle input).
- Diagnostics print if types or states mismatch.
TritLang notation for game actions
Use triads to declare game operations:
- Draw a sprite: ⟨{id:
hero
, pos:[x,y]} | DrawSprite -> ok⟩ - Move an entity: ⟨{id:
hero
, vel:[vx,vy]} | Integrate -> {x’,y’}⟩ - Handle input: ⟨Key(
Space
) | IfPressed -> Jump⟩ - Scenes/pipelines: Compose triads to form frame updates.
Example Ruby:
- move = TritLang.parse(
⟨{id:'hero', pos:[100,200], vel:[2,0]} | Integrate -> {pos:[102,200]}⟩
) - draw = TritLang.parse(
⟨{id:'hero', pos:[102,200], tex:'hero.png'} | DrawSprite -> ok⟩
) - pipeline = move.compose(draw)
Rust–Ruby integration (Magnus)
Magnus embeds Ruby in Rust to call Ruby functions and receive actions. The pattern:
- Initialize Ruby VM with magnus::init().
- eval() a TritLang script containing scene logic.
- Call a Ruby function each frame (e.g., trit_get_frame_actions) to return actions.
Example Rust:
- use magnus::{eval, Value};
- let script = include_str!(
scripts/game.rb
); eval(script)?; - let get_actions: Value = eval(
method(:trit_get_frame_actions)
)?; - Each frame:
- let actions: Vec<(String, magnus::Value)> = convert/get Ruby array of [command, payload] pairs.
- Match on command strings and perform raylib drawing or state updates.
Example Ruby:
- require_relative 'tritlang_runtime'
- def trit_get_frame_actions
- [
- [
DrawText
,Hello from TritLang!
], - [
MoveHero
, { id:hero
, vel: [2,0] }], - [
DrawSprite
, { id:hero
, tex:hero.png
, pos: [100,200] }] - ]
- end
TritLang runtime reference (Ruby)
Triad API:
- Create: Triad.new(input, process_label, output, :forward, &op)
- Validate: triad.typecheck!, triad.valid?
- Compose: triad1.compose(triad2)
- Adjoint: triad.adjoint(&inverse)
- Draw ASCII: triad.draw
- Codegen: triad.codegen
DSL:
- Forward: ⟨x | y -> z⟩
- Backward: ⟨x <- y | z⟩
- Neutral: ⟨x | y | z⟩
- Parse: TritLang.parse(
⟨1 | Add3 -> 4⟩
)
Sets:
- set_union(a,b), set_intersection(a,b), set_difference(a,b), set_cartesian(a,b)
Trit logic:
- trit_not(-1|0|1), trit_and(x,y), trit_or(x,y), trit_sum(x,y)
Algebra:
- identity(x)
- conditional(x, cond_proc, label) { |x| ... }
- iterate(x, label, n) { |x| ... }
Quantum (optional):
- QutritState.new([re,im],[re,im],[re,im])
- ket, bra, inner(other), apply_unitary(u3)
- Dirac bridge: dirac_element(z,u,x) returns a triad of ⟨z|U|x⟩
Raylib action mapping
Define a registry of processes that map triads to raylib calls. Recommended actions:
- Window: Init, toggle fullscreen, set target FPS.
- Draw: DrawText, DrawTexture, DrawRectangle, ClearBackground.
- Input: IfPressed(Key), MousePos, Axis (WASD).
- Audio: PlaySound, StopSound.
- Scene: PushScene, PopScene, LoadTexture, UnloadTexture.
Example Ruby mapping:
- REG = TritLang::Registry.new
- REG.define(
DrawText
) { |x| [DrawText
, x[:text]] } - REG.define(
DrawSprite
) { |x| [DrawSprite
, x] } - REG.define(
Integrate
) { |x| pos = x[:pos]; vel = x[:vel]; { id: x[:id], pos: [pos[0] + vel[0], pos[1] + vel[1]] } }
Rust consumes these [command, payload] tuples and performs raylib rendering and updates per frame.
Development workflow
- Define scenes in TritLang: Use triads to declare pipelines for each frame.
- Run Rust REPL or hot‑reload: Magnus executes Ruby; Rust renders via raylib.
- Diagnose: Compiler‑style logs show type checks, composition, and codegen.
- Iterate: Add actions by extending the Ruby Registry and Rust match arms.
Error handling and diagnostics
- Type mismatch or missing inverse → error with message.
- Composition boundary mismatch → “State mismatch” diagnostic.
- Eval/system calls are gated and logged.
- Matrix operations validate shapes and numerics.
Project setup
- Cargo.toml: raylib for rendering, magnus to embed Ruby.
- Build deps: raylib may require platform tools; use raylib‑rs guidance for setup.
- Ruby scripts: Place TritLang runtime and scene scripts under scripts/.
- Assets: Manage textures and sounds; preload and cache handles.
Example: full frame pipeline
Ruby:
- def scene_frame
- hero = { id:
hero
, pos: [100, 200], vel: [2, 0], tex:hero.png
} - move = TritLang.parse(
⟨#{hero} | Integrate -> {pos:[102,200]}⟩
) - draw = TritLang.parse(
⟨{id:'hero', pos:[102,200], tex:'hero.png'} | DrawSprite -> ok⟩
) - pipeline = move.compose(draw)
- [
- [
DrawSprite
, { id:hero
, tex:hero.png
, pos: [102, 200] }], - [
DrawText
,Frame OK
] - ]
- hero = { id:
- end
Rust:
- let actions: Vec<(String, Value)> = call_ruby(
scene_frame
)?; - for (cmd, payload) in actions {
- match cmd.as_str() {
DrawSprite
=> { /* draw sprite using payload fields */ }DrawText
=> { let text: String = try_convert(payload).unwrap(); d.draw_text(&text, 12, 12, 20, Color::WHITE); }- _ => {}
- }
- }
Best practices
- Keep logic in TritLang triads; keep rendering in Rust.
- Normalize action payloads to simple maps (id, pos, tex).
- Use composition to build per‑frame pipelines and scenes.
- Log diagnostics; fail fast on type or state mismatches.
- Cache textures and sounds; avoid loading every frame.
FAQ
- Can I avoid writing Rust? You’ll still need a small Rust host to run raylib efficiently, but most gameplay logic can live in Ruby/TritLang.
- Why magnus? It’s a robust bridge to embed Ruby in Rust and call functions both ways, co‑creating a no‑code interface.
- Is raylib suitable? raylib is simple and pragmatic for finishing games; raylib‑rs is actively maintained and fits small to mid projects.
Appendix: action catalog
- DrawSprite, DrawText, DrawRect, ClearBackground
- Integrate (pos += vel), ApplyForce, SetVelocity
- IfPressed(Key), KeyAxis, MousePos
- PlaySound, StopSound, SetVolume
- LoadTexture, UnloadTexture, PushScene, PopScene
Extend by adding entries to the Ruby Registry and corresponding Rust match arms.
License and credits
- TritLang is your no‑code DSL for games.
- raylib‑rs: Rust bindings for raylib that provide the windowing and draw loop.
- The magnus crate embeds Ruby, enabling TritLang scripts to direct the engine.
Let’s break down what this code accomplishes:
Class Definition (Trit etc): We define a class with attributes for input (x), process_label (y), output (z), and direction. The initializer takes these along with an optional block representing the actual operation to perform. (For example, if y is “Add3”, the block could be { |x| x+3 }.) Storing a Proc in @operation lets us actually compute y(x) y(x) when needed. We default the direction to :forward but it can be set to :backward to indicate an inverse relationship.
String Representation (to_s): For easy visualization, we override to_s to display the triple in the form ⟨x | y -> z⟩ or ⟨x <- y | z⟩, depending on the direction. We use the Unicode bra-ket symbols “⟨⟩” for a closer analogy to Dirac notation, and insert an arrow -> or <- next to the process. For instance, a forward triple might print as ⟨2 | Add3 -> 5⟩, indicating 2→Add35 2 Add3
A backward triple like ⟨3 <- Square | 9⟩ indicates 9 9 is the result of squaring 3 3, or equivalently 3 3 is obtained by applying the inverse of “Square” to 9
This string format provides a visual pseudocode for the computation, which could be useful for logging or diagrams of data flow.
Validation (valid? method): We include a helper that actually checks the math: it uses the stored @operation (if provided) to verify that applying y y to x x yields z z (forward) or that applying the inverse (modeled by the block when direction is backward) to z z gives x x. For example, for triple1 = ⟨2|Add3->5⟩, valid? will compute 2+3 and confirm it equals 5. This ensures internal consistency of the triple. If no actual operation block is given, valid? just returns true by default (treating it as a purely symbolic triple).
Composition (compose method): Here we allow two triples to be composed sequentially, analogous to function composition or chaining of operations. The method checks that the current triple is forward and the next triple is forward (for simplicity, we only compose forward-directed computations), and that this triple’s output matches the next triple’s input. If so, it creates a new TriBraKet whose input is the first triple’s input, output is the second triple’s output, and the process label is a concatenation like Process1; Process2 Process1;Process2. If both triples have actual operations, it also composes those functions so that the new triple’s @operation will execute first y then y'. For example, if we have ⟨2∣Add3∣5⟩ ⟨2∣Add3∣5⟩ and ⟨5∣Mul2∣10⟩ ⟨5∣Mul2∣10⟩, their composition is ⟨2∣Add3; Mul2∣10⟩ ⟨2∣Add3; Mul2∣10⟩. Internally, this new triple’s operation will do x + 3 then times 2. This mimics how one would compose two transformations y;y′ y;y ′ , reflecting the mathematical composition y′(y(x)) y ′ (y(x)). (This is directly analogous to composing arrows in Haskell with >>> when types align.) If the states don’t align, we raise an error – preventing invalid compositions where the “output” of one step doesn’t match the “input” of the next.
Example objects and output: We create two forward triples, triple1 and triple2.
triple1 = ⟨2|\text{Add3}->5⟩ is instantiated with input=2, process_label= Add3 Add3, output=5, and a block {|x| x+3}. The puts triple1.to_s line would output something like: “⟨2 | Add3 -> 5⟩”. Calling triple1.valid? would compute the block (2+3) and print “Valid?” (indicating the triple’s operation is correct).
triple2 = ⟨5|\text{Mul2}->10⟩ analogously represents multiplying by 2 (taking 5 to 10). Its string form would be “⟨5 | Mul2 -> 10⟩”. It should also validate since 5×2=10 5×2=10.
We then compose triple1.compose(triple2) to get triple3. This represents the combined process “Add3; Mul2” taking 2 all the way to 10. The to_s of triple3 would produce “⟨2 | Add3; Mul2 -> 10⟩”, clearly showing a pipeline: 2 →(+3)→ 5 →(×2)→ 10. The composed valid? now effectively checks (2+3)×2==10 (2+3)×2==10, which should pass. This demonstrates how multiple ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ structures can be linked to model a multi-step computation, just as successive quantum gates or function calls would be.
Finally, we show triple_back = ⟨3 \leftarrow \text{Square} | 9⟩ as an example of a backward-directed triple. We supply a block {|y| Math.sqrt(y)} which is essentially the inverse of squaring. Its to_s prints “⟨3 <- Square | 9⟩”, indicating that squaring 3 yields 9 (or taking sqrt of 9 returns 3). The valid? will do Math.sqrt(9) and check it equals 3 – again confirming the triple’s consistency but this time interpreting the block as y−1 y −1 on the output.
The Ruby simulation above treats the ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ notation as a concrete object with which we can compute and verify results. We also leveraged Ruby’s flexibility (for instance, we could overload + or * operators to combine triples in a more natural syntax if desired, and we can easily extend the class with more features). This kind of implementation illustrates that the notation is not just abstractly elegant, but also practical to work with in code. One could imagine building a small library where ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ objects can be manipulated, logged, or visualized as part of an algorithm’s pseudocode.
Extensions and Manipulations of $\langle x|y|z \rangle$ Notation
We have seen basic composition and inversion. To make ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ truly analogous to Dirac notation and useful for complex systems, we propose additional notations and manipulations:
Sequential Composition: Just as we composed two triples in the Ruby example, we formalize that if one triple’s output matches another’s input, they can be sequenced. Notationally, ⟨a∣ p ∣b⟩ ∘ ⟨b∣ q ∣c⟩=⟨a∣p;q∣c⟩ ⟨a∣p∣b⟩∘⟨b∣q∣c⟩=⟨a∣p;q∣c⟩. This operation is associative, meaning (⟨a∣p∣b⟩∘⟨b∣q∣c⟩)∘⟨c∣r∣d⟩=⟨a∣p;q;r∣d⟩ (⟨a∣p∣b⟩∘⟨b∣q∣c⟩)∘⟨c∣r∣d⟩=⟨a∣p;q;r∣d⟩, etc., similar to how matrix multiplication of operators is associative. If p p and q q were actual functions or quantum gates, p;q p;q corresponds to performing p p then q q. This mirrors the category-theory concept of arrows where
‘composesarrowsend−to−end.Compositionallowsbuilding∗∗pipelines∗∗orcircuitsofcomputation:forexample,onecouldchainmany ‘composesarrowsend−to−end.Compositionallowsbuilding∗∗pipelines∗∗orcircuitsofcomputation:forexample,onecouldchainmany\langle\cdot|\cdot|\cdot\rangle triplestorepresentanentirealgorithmasasequenceofelementarytransformations. triplestorepresentanentirealgorithmasasequenceofelementarytransformations.
Parallel Composition and Tensor Product: In quantum notation, one can take tensor products of states or operators (e.g. ∣ψ⟩⊗∣ϕ⟩ ∣ψ⟩⊗∣ϕ⟩ for composite systems). For our triple, we could define a form of parallel or independent combination. For instance, ⟨x1∣y1∣z1⟩⊗⟨x2∣y2∣z2⟩ ⟨x 1 ∣y 1 ∣z 1 ⟩⊗⟨x 2 ∣y 2 ∣z 2 ⟩ might denote a process y1 y 1 acting on x1 x 1 and simultaneously y2 y 2 on x2 x 2 , yielding z1 z 1 and z2 z 2 respectively. The result could be written as ⟨(x1,x2)∣(y1∥y2)∣(z1,z2)⟩ ⟨(x 1 ,x 2 )∣(y 1 ∥y 2 )∣(z 1 ,z 2 )⟩. This could model independent sub-computations or, in quantum terms, operations on separable qubits/qutrits. Such notation would be useful for describing concurrent or parallel algorithms in a structured way, akin to how quantum circuit diagrams show parallel gates on different wires.
Superposition of Processes: A particularly quantum-inspired extension is allowing a superposition of different triples. In quantum mechanics, a state can be a superposition of basis states (e.g. 12(∣0⟩+∣1⟩) 2 1 (∣0⟩+∣1⟩)). By analogy, one could imagine a formal combination like a weighted sum α ⟨x∣y1∣z⟩+β ⟨x∣y2∣z⟩ α⟨x∣y 1 ∣z⟩+β⟨x∣y 2 ∣z⟩, representing a situation where process y1 y 1 or y2 y 2 might occur (perhaps in a nondeterministic or parallel sense). While classical computing doesn’t have linear superposition of procedures, this notation could be used to reason about probabilistic or quantum algorithms. For example, a quantum algorithm that applies Y Y or Z Z gate with certain amplitudes could be notated as a superposed triple ⟨ψ∣ αY+βZ ∣ψ′⟩ ⟨ψ∣αY+βZ∣ψ ′ ⟩. This is speculative, but it aligns with how quantum gates like the Hadamard can create superpositions of outcomes. In pseudocode terms, we might use it to represent branching or uncertain processes in a high-level way.
Adjoint and Inverse Notation: We already introduced the idea of a “backwards” triple. To formalize, for every forward triple ⟨x∣y∣z⟩ ⟨x∣y∣z⟩, if the process y y is invertible (or reversible), we define an adjoint triple ⟨z∣y†∣x⟩ ⟨z∣y † ∣x⟩ to denote the inverse mapping (here we use y† y † akin to the Hermitian adjoint notation in quantum mechanics). In computation, y† y † corresponds to the inverse function or procedure of y y. For example, if y y is encryption, y† y † is decryption; if y y is a quantum unitary gate, y† y † is its conjugate transpose gate. Notationally, ⟨x←y∣z⟩ ⟨x←y∣z⟩ is a convenient way to write the inverse without introducing a new symbol for y† y † . We ensure that ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ is valid iff ⟨z∣y†∣x⟩ ⟨z∣y † ∣x⟩ is valid – a direct parallel to the bra–ket relationship ⟨ψ∣ϕ⟩=⟨ϕ∣ψ⟩∗ ⟨ψ∣ϕ⟩=⟨ϕ∣ψ⟩ ∗ in quantum mechanics. This property ties our system to the concept of reversible computing, where every computational step can in principle be undone. It also connects to the idea of bidirectional transformations in computer science (for instance, parsing vs. pretty-printing, where one specification yields two directions of computation).
Identity and Unit Processes: In Dirac notation, the identity operator can be inserted without changing a state (often written as I=∑i∣i⟩⟨i∣ I=∑ i ∣i⟩⟨i∣, which satisfies ∣ψ⟩=I∣ψ⟩ ∣ψ⟩=I∣ψ⟩). For our triple, we can define a special notation for an identity process. ⟨x∣I∣x⟩ ⟨x∣I∣x⟩ represents a no-op that leaves state x x unchanged. This is analogous to a skip statement in programming (which does nothing but trivially x→x x→x). Including an identity triple in a composition acts as the neutral element: ⟨a∣p∣b⟩∘⟨b∣I∣b⟩∘⟨b∣q∣c⟩ ⟨a∣p∣b⟩∘⟨b∣I∣b⟩∘⟨b∣q∣c⟩ simplifies to ⟨a∣p;q∣c⟩ ⟨a∣p;q∣c⟩. Identity triples would be useful for aligning interfaces or explicitly showing that a part of the system is unchanged (e.g., ⟨userInput∣I∣userInput⟩ ⟨userInput∣I∣userInput⟩ might be a placeholder in a larger sequence, indicating that piece of data is carried through untouched).
Notation for Conditional or Iterative Processes: Traditional pseudocode uses constructs like “if…then” or loops. In a bra–ket style, we might augment the middle section y y with such constructs. For instance, ⟨x∣y1{P}∣z⟩ ⟨x∣y 1 {P}∣z⟩ could denote that y1 y 1 is applied under condition P P, otherwise perhaps x x stays as z z (if nothing happens). Or a loop could be represented by a superscript or annotation like ⟨x∣y(n)∣z⟩ ⟨x∣y (n) ∣z⟩ meaning apply y y n n times to get z z. This is moving somewhat beyond the static algebraic notation into algorithmic syntax, but it shows that the bracket can be flexible. We could even imagine ⟨x∣while C{y}∣z⟩ ⟨x∣while C{y}∣z⟩ to compactly represent a loop that starts in state x x and ends in state z z after repeatedly applying y y while condition C C holds. Such extensions would make the notation more like a true pseudocode language for algorithms, where the angle brackets denote a mapping from pre-state to post-state via some structured program.
In implementing these extensions, one must be careful to maintain logical consistency. The algebra of ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ should ideally form a mathematical structure (like a small category, where objects are states and morphisms are processes). Many of the above notations align with category theory ideas: we have identity morphisms, composition, possibly direct sums (superpositions) and products (parallel composition). By enforcing rules analogous to those in quantum mechanics (linearity, unitarity where applicable), we could ensure that the system remains well-behaved. For example, defining a distributive law for superposition: ⟨x∣(y1+y2)∣z⟩ ⟨x∣(y 1 +y 2 )∣z⟩ could be defined as shorthand for ⟨x∣y1∣z⟩+⟨x∣y2∣z⟩ ⟨x∣y 1 ∣z⟩+⟨x∣y 2 ∣z⟩, much as (A+B)∣ψ⟩=A∣ψ⟩+B∣ψ⟩ (A+B)∣ψ⟩=A∣ψ⟩+B∣ψ⟩ in linear algebra.
It’s worth noting that physicists and computer scientists have already explored using Dirac notation in program semantics. Quantum Hoare logic is a framework for verifying quantum programs, and it often uses a labeled Dirac notation to express assertions about program state (for instance, stating that a quantum register is in a certain state). In these logics, one might see judgments that combine classical conditions with bra-ket formalism for quantum parts. Our proposal for ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ could complement such efforts by providing a uniform way to talk about the program’s execution as a whole – bridging classical control structures (via the explicit process y y) with quantum state transformations (via the bra-ket style notation around them). It essentially embeds the program (algorithm y y) into the notation itself, rather than treating it as an external concept.
Philosophical Insights and Practical Applications
The ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ notation straddles the line between a mathematical formalism and a description language for computations. This dual nature invites several philosophical reflections:
Unifying States and Processes: By inserting the process y y into the bracket, we assert that the transformation is an integral part of the description of reality, not separate from it. In physics, one typically talks about a system’s state and how an external operator affects it. Here, we almost treat the operator as a quasi-state. This resonates with philosophical viewpoints where processes are primary constituents of reality (process philosophy). In computation, it emphasizes that an algorithm (process) plus input yields output – none of these three elements alone gives a full picture; they form a triad. It’s reminiscent of the Hegelian triad (thesis–antithesis–synthesis) in a very abstract sense, or the idea that an event is defined by a before state, an after state, and the transformation between. By formalizing ⟨x∣y∣z⟩ ⟨x∣y∣z⟩, we acknowledge that computational steps can be discussed as standalone entities (with “beginning, middle, end”), bringing program semantics closer to the language of quantum transitions.
Arrow of time and causality: The introduction of arrows highlights the role of time or causality in computation. A forward triple ⟨x∣y−>z⟩ ⟨x∣y−>z⟩ is time-directed: cause x x produces effect z z under y y. If we consider the backward triple, it’s as if we are looking backward in time or inference (effect to cause). In physics, microscopic laws are often time-symmetric, but when we describe a process, we impose a direction (e.g., we prepare a state x x and later observe z z). Similarly, in computing, programs are usually run forward, but for debugging or AI inference, we sometimes reason backwards (e.g., goal-directed reasoning). Our notation makes that direction explicit and thus is a good vehicle to discuss questions of determinism and invertibility. A classical computer program is generally not reversible (information is lost, e.g., when you add two numbers, you can’t uniquely recover the inputs from just the sum), meaning for most ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ there is no ⟨x←y∣z⟩ ⟨x←y∣z⟩. However, in principle, any computation can be made reversible by carrying along ancillary information. Philosophically, this touches on Landauer’s principle and the connection between information and thermodynamics – if we treat every ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ as (potentially) invertible, we’re aligning with a physical perspective that information is conserved (except when deliberately erased by non-invertible operations).
Quantum-Classical Connections: The notation was born from a quantum analogy, so what does it give us when thinking about quantum computing? One immediate insight is a clearer way to reason about a quantum algorithm’s behavior in terms of input and output states and the algorithm itself as an entity. For instance, take Shor’s algorithm for factoring: classically, we think of it as input N N (number to factor), output (p,q) (p,q) factors. Quantum mechanically, the process involves a quantum Fourier transform and is probabilistic. We could denote a successful run abstractly as ⟨N∣ShorAlg∣(p,q)⟩ ⟨N∣ShorAlg∣(p,q)⟩. Now, consider that in Shor’s algorithm, we know N N and seek (p,q) (p,q). Contrast this with something like Grover’s search: we know the “marked item” condition (output condition) and we want to find the input that satisfies it. That could be notated ⟨solution←GroverAlg∣unsortedDB⟩ ⟨solution←GroverAlg∣unsortedDB⟩ (reading as: from an unsorted database, Grover’s algorithm finds the solution). By having y y (the algorithm) in the middle, these two cases look like variants—just flipping the arrow. This suggests a symmetry: the triple notation may help illuminate how quantum computing blurs the line between input and output due to superposition and entanglement. In fact, it was noted that quantum computing takes the output and model as given and produces the input probabilistically in some scenarios. Our notation cleanly encapsulates that idea.
Program Verification and Reasoning: The triple bears obvious resemblance to Hoare triples, as discussed, which are the cornerstone of program correctness reasoning. In a way, ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ could serve as a more semantic Hoare triple: instead of x x and z z being logical assertions, they are actual states (or data values) and y y is the actual program (not just an abstract command). For practical applications, one could imagine a tool or language where you write specifications in this form, and then use automated reasoning to check them. For example, one might specify a function with something like ⟨input list∣Sort∣sorted list⟩ ⟨input list∣Sort∣sorted list⟩. This is more readable at times than writing “Given an input list, after Sort, the output is a sorted list” in English or logical formulas. It’s concise and mathematically flavored, which could aid in formal methods. Researchers are already using algebraic techniques to reason about program correctness with Dirac-like notation in the quantum realm. Our system could extend that to hybrid classical-quantum programs or even purely classical ones, by providing an algebra of triples to represent and manipulate program specs.
Innovative computational models: With quantum computing on the rise, new models of computation are being explored that mix classical and quantum logic. The ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ notation might inspire quantum pseudocode formats or even programming language syntax. For instance, a quantum programming language might allow a construct like:
⟨qubit_state | Apply(Hadamard) -> superposed_state⟩; ⟨superposed_state | Apply(Oracle) -> flipped_amplitudes⟩; ⟨flipped_amplitudes | Measure -> outcome⟩.
This isn’t far from how one talks through quantum algorithms in prose, but here it’s structured. It could serve as an intermediate representation that is human-readable yet precisely ties together states and operations. Practically, this could help in teaching quantum computing – students can write out the steps of an algorithm in bracket form to ensure they track the state changes explicitly, much like how one writes kets ∣ψinit⟩→U1∣ψ1⟩→U2∣ψ2⟩ etc.. The difference is our notation packages each step into a single object.
Multi-valued logic and hardware: On the classical hardware side, as mentioned, ternary or multi-valued logic circuits are an active area of research. One could imagine designing a ternary computer’s instruction set or circuit description using ⟨x∣y∣z⟩ ⟨x∣y∣z⟩. For example, a ternary full adder might be described by triples mapping input triplets (including carry) to outputs. The notation may help abstract away the low-level detail and focus on state transformations. Moreover, because the triple is reminiscent of a database record, it might integrate well with tools – one could store a large list of triples to represent a transition system or a state machine (somewhat like triple stores in semantic web, though those are [subject, predicate, object]). The added benefit is the arrow notation could indicate whether transitions are reversible or not.
Cognitive and linguistic angle: There’s an interesting cognitive aspect to using brackets with three slots. Human language typically structures transitive statements as subject-verb-object (SVO) – which is a ternary relation. In “Alice greets Bob”, we have Alice (actor), greets (action), Bob (receiver). The ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ notation can be thought of as a formal “sentence” with subject x x, verb y y, object z z. This parallel to natural language might make the notation more intuitive in describing processes. Philosophically, it underscores that computations can be communicated in a sentence-like form that is both human-readable and mathematically precise. This could foster better understanding between domain experts (who might prefer English/pseudocode) and formal methods experts (who prefer equations). The notation acts as a bridge, much like how Dirac notation helped bridge between physicists’ intuitive pictures and the rigor of linear algebra.
Future computational models: As computing moves toward more integrated paradigms (consider quantum-classical hybrid computers, reversible computing, or even bio-computing), having a unified notation is valuable. ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ could evolve to describe transformations in exotic models. For instance, one might talk about a DNA computing step as ⟨DNA segment∣enzymatic reaction∣new segment⟩ ⟨DNA segment∣enzymatic reaction∣new segment⟩. Or a neural network’s training as ⟨old weights∣learning step∣updated weights⟩ ⟨old weights∣learning step∣updated weights⟩. It’s a generic template wherever there’s a state transition. Its quantum-origin gives it a solid foundation for probabilistic and linear algebraic semantics, which are common in advanced models. By analogy, since bra–ket notation proved extremely adaptable (used not just for pure quantum states but in quantum information, quantum computing algorithms, etc.), we expect ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ could similarly adapt and find niches.
In conclusion, the proposed three-state computation notation ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ extends Dirac’s elegant bra–ket formalism to encapsulate the dynamic aspect of computations. We showed how this notation can be implemented and manipulated in a programming language (Ruby), proving that it’s not merely a theoretical curiosity but can serve as a computable pseudocode structure. By proposing additional analogues of Dirac notation’s features – composition, superposition, adjoint, identity – we make ⟨x∣y∣z⟩ ⟨x∣y∣z⟩ into a flexible toolkit, much like bra–ket is for quantum theory. The philosophical and practical implications are far-reaching: it could influence how we think about algorithms (merging the description of “what” and “how” into one bracketed expression), how we design future computing systems (especially ones that are inherently reversible or multi-state), and how we explain complex processes. In spirit, this approach aligns with the trend of borrowing concepts across disciplines: just as computer science has learned from category theory (arrows, monads) and physics has leveraged computer science for quantum algorithms, this triple notation is a transdisciplinary idea. It takes the clarity of quantum state notation and infuses it with the concreteness of computational steps, potentially leading to new ways to reason about and visualize computations as algebraic objects. The true test of its utility will be in applying it to real problems – whether in formal verification of programs, design of quantum algorithms, or even philosophical modeling of causation. The rich set of existing knowledge (from reversible computing theories to quantum Hoare logic) provides a foundation to build on, suggesting that this 3-state notation can be grounded in solid theory while opening pathways to innovative applications.
TritLang RTFM: No‑code game development with Rust, Magnus (embedded Ruby), and raylib
Purpose: TritLang is a triadic, no‑code notation for game logic that you write as ⟨state | action | result⟩. Rust runs raylib for rendering and input; Ruby (embedded via Magnus) holds TritLang scripts that describe scenes, entities, and frame behaviors as readable triples. The engine validates and executes these triples with compiler‑style diagnostics. Philosophy: separate rendering and performance‑critical loops (Rust/raylib) from dynamic scene logic (Ruby/TritLang), keeping iteration fast, safe, and expressive.
Installation and setup: In Cargo.toml, add raylib and magnus. Ensure raylib native dependencies are available for your OS. Organize a “scripts” directory for Ruby files (TritLang runtime and scene DSL). Cache textures and sounds in Rust to avoid per‑frame loads.
Cargo.toml: [dependencies] raylib = 5.7.0
; magnus = 0.6
. Rust entry point: initialize window with raylib::init().size().title().build(), run a loop while !rl.window_should_close(), begin_drawing, clear_background, and draw per frame. Initialize Ruby VM in Rust via magnus::init(); load Ruby scripts with eval(include_str!(...)); call Ruby functions to retrieve a vector of [command, payload] actions and apply them using raylib.
Core notation: Use ⟨x | y -> z⟩ for forward steps (apply process y to input x to produce z), ⟨x <- y | z⟩ for inverse steps (process y-1 maps z to x), and ⟨x | y | z⟩ as a neutral declarative form. Compose forward triples when one’s output equals the next input: ⟨a | p -> b⟩ ∘ ⟨b | q -> c⟩ = ⟨a | p;q -> c⟩. Identity: ⟨x | I | x⟩ carries data unchanged. Conditional and iterative forms: ⟨x | if C then y | z⟩ and ⟨x | yn | z⟩ represent guarded and repeated transformations, respectively. Adjoint/inverse: the backward triple mirrors reversible computing; for unitary/fully invertible processes, ⟨x | y -> z⟩ is valid iff ⟨z | y† -> x⟩ is valid.
Runtime features: Triad objects store input, process label, output, and direction, plus an optional operation (Proc) that executes y or y-1. Typecheck reports compatibility and soft‑passes when an executable operation exists. valid? executes and compares results for forward/backward directions. compose enforces forward‑forward composition with state alignment, builds the composed operation and peephole‑optimizes labels (e.g., Add1;Add1 → Add2, Mul2;Mul2 → Mul4). draw renders an ASCII diagram; codegen prints pseudo‑assembly (LOAD, CALL/ADD/MUL, STORE). Sets: union, intersection, difference, cartesian product. Trit logic: -1, 0, 1 operations (not, and, or, sum, balanced addition). Algebra: identity, conditional, iterate with composed operations. Matrix: validated multiply with shape and type checks. Gates: safe_eval for arithmetic only; system_call for read‑only shell commands. DSL: parses forward/backward/neutral bra‑ket forms with numeric, boolean, nil, string, and array literals.
Quantum (optional): QutritState holds three complex amplitudes, normalizes, provides ket/bra, inner product, unitary application, and a Dirac bridge triad for ⟨z|U|x⟩. This is for exploration and learning; game rendering pipelines remain classical.
Game actions mapping: Define Ruby Registry processes that return actions for Rust. Recommended commands: Window (Init, ToggleFullscreen, TargetFPS), Draw (DrawText, DrawTexture, DrawRectangle, ClearBackground), Input (IfPressed(Key), MousePos, Axis), Audio (PlaySound, StopSound, SetVolume), Scene (PushScene, PopScene, LoadTexture, UnloadTexture). Example Ruby mappings: “DrawText” → [DrawText
, x[:text]]; “DrawSprite” → [DrawSprite
, x]; “Integrate” updates pos by vel and returns the new state. Rust consumes actions via match arms: draw text, render textures at positions, update entity state, play sounds. Maintain an asset cache (HashMap
Magnus integration pattern: Initialize Ruby VM once. Load TritLang runtime and scene scripts. Obtain a method handle to a Ruby function (e.g., trit_get_frame_actions or scene_frame). Each frame: call the Ruby function, convert the returned array of [command, payload] into Rust types (Strings, numbers, vectors), and apply them. Keep call overhead modest; batch actions per frame. For hot‑reload: re‑eval scripts on file change or expose a :reload command.
Development workflow: Write scene logic as triads in Ruby. Compose pipelines per frame (move, collide, draw, UI). Run the Rust host; observe diagnostics (typecheck, composition boundary, execution mismatches). Iterate quickly by editing Ruby scripts; rendering and input stay stable in Rust. As features grow, add new Registry processes (Ruby) and corresponding Rust match arms. Use identity triads to explicitly carry state through steps; include conditional/iterative forms for guards and loops. Keep each triple tight—input, process, output—and compose for clarity.
Error handling and diagnostics: Type mismatches, missing inverses, and composition boundary mismatches print explicit messages and abort the operation. Matrix and gate validators reject unsafe shapes or code. Triad.valid? shows actual results vs. expected. codegen aids visualization. Keep logs on; fail fast in development.
Project structure: src/main.rs (raylib host, Magnus bridge), scripts/tritlang_runtime.rb (the single‑class TritLang runtime), scripts/game.rb (scene logic using triads), assets/ (textures, audio). In Rust, preload assets; pass lightweight payloads from Ruby. In Ruby, avoid heavy computation per frame; prefer declarative state transformations.
Example frame pipeline: Ruby returns actions such as [DrawSprite
, {id:hero
, tex:hero.png
, pos:[x,y]}], [DrawText
, Frame OK
], [MoveHero
, {id:hero
, vel:[2,0]}]. Rust applies movement to the world state, then draws. Compose movement and draw triads: ⟨{id:'hero', pos:[100,200], vel:[2,0]} | Integrate -> {pos:[102,200]}⟩, then ⟨{id:'hero', pos:[102,200], tex:'hero.png'} | DrawSprite -> ok⟩; the composed pipeline communicates intent cleanly and remains auditable.
Best practices: Keep rendering in Rust; keep game logic declarative in Ruby. Normalize payloads to simple maps (id, pos, tex, vel). Use composition for multi‑step updates; identity for carry‑through. Cache assets and world state; never load per frame. Keep triads small and composable. Prefer guards and iteration annotations over ad‑hoc code. Use diagnostics to catch mismatches early.
FAQ: You still need a Rust host to leverage raylib’s performance, but most logic can live in Ruby/TritLang. Magnus is chosen for robust Ruby embedding and easy bidirectional calls. raylib is pragmatic and simple, ideal for small/mid projects; raylib‑rs keeps a safe Rust interface with the familiar frame loop. Quantum modules are optional; they exist for inspiration and education rather than runtime use.
Action catalog (extendable): DrawSprite, DrawText, DrawRect, ClearBackground; Integrate, ApplyForce, SetVelocity; IfPressed(Key), KeyAxis, MousePos; PlaySound, StopSound, SetVolume; LoadTexture, UnloadTexture, PushScene, PopScene. Add new actions by defining Registry entries in Ruby and match arms in Rust.
Closing: TritLang makes your game loop legible and ritual‑clean: every step is a triad—input, process, output. Compose them to form pipelines; validate and visualize with codegen. Rust and raylib give you speed and stability; Ruby and Magnus give you hot‑editable, no‑code gameplay logic. Build worlds by chaining brackets.