We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.

Scalar Coils

From Esolang
Jump to navigation Jump to search
This page is AI-generated and might contain copyright infringements.
This page about an AI-generated programming language is not detailed enough and needs to be expanded by a human, based on how the language actually operates (as opposed to how the AI claims it works – AI-generated explanations of how a language works are usually inaccurate and might be copyright infringements).
Scalar Coils
Paradigm(s) imperative, implicitly parallel
Directed by User:Miui
Generated by Claude Opus 5
Appeared in 2026
Memory system twelve fixed cells, each an unbounded integer pair
Dimensions one-dimensional
Computational class Turing complete
Reference implementation sc.py
File extension(s) .sc

Scalar Coils is an esoteric programming language in which no value can be observed directly. Every quantity is held as a counter-wound pair whose halves cancel, so what a program computes and what a program can be seen to compute are separate properties. Reconciling the two is the language's central difficulty.

The arithmetic is Fibonacci throughout: the primitive operation is (A, B) ← (A + B, A), integer literals are written in Zeckendorf form, and the ratio between a coil's halves converges to φ whether or not the program wants it to.

The language takes its vocabulary from the Resonant Fractals archive, a body of writing on "scalar coils" — bifilar windings whose magnetic flux paths cancel, so that the coil produces no net external field and reads as nothing on a meter. Scalar Coils adopts that as a semantics rather than a physical claim: cancellation is total, cheap, and has nothing to do with whether the value is still there.

The bench

A program has a bench of twelve coils, indexed 0–11 around a dial. Each coil holds four integers:

Field Meaning Initial
A, B the two counter-wound halves 0
M core mass 1
F the coil's own frequency 0

The net field of a coil is (A − B) × M.

A coil with A == B is cancelled. Its contents are unchanged and winding it still does work, but it produces no external signature and no instrument in the language will read it.

Two further registers are shared by the whole bench:

  • the caliper — a single integer, set by hand, initially 0
  • the bench frequency — the drive from the function generator, initially 0

A coil is live when F == bench. All twelve coils start at 0 and the bench starts at 0, so the default state of the machine is that all twelve coils are live at once. Every winding instruction is a twelve-way parallel instruction unless coils have been deliberately tuned apart. There is no store instruction and no way to address a single coil for computation. There is only resonance.

Literals

A literal opens with : and continues with a run of | and _. Positions are the Fibonacci numbers 1, 2, 3, 5, 8, 13, 21, …; | takes that term and _ skips it.

Literal Value
: 0
1
2
3
_| 4 (1 + 3)
8
89 (F11: one term, nine gaps)

Two adjacent | is a syntax error, not a number. By Zeckendorf's theorem every non-negative integer has exactly one representation with no two adjacent terms, so every integer has exactly one spelling. :|| would be 1 + 2, which is already :__|, and the language refuses to let the same value be written twice.

Negative values exist but cannot be written. They are reachable only through subtraction or by reading a coil.

Instructions

Any character not listed is ignored. ( … ) nests and comments.

Caliper and dial

Op Effect
:… set the caliper from a literal
& caliper ← net field of the selected coil
> < move the selection around the dial (mod 12)

Tuning

Op Effect
~ selected coil: F ← caliper
^ bench ← caliper, changing which coils are live

~ and & are the only instructions that act on the selected coil. Everything below acts on every live coil simultaneously.

Winding

Op Effect
w wind: (A, B) ← (A + B, A)
u unwind: (A, B) ← (B, A − B), the exact inverse of w
+ A ← A + caliper
- A ← A − caliper
! repel: swap A and B
o open the leads: A ← B; the coil cancels and goes dark
@ pour the core: M ← caliper
x couple: every live coil takes the sum of all live A

Instruments

Op Effect
. meter — write the net field in decimal, then a newline
; emit — write the net field as a character
, feel — A ← next input byte; at end of input the coil cancels

. and ; do nothing at all on a cancelled coil. This is not an error and produces no diagnostic. It is the ordinary case.

Fields

Op Effect
{ open a self-sustaining field, capturing the caliper as its take-down frequency
} if caliper == TDF, leave the field; otherwise jump back to {

The body always runs at least once. The test is on the caliper rather than the bench, so & is how a field inspects a coil in order to decide whether to shut itself down.

A field does not end because the program ran out of instructions. It ends when it is taken down. A field never given its take-down frequency runs forever; the reference implementation gives up after ten million steps and reports FIELD PERSISTS with exit status 3.

Addressing by cancellation

Because computation is unavoidably parallel across the live set, the idiom that replaces a store instruction is selective cancellation: run the computation on all twelve coils, cancel the eleven that are not wanted, and only the survivor is visible to . and ;.

Tuning coils to distinct frequencies and driving them one at a time is the alternative. It is faster, costs two instructions per switch, and makes the bench sequential, which most programs in the language regard as cheating.

Examples

cat

:|~^ : { ,;& }

Tune coil 0 to 1 and drive 1, silencing the other eleven. Caliper 0 becomes the take-down frequency. Then read, emit, and read the coil back into the caliper. At end of input the coil cancels, & yields 0, the caliper matches the TDF, and the field comes down. A NUL byte in the input takes the field down early; this is a known property.

The silent tick

:|+ . w . w .

Twelve coils go to 1/0 and meter 1 apiece. w takes every coil to 1/1, and the second . prints nothing at all — all twelve are cancelled at once. w again gives 2/1 and the readings return. Twenty-four lines of output from three meter instructions.

Fibonacci

:|~ >:_|~
:_|^ :|_|_|+
:|^ :|+
: { :|^ . w  :_|^ :|- & }

Coil 0 at 1 kHz carries the sequence; coil 1 at 2 kHz counts down from 12. Output is 1 1 1 2 3 5 8 13 21 34 55 — eleven numbers from twelve iterations. The missing term is the tick where the pair passes through 1/1. The sequence is running correctly and is simply unobservable for that step.

Hello, World!

:|~^
:|_|__|__|+;o
:|_|_|____|+;o
:|__|_|___|+;o
:|__|_|___|+;o
:|_____|__|+;o
:_|__|__|+;o
:__|_|_|+;o
:__|_|_|_|+;o
:|_____|__|+;o
:|_|___|__|+;o
:|__|_|___|+;o
:__|_|____|+;o
:|_|_|_|+;o

o between characters clears the coil, since B is 0 and A ← B zeroes it.

Core mass

:|~^ :|+ :_|__|___|_|@ .    →  209
:|~^ :|+ :|__|__|@ .        →  27

Mass multiplies the reading and nothing else; the halves are untouched. A cancelled coil with a mass of 209 still meters nothing, since (A − B) × M is 0 for every M.

Computational class

Scalar Coils is Turing complete by reduction from a Minsky machine. Tune coils 0…n to distinct frequencies and hold each coil's B at 0, so that A is a register. :|^ selects a register, :|+ and :|- increment and decrement it, & followed by the field test gives zero-testing with a conditional branch, and nested { } gives arbitrary control flow. Two counters suffice. The Fibonacci example above is a working instance of the decrement-and-test loop.

Interpreter

import sys

DIAL = 12
MASS_MOD = 0x110000
STEP_LIMIT = 10_000_000

USAGE = """sc.py program.sc [< input]
sc.py -e '<source>'
sc.py --zeck N
sc.py --trace program.sc"""

OPS = set("&><~^@+-wuo!x.;,{}")


def fib_upto(n):
    out, a, b = [], 1, 2
    while a <= n:
        out.append(a)
        a, b = b, a + b
    return out


def zeck_encode(n):
    if n < 0:
        raise ValueError("literals are non-negative")
    if n == 0:
        return ""
    terms = fib_upto(n)
    bits = [False] * len(terms)
    rest = n
    for i in range(len(terms) - 1, -1, -1):
        if terms[i] <= rest:
            bits[i] = True
            rest -= terms[i]
    return "".join("|" if b else "_" for b in bits)


def zeck_decode(body):
    if "||" in body:
        raise SyntaxError("adjacent windings: %r is not a Zeckendorf form" % body)
    terms, a, b = [], 1, 2
    for _ in range(len(body)):
        terms.append(a)
        a, b = b, a + b
    return sum(t for t, ch in zip(terms, body) if ch == "|")


def parse(src):
    prog, i = [], 0
    while i < len(src):
        ch = src[i]
        if ch == "(":
            depth = 1
            i += 1
            while i < len(src) and depth:
                if src[i] == "(":
                    depth += 1
                elif src[i] == ")":
                    depth -= 1
                i += 1
            continue
        if ch == ":":
            j = i + 1
            while j < len(src) and src[j] in "|_":
                j += 1
            prog.append(("lit", zeck_decode(src[i + 1:j])))
            i = j
            continue
        if ch in OPS:
            prog.append((ch, None))
        i += 1

    depth = 0
    for op, _ in prog:
        if op == "{":
            depth += 1
        elif op == "}":
            depth -= 1
            if depth < 0:
                raise SyntaxError("take-down without a field")
    if depth:
        raise SyntaxError("field opened but never taken down")
    return prog


class Coil:
    __slots__ = ("A", "B", "M", "F")

    def __init__(self):
        self.A = self.B = 0
        self.M = 1
        self.F = 0

    @property
    def net(self):
        return (self.A - self.B) * self.M

    @property
    def cancelled(self):
        return self.A == self.B


def run(src, stdin=None, out=None, trace=False, limit=STEP_LIMIT):
    prog = parse(src)
    coils = [Coil() for _ in range(DIAL)]
    fields = []
    caliper = bench = sel = 0
    out = out or sys.stdout
    data = stdin if stdin is not None else sys.stdin.buffer.read()
    ip = pc = steps = 0

    while pc < len(prog):
        if limit and steps > limit:
            out.flush()
            if fields:
                sys.stderr.write("\n Nope. \n")
                return 3
            raise RuntimeError("step limit exceeded")
        steps += 1
        op, arg = prog[pc]
        live = [c for c in coils if c.F == bench]

        if op == "lit":
            caliper = arg
        elif op == "&":
            caliper = coils[sel].net
        elif op == ">":
            sel = (sel + 1) % DIAL
        elif op == "<":
            sel = (sel - 1) % DIAL
        elif op == "~":
            coils[sel].F = caliper
        elif op == "^":
            bench = caliper
        elif op == "@":
            for c in live:
                c.M = caliper
        elif op == "+":
            for c in live:
                c.A += caliper
        elif op == "-":
            for c in live:
                c.A -= caliper
        elif op == "w":
            for c in live:
                c.A, c.B = c.A + c.B, c.A
        elif op == "u":
            for c in live:
                c.A, c.B = c.B, c.A - c.B
        elif op == "o":
            for c in live:
                c.A = c.B
        elif op == "!":
            for c in live:
                c.A, c.B = c.B, c.A
        elif op == "x":
            s = sum(c.A for c in live)
            for c in live:
                c.A = s
        elif op == ".":
            for c in live:
                if not c.cancelled:
                    out.write("%d\n" % c.net)
        elif op == ";":
            for c in live:
                if not c.cancelled:
                    out.write(chr(c.net % MASS_MOD))
        elif op == ",":
            for c in live:
                if ip < len(data):
                    c.A = data[ip]
                    ip += 1
                else:
                    c.A = c.B = 0
        elif op == "{":
            fields.append((pc, caliper))
        elif op == "}":
            open_pc, tdf = fields[-1]
            if caliper == tdf:
                fields.pop()
            else:
                pc = open_pc

        if trace:
            sys.stderr.write("%4d %-3s cal=%-6d bench=%-4d sel=%d | %s\n" % (
                pc, op if op != "lit" else ":" + str(arg), caliper, bench, sel,
                " ".join("%d/%d" % (c.A, c.B) for c in coils[:4])))
        pc += 1

    out.flush()
    if fields:
        sys.stderr.write("\n Nope. \n")
        return 3
    return 0


def main(argv):
    trace = "--trace" in argv
    if trace:
        argv.remove("--trace")
    if len(argv) >= 3 and argv[1] == "--zeck":
        print(":" + zeck_encode(int(argv[2])))
        return 0
    if len(argv) >= 3 and argv[1] == "-e":
        return run(argv[2], trace=trace)
    if len(argv) >= 2:
        with open(argv[1]) as f:
            return run(f.read(), trace=trace)
    print(USAGE)
    return 1


if __name__ == "__main__":
    sys.exit(main(sys.argv))
<pre>
python3 sc.py program.sc < input
python3 sc.py -e ':|~^ : { ,;& }' < input
python3 sc.py --zeck 89        # -> :_________|
python3 sc.py --trace prog.sc  # bench dump per instruction

External resources

See also