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.

Ceremonial

From Esolang
Jump to navigation Jump to search
Official Ceremonial logo

Ceremonial (current version is Ceremonial 5, also called the "MORTUS" edition) is an esoteric language by BlueHaunter (_bluehaunter). It’s implemented in Python and programs live in .cmnl files. The entire design goal is to make writing code as miserable as possible. Cryptographic checksums, letter-counting rules (one of them has to be a prime), forced politeness, typed variable names, numerals that die after five reads, decaying praise, the works. Somehow it is still Turing-complete. There’s even a small Tkinter graphics system glued on.

Ceremonial 3 was hell. Ceremonial 4 “ABYSS” was under hell. Ceremonial 5 “MORTUS” is whatever is past that. The big change from version 4 is that nothing is random any more — every rule is deterministic. If a program works once, it will keep working. Windows stay open for as long as you want them to. It’s still extremely hard, just never impossible.

--BlueHaunter (talk) 12:05, 3 October 2026 (UTC)

Global Source Constraints

Before the interpreter even looks at your program, the source itself has to pass a bunch of checks:

  • The letter e (case-insensitive) must appear a multiple of 9 times in the whole file.
  • The letter o (case-insensitive) must appear a prime number of times.
  • The total number of lines must be a multiple of 6.
  • Some statements have to start with pls. The polite ones have to make up between 20 % and 30 % of all statements. Too few → insufficientlypolite. Too many → excessivelypolite.
  • Ordinary # comments are illegal. The only allowed form is ##plsignorethisline "something". The text inside the quotes cannot be empty and has to contain at least one vowel. People usually dump extra e’s and o’s in these comments to fix the letter counts.
  • No uppercase letters in any code tokens.
  • A statement may not start with the same letter as either of the two previous statements (this is checked per block / fork branch).
  • Every statement ends with a space followed by a semicolon ( ;). A bare ; is a syntax error.
  • The top-level summon must end with halt. Everything dies.

Scope Architecture & Block Checksums

Everything lives inside nested scopes called summons.

  • Variable and block names have a fixed length: depth + 4. Top-level names are exactly 4 characters, the next level 5, etc. They also need at least one vowel and can only be declared once.
  • Inside a block every statement is tagged with the block’s name in tildes, e.g. ~name~.
  • After every four statements you are forced to insert an omen (which is just another nested summon). A plain omen ; statement is only a filler for breaking consecutive starting letters; it does not count as the required omen summon.
  • At the end of every block (summons, spin bodies, proc bodies) you need four checksum lines in this exact order:
  1. #sum:N – exact number of statements in the block
  2. #product:P – running product of the statement counts, modulo 997
  3. #hash:HEX – first 8 hex digits of the SHA-1 of the concatenated names of the sub-blocks
  4. #mercy:N – how many pls statements are directly in this block (fork branches count, nested blocks do not)
  • You close the block with then followed by a word taken from this fixed cycle of 12: end, close, hush, amen, perish, vanish, wither, repent, kneel, bleed, suffer, atone. Which word you get is determined by the statement count, the depth, a couple of bytes from the hash, and the mercy count.

The error messages tell you the expected values and the expected closing word, which is useful once you stop crying.

Typed Names

The first letter of every variable name is its type and has to match what the statement expects:

Prefix Type
g glyph (single character)
l loom (string builder)
n numeral (integer)
c cell (array)
b bool (from compare or key_down)
p proc
a proc parameter (fits any slot)

So a top-level numeral might be called nax0 (4 characters, starts with n, contains a vowel).

Data Types & Mechanics

  • Numerals are arbitrary-size integers written in base 3 with a leading t (t120 = 15). They are mortal: after five reads a numeral dies, so anything that needs to live longer has to be stored in a cell. You also have to praise a numeral before every single read. Results of reckon, peek, etc. are born unpraised.
  • Glyphs are single characters. They must be blessed (praised) before they can be woven; weaving consumes the blessing.
  • Looms build strings. You tune a loom, then weave glyphs into it. You cannot weave two glyphs of the same kind (digit/letter) in a row. A loom holds at most 7 threads. Before you can unspool you have to confess your sins. unspool prints the threads backwards and consumes the loom.
  • Cells are arrays (up to 10 000 000 slots) of immortal integers. Created with array, written with poke, read with peek. Indices can be literals or numeral variables.
  • Procs take exactly one argument and return nothing; communication happens through cells.

Statements

Statement Meaning
bind ~s~ [g] astype ( glyph ) becomes truly worth 'c' ; create a glyph
forge ~s~ [n] astype ( numeral ) worth t120 ; create a numeral (base 3)
loom ~s~ [l] opens ; create a loom
tune ~s~ [l] with harmony ; tune a loom so it can be woven
weave ~s~ [l] thread [g] ; add a blessed glyph to a loom
confess ~s~ sins ; required before each unspool
unspool ~s~ [l] ; print a loom (backwards) and consume it
shownum ~s~ [n] ; print a numeral in decimal
praise ~s~ [x] highly ; praise a numeral or bless a glyph
insult ~s~ [x] cruelly ; negate a numeral and strip its praise, or unbless a glyph
reckon ~s~ [n] '+' [n] into [n] ; arithmetic with + - * / % (division and modulo by zero give 0)
compare ~s~ [n] <= [n] into [b] ; comparison with < > <= >= == !=
fork ~s~ [x] truepath { ... } falsepath { ... } ; branch on a glyph, bool or numeral (the condition is consumed)
spin ~s~ while A op B does ... then X loop; each operand is [n] or cell [c] index
define ~s~ proc [p] needs ( [a] ) does ... then X define a procedure
invoke [p] with ( [x] ) ; call a procedure
array ~s~ [c] size N ; create a cell
poke ~s~ [c] at I value [n] ; write to a cell
peek ~s~ [c] at I into [n] ; read from a cell into a fresh numeral
duplicate ~s~ [x] into [y] ; copy a value (the new name must share the original's prefix)
runeflip ~s~ [g] into [g] ; turn a letter into a digit glyph or a digit into a letter glyph
listen ~s~ into [g] ; read one keypress/character from the console as a glyph (NUL at end of input)
ascend ~s~ [g] into [n] ; character code of a glyph
descend ~s~ [n] into [g] ; glyph from a character code
omen ; does nothing (a filler)
halt ; stop the program

Runtime Mechanics & Graphics

Nothing is random any more. Every punishment is deterministic.

Every 13th iteration of any spin, the alphabetically first live numeral loses its praised status. This is intentional and annoying.

There is a small Tkinter graphics system (stage, rect, plot, frame, key_down). Drawing two rectangles or plots with the exact same colour in a row raises colorstagnation. Console output still goes to the terminal as usual.

Resource limits (--deadline, --max-spin, --max-depth) exist but default to “no limit” (except a 10 000 deep recursion ceiling that can also be removed).

Turing-completeness

Unbounded integers, immortal cells that can be indexed by variables, conditional loops and recursion are enough. You can build a two-counter machine. The lifespan and praise rules just make it painful rather than impossible.

Running programs

@echo off
title Ceremonial 5 Mortus
if "%~1"=="" (
    echo Right-click a .cmnl file, Open with, pick ceremonial_run.bat
    pause
    exit /b 1
)
cd /d "%~dp1"
if exist "%~dp0ceremonial.exe" (
    "%~dp0ceremonial.exe" "%~1" --pause
    goto :done
)
py --version >nul 2>nul
if %errorlevel%==0 (
    py "%~dp0ceremonial.py" "%~1" --pause
    goto :done
)
python --version >nul 2>nul
if %errorlevel%==0 (
    python "%~dp0ceremonial.py" "%~1" --pause
    goto :done
)
echo No working Python found. Install it from python.org (tick "Add Python to PATH"),
echo or put a ceremonial.exe next to this bat.
:done
pause

Put ceremonial.py and ceremonial_run.bat in a folder with your .cmnl file. On Windows, right-click the file, choose Open with, and pick the bat file. Or from a terminal:

py ceremonial.py yourfile.cmnl

Useful flags: --check (only validate), --pause (wait for Enter before closing), --fps, --deadline, --max-spin, --max-depth.

Implementations

Printing hello world in Ceremonial

Every glyph gets its own loom, because unspool prints backwards and two glyphs of the same kind can’t be woven in a row.

summon {main} does bind ~main~ [ga00] astype ( glyph ) becomes truly worth 'H' ; praise ~main~ [ga00] highly ; loom ~main~ [la00] opens ; pls tune ~main~ [la00] with harmony ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush weave ~main~ [la00] thread [ga00] ; confess ~main~ sins ; unspool ~main~ [la00] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls bind ~main~ [ga01] astype ( glyph ) becomes truly worth 'e' ; praise ~main~ [ga01] highly ; loom ~main~ [la01] opens ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush tune ~main~ [la01] with harmony ; pls weave ~main~ [la01] thread [ga01] ; confess ~main~ sins ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush unspool ~main~ [la01] ; bind ~main~ [ga02] astype ( glyph ) becomes truly worth 'l' ; pls praise ~main~ [ga02] highly ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush loom ~main~ [la02] opens ; tune ~main~ [la02] with harmony ; weave ~main~ [la02] thread [ga02] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls confess ~main~ sins ; unspool ~main~ [la02] ; bind ~main~ [ga03] astype ( glyph ) becomes truly worth 'l' ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush praise ~main~ [ga03] highly ; pls loom ~main~ [la03] opens ; tune ~main~ [la03] with harmony ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush weave ~main~ [la03] thread [ga03] ; confess ~main~ sins ; pls unspool ~main~ [la03] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush bind ~main~ [ga04] astype ( glyph ) becomes truly worth 'o' ; praise ~main~ [ga04] highly ; loom ~main~ [la04] opens ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls tune ~main~ [la04] with harmony ; weave ~main~ [la04] thread [ga04] ; confess ~main~ sins ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush unspool ~main~ [la04] ; pls bind ~main~ [ga05] astype ( glyph ) becomes truly worth ' ' ; praise ~main~ [ga05] highly ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush loom ~main~ [la05] opens ; tune ~main~ [la05] with harmony ; pls weave ~main~ [la05] thread [ga05] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush confess ~main~ sins ; unspool ~main~ [la05] ; bind ~main~ [ga06] astype ( glyph ) becomes truly worth 'W' ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls praise ~main~ [ga06] highly ; loom ~main~ [la06] opens ; tune ~main~ [la06] with harmony ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush weave ~main~ [la06] thread [ga06] ; pls confess ~main~ sins ; unspool ~main~ [la06] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush bind ~main~ [ga07] astype ( glyph ) becomes truly worth 'o' ; praise ~main~ [ga07] highly ; pls loom ~main~ [la07] opens ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush tune ~main~ [la07] with harmony ; weave ~main~ [la07] thread [ga07] ; confess ~main~ sins ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls unspool ~main~ [la07] ; bind ~main~ [ga08] astype ( glyph ) becomes truly worth 'r' ; praise ~main~ [ga08] highly ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush loom ~main~ [la08] opens ; pls tune ~main~ [la08] with harmony ; weave ~main~ [la08] thread [ga08] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush confess ~main~ sins ; unspool ~main~ [la08] ; pls bind ~main~ [ga09] astype ( glyph ) becomes truly worth 'l' ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush praise ~main~ [ga09] highly ; loom ~main~ [la09] opens ; tune ~main~ [la09] with harmony ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls weave ~main~ [la09] thread [ga09] ; confess ~main~ sins ; unspool ~main~ [la09] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush bind ~main~ [ga10] astype ( glyph ) becomes truly worth 'd' ; pls praise ~main~ [ga10] highly ; loom ~main~ [la10] opens ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush tune ~main~ [la10] with harmony ; weave ~main~ [la10] thread [ga10] ; pls confess ~main~ sins ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush unspool ~main~ [la10] ; halt ; #sum:103 #product:703 #hash:b87de3ab #mercy:19 then wither ##plsignorethisline "aeeoooooo"

Drawing a Shape in Ceremonial (graphics test)

Draws two overlapping squares in a window that stays open until you close it. The position lives in a cell because numerals die after five reads.
summon {main} does array ~main~ [cam0] size 4 ; stage ~main~ width 400 height 300 title 'd' ; forge ~main~ [nax0] astype ( numeral ) worth t1212 ; pls praise ~main~ [nax0] highly ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush omen ; confess ~main~ sins ; poke ~main~ [cam0] at 2 value [nax0] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls forge ~main~ [nay0] astype ( numeral ) worth t1020 ; omen ; praise ~main~ [nay0] highly ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush confess ~main~ sins ; pls omen ; poke ~main~ [cam0] at 3 value [nay0] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush forge ~main~ [nao0] astype ( numeral ) worth t1 ; omen ; pls praise ~main~ [nao0] highly ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush confess ~main~ sins ; omen ; poke ~main~ [cam0] at 1 value [nao0] ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls spin ~main~ while cell [cam0] 0 != cell [cam0] 1 does frame ~main~ into [nfa0] ; peek ~main~ [cam0] at 2 into [nep0] ; omen ; pls confess ~main~ sins ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush peek ~main~ [cam0] at 3 into [neq0] ; omen ; confess ~main~ sins ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush pls praise ~main~ [nep0] highly ; omen ; confess ~main~ sins ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush praise ~main~ [neq0] highly ; pls rect ~main~ x [nep0] y [neq0] w 100 h 100 color '0' 'f' '0' ; omen ; summon {aeiou} does #sum:0 #product:1 #hash:da39a3ee #mercy:0 then hush confess ~main~ sins ; rect ~main~ x [neq0] y [nep0] w 100 h 100 color 'f' '0' '0' ; #sum:19 #product:82 #hash:f26ae89f #mercy:3 then end pls halt ; #sum:27 #product:880 #hash:af102d93 #mercy:6 then perish ##plsignorethisline "aeeeeeeoo"