OpenRune

Openrune Format

Specification draft v0.1 · 20 October 2026
Binary format version 1 · Experimental text transport

Openrune is an open format for creating, representing and exchanging custom graphical characters. Each character is called a rune: a monochrome geometric mark designed to sit alongside ordinary text.

A rune contains the geometric information needed to reproduce its shape. Applications that support Openrune can render that shape without downloading an image, installing a font or consulting a central registry. Applications without Openrune support generally show a conventional fallback character if the chosen transport preserves it.

This document describes the proposed shape grammar, canonical representation, binary format, text transport, rendering behaviour and optional Hyperrune mechanism. Version 0.1 is a public draft, not a ratified interoperability standard. Implementations should expect revisions and test against a shared reference implementation and fixtures before claiming conformance.

1. Principles and scope

The v0.1 draft does not specify multicolour runes, animation, free-positioned arbitrary vector paths, grids larger than 8 cells wide or 5 tall, or a central registry. Hyperrunes are an optional extension, not a prerequisite for displaying a rune.

2. Rune model and shape grammar

A rune occupies a rectangular grid of w × h square cells, where 1 ≤ w ≤ 8 and 1 ≤ h ≤ 5. Each cell is one unit wide and high. The origin is at the upper-left corner; x increases rightward and y downward. A cell is empty or contains one geometric primitive.

Why the limit is 8 × 5. A rune is drawn at the height of a capital letter, so it is scaled by its height. In ordinary 16 px text that is roughly 11 to 12 pixels, and detail finer than about 2 pixels cannot be read. A height of 5 cells keeps every cell at about 2 pixels or more at that size, and gives a square grid a true centre cell. Width only makes the rune longer without making the cells smaller, so it may be greater, up to 8, which allows short monograms and wordmarks. Larger grids are not valid runes. The limits can be raised in a later revision without breaking anything, because older software shows the plain fallback character for a rune it cannot read; lowering them would invalidate runes already made.

PrimitiveCodeParameterAllowed values
SquaresqSide as a percentage of cell size50, 60, 70, 80, 90, 100
TriangletriClockwise quarter-turn rotation0, 1, 2, 3
SemicirclesemiClockwise quarter-turn rotation0, 1, 2, 3
CirclecircDiameter as a percentage of cell size50, 60, 70, 80, 90, 100
Quarter circleqtrClockwise quarter-turn rotation0, 1, 2, 3

Tone. Every cell is either solid or hatched. A hatched cell is drawn as a fine checker of tiny square dots (see Hatching, section 3), which reads as half-strength ink at text size. A rune has no colour field: both tones take the colour of the surrounding text, and the hatched tone simply gives a mark a second, lighter value. Hatching is drawn as plain shapes, so it works in any font renderer and any browser, with no dependence on colour-font support. Tone is part of the geometry and therefore affects rune identity.

Bridges. Circles in cells that touch, whether side by side, one above the other or corner to corner, may be joined by a waisted bridge formed from tangent arcs. The same shape and the same neck apply in every direction. The rune-level parameter neck determines the waist width relative to the smaller circle's radius. It ranges from 0.10 to 1.00, with a default of 0.55. A bridge is invalid unless both endpoints contain circles. A bridge joins two shapes of the same kind and the same tone: both solid or both hatched. A bridge between a solid shape and a hatched one is invalid. Squares can be bridged too, only to other squares: a bridge between two squares is a straight bar along the line between their centres, neck times the side of the smaller square thick (all of it for Full), so two full-size squares joined by a Full bridge become one rectangle. A circle and a square cannot be bridged.

Column spacing. A rune may adjust the gap between adjacent columns by increments of one-tenth of a cell, from −0.9 to +0.9. This shifts the designated column and every column to its right; row positions do not move. Spacing is part of the geometry and therefore affects rune identity.

3. Geometry and drawing

Cell-local geometry

Each primitive is defined in local coordinates from (0, 0) to (1, 1) and translated to its cell. One clockwise quarter turn transforms a point as follows:

(px, py) → (1 − py, px)
PrimitiveOutline at rotation 0
SquareA square of side size / 100, centred in the cell: vertices (m,m) → (1−m,m) → (1−m,1−m) → (m,1−m) with m = (1 − size/100) / 2. At size 100 it fills the cell.
TriangleVertices (0,0) → (1,1) → (0,1); fills the lower-left half of the cell.
SemicircleArc of radius 0.5 from (0,1) over the top to (1,1), closed by the lower horizontal edge.
CircleCentred at (0.5,0.5), radius diameter / 200.
Quarter circleQuarter-disc of radius 1, centred at (0,1), bounded by (0,0), (1,1) and (0,1).

Bridge construction

For circle centres A and B, radii r1 and r2, centre separation d, and m = min(r1, r2), the intended waist half-width is q = neck × m.

For the ordinary non-degenerate case, the fillet-circle radius is described by:

R = (d²/4 + q² − m²) / (2 × (m − q))

The bridge joins tangent points on the two circles using concave fillet arcs. Full bridge. The formula is undefined at neck = 1.00 (the denominator is zero), so a neck of 0.99 or more is drawn as the Full bridge instead: the waist is as wide as the smaller circle, and the two sides are straight lines touching both circles on the outside. With two circles of the same size it is a pill; with different sizes the sides taper. For unit direction u from A to B, normal n (u turned a quarter turn anticlockwise on screen), and k = (r1 − r2) / d, the side normals are N = k·u ∓ √(1 − k²)·n, and the four corners are A + r1·N and B + r2·N for each of the two normals, joined by straight lines. Rune Maker offers 0.99 as Full. Because 0.99 is below the old limit of 1.00, a decoder that predates Full draws it as a nearly straight, very slightly curved bridge and still decodes the rune; nothing is rejected. The reference tools never write exactly 1.00.

Hatching

A hatched cell is divided into an 8 × 8 grid of small squares, each 1/8 of a cell wide. The squares where column + row is even (counting from 0 at the top-left) are dots; the others are left empty, so the top-left square is always a dot. A shape keeps only the dots whose centre lies inside it, with the shape's edge counted as inside (a tolerance of 1e-9 cells). Curves and slants therefore become fine stair-steps. Each dot is a clockwise square.

Dot positions follow the cell, so column spacing moves a hatched cell's dots with it. At ordinary text sizes a dot is a fraction of a pixel and the checker blends to an even half tone; enlarged, it shows as a halftone texture. The pattern is part of how a rune is drawn, not of what it encodes: only the tone bit is stored. Implementations should compare output against reference fixtures.

Column positioning and filled outlines

For spacing adjustment s_g (an integer from −9 to +9) at gap g, column x is drawn at:

X(x) = x + (Σ s_g for all g ≤ x) / 10

The drawn width is X(w − 1) + 1. Bridges follow the displaced circle centres. The renderer combines the shapes into a filled path using the non-zero fill rule to obtain a continuous monochrome silhouette. Implementations should compare output against reference fixtures, particularly for overlapping components, shared edges and bridge joins; identical outline generation across independent renderers remains a conformance goal.

4. Canonical form and identity

Canonicalisation produces a deterministic representation of a rune's construction.

  1. Trim the grid to the smallest rectangle containing its occupied cells, moving that rectangle to (0,0).
  2. Order cells from left to right, then top to bottom.
  3. Normalise rotations to 0–3; normalise circle diameters and square sizes to the supported 50–100 range in increments of 10.
  4. Validate bridges: retain only valid joins between circles in touching cells (corner-to-corner joins included), remove duplicates, and sort them deterministically.
  5. Normalise neck: round to two decimal places; use 0.55 when the rune has no bridges.
  6. Normalise column spacing: adjust positions after trimming, retain non-zero valid gap values in ascending order, and omit the field when there is no spacing.

Two runes with the same canonical construction encode to the same geometry bytes. This does not guarantee that visually identical shapes created through different primitive combinations have the same identity. Editors may simplify equivalent constructions, but such editing behaviour is not a substitute for a formally specified equivalence algorithm.

Content-derived identifier

The identifier is rune1- followed by 32 lowercase hexadecimal digits: the first 16 bytes of SHA-256 over the geometry encoding, with optional name and domain metadata omitted and the checksum excluded. Length and relevant flags are recalculated for that identity input.

For the Made Union example, the reported identifier is:

rune1-537f27cbf3d677979c2fda25d6e6622f

A shorter display form may show the first 12 hexadecimal digits; the full identifier is used for protocol comparisons and verification.

The identifier is content-derived, not centrally issued. Changing a rune's geometry changes its identifier. Names, domain claims, presentation colour and ordinary hyperlink destinations do not change it.

5. Binary representation — version 1

A complete encoded rune is at most 255 bytes. The fields appear in the following order:

SizeFieldDefinition
1 byteHeader0xA1 denotes Openrune binary version 1.
1 byteLengthTotal length including the header and final checksum.
1 byteWidth1–8.
1 byteHeight1–5.
1 byteFlagsBit 0 = sparse cells; bit 1 = name; bit 2 = column spacing; bit 3 = domain; other bits reserved and zero.
VariableCellsDense or sparse shape encoding, described below.
1 byteBridge countNumber of bridges.
Conditional 1 byteNeckneck × 100; present only if the bridge count is non-zero.
2 bytes per bridgeBridge entriesBig-endian 16-bit value. With the top bit clear: cellIndex × 2 + direction, where cellIndex is the left or upper cell and 0 = right, 1 = down. With the top bit set (corner to corner): 0x8000 + upperCellIndex × 2 + direction, where 0 = down and to the right, 1 = down and to the left. Every other bit pattern is invalid.
Conditional variableSpacingGap count followed by gap-number/signed-value pairs; present if flag bit 2 is set.
Conditional variableNameByte count followed by UTF-8 text; present if flag bit 1 is set.
Conditional variableDomainByte count followed by ASCII hostname; present if flag bit 3 is set.
1 byteChecksumCRC-8, polynomial 0x07, initial value 0, over all preceding bytes.

Cell encoding

A cell code uses the upper four bits for shape type: 1 square, 2 triangle, 3 semicircle, 4 circle and 5 quarter circle. Bit 3 (0x08) is the tone: 0 solid, 1 hatched. The lower three bits hold rotation 0–3 for a triangle, semicircle or quarter circle; for a circle the value is (diameter − 40) / 10 (1–6, so 0 is invalid); for a square it is (100 − size) / 10 (0–5, so 0 is a full-size square and 6–7 are invalid). Code 0x00 means an empty cell. For example, a solid full-size square is 0x10, the same square at 80% is 0x12, a hatched full-size square is 0x18, and a hatched full-size circle is 0x4e.

All fields, including optional metadata, must fit within the 255-byte length limit. An encoder should reject marks it cannot represent within that limit rather than truncate them.

Optional spacing

With flag bit 2 set, spacing begins with a gap count (1 to width − 1). Each entry is a gap number (1 to width − 1) followed by a signed byte from −9 to +9, excluding zero. Entries are ordered from left to right with no duplicates.

A gap at position 3 tightened by 0.4 cells is represented as 01 03 fc, where fc is the signed byte −4.

Optional accessible name

A rune may carry a human-readable name of up to 24 characters. The current draft permits ASCII letters, digits, spaces and the characters . ' & -. Names are NFC-normalised, trimmed and use single spaces. The binary field is length-prefixed UTF-8; decoders reject names violating the allowed character set or limits. The name does not influence rune identity.

Optional domain claim

A rune may carry a bare hostname for Hyperrune verification. The domain is untrusted metadata until verified separately.

The domain does not influence rune identity or drawing. See Hyperrunes.

Worked example: Made Union

The Made Union rune is represented in the current example as a 5 × 2 grid containing seven shapes, with no bridges or optional metadata, encoded in 17 bytes:

a1 11 05 02 00 | 00 10 20 10 20 | 10 00 10 00 10 | 00 | 61
header/length/dimensions/flags | first row | second row | bridges | CRC

With the optional name Made Union, the reported encoding is 28 bytes; adding the domain madeunion.co increases the reported total to 41 bytes. These example vectors are illustrative until confirmed against published conformance fixtures.

6. Text transport — experimental

The first proposed plain-text transport consists of:

  1. One visible fallback character.
  2. A sequence of invisible Unicode variation selectors, encoding the rune's binary bytes.

Suggested fallback characters include ■ (U+25A0), ◆ (U+25C6), ● (U+25CF) and ★ (U+2605). The fallback is chosen when the rune is copied and is not part of the rune's identity.

Byte-to-character mapping

Binary valueVariation-selector range
0–15U+FE00 through U+FE0F
16–255U+E0100 through U+E01EF, using value − 16 as the offset

A scanner can identify runs of these selectors, locate the 0xA1 binary header after any leading presentation selectors, read the indicated length, and validate the full payload. Invalid or partial payloads must not be rendered as valid runes. The original visible fallback is retained as the safe representation when decoding fails.

Example sizes

Measurements from the initial sample set, without optional name/domain metadata:

MarkGridBinary bytesUnicode code points, including fallbackUTF-16 code unitsUTF-8 bytes
Made Union5 × 217182864
MU6 × 219203272
Made8 × 223243886
Mixed-primitive test mark with bridges5 × 4313245109

Although common grapheme-segmentation implementations may treat the fallback-plus-selector sequence as a single grapheme cluster, platforms can still impose byte limits, strip invisible code points, transform text or provide different editing behaviour. A single grapheme cluster is not a promise that every app will treat a rune as one editable character.

Standards and compatibility warning

Unicode variation selectors have established uses, including registered variation sequences. The proposed use for carrying privately defined rune payloads is not an endorsed Unicode extension mechanism. Applications may remove or rewrite these code points, and some systems treat invisible payloads as a security concern.

This transport is experimental and must not be Openrune's only supported interchange mechanism. It does not make a rune universally displayable: the actual geometry requires an Openrune-aware renderer.

7. Other transport candidates

These alternatives could carry the same canonical binary payload. They are proposals, not fully specified interoperable transports in v0.1.

TransportProposed representationIntended use
Rich textInline element carrying base64url-encoded rune data, together with a plain-text fallbackEditors and websites that preserve HTML or structured inline objects
URLA URL containing a base64url representation of the rune bytesApplications that remove invisible Unicode data
ImagePNG/SVG rendered from canonical data, optionally including the rune identifier as metadataSystems without inline rune support
Private-use fontUser-installed font with private-use character mappingsLocal experimentation; not a reliable cross-platform exchange format

All proposed transports must preserve exact geometry and define validation, fallback and clipboard behaviour before they can claim interoperability.

8. Rendering and accessibility

The intended inline rendering model treats a rune as a typographic element aligned with capital letters:

Very wide runes can interrupt the rhythm of running text. Authoring tools may warn when the width-to-height ratio exceeds 4:1.

9. Hyperrunes — optional domain verification

A Hyperrune is a rune carrying a domain claim that a compatible application may verify. Verification can enable a trusted association and optional hyperlink, but it does not alter the underlying rune geometry.

A domain claim alone does not establish ownership or authenticity. The claimed website must publish a verification file at:

https://example.com/.well-known/openrune.json

Example verification file

{
  "version": 1,
  "runes": [
    {
      "id": "rune1-537f27cbf3d677979c2fda25d6e6622f",
      "name": "Made Union",
      "href": "/",
      "status": "current"
    }
  ]
}

The recognised keys are:

KeyRequiredPurpose
versionYesVerification-file schema version (1).
runesYesCollection of declared rune entries.
idYesFull content-derived rune ID.
nameNoDisplay name supplied by the domain.
hrefNoRelative path or same-domain URL; external destinations must not be accepted.
statusYescurrent or replaced.
replacedByNoFull ID of a successor rune when one replaces another.

A rune is verified for a given domain only when that domain's valid file explicitly lists the rune's full ID. Missing, invalid, unreachable or mismatched verification must not generate a verified badge or an automatic link.

Lookup and navigation safeguards

A Hyperrune is not a universally clickable Unicode character. Link behaviour exists only in applications that explicitly implement the Hyperrune extension. An ordinary rune can also be linked by a conventional rich-text editor without carrying any Hyperrune metadata.

10. Decoder and security requirements

A decoder must reject malformed or unsupported inputs rather than guess at missing geometry. At minimum, reject:

Drawing must not execute embedded code, evaluate arbitrary SVG/HTML, or fetch external resources. It operates only on validated primitives and geometric parameters.

Rendering and Hyperrune verification are separate operations. A rune must render without network access and without waiting for domain verification. A claimed domain must never be presented as proof of who authored, transmitted or owns a message.

Because the experimental text transport uses invisible characters, applications should treat it as untrusted input. Products incorporating Openrune into AI-assisted workflows may choose to remove the hidden payload from model-facing text while preserving a separate visual representation for users.

11. Early transport observations

An initial manual test on 19 October 2026 copied a Made Union rune through real applications, recovered the text and attempted a byte-for-byte decode. The tests recorded six successful text routes out of seven, with one failure. These are preliminary observations on specific devices and versions, not a universal platform compatibility guarantee.

RouteObserved result
Claude chat title, MacEncoded data recovered intact
ChatGPT chat title, MacEncoded data recovered intact
Public Voice post, MacEncoded data recovered intact
Apple Notes, MacEncoded data recovered intact
WhatsApp sent to self, AndroidEncoded data recovered intact
WhatsApp round trip via iPhone, Android ↔ iPhoneEncoded data recovered intact
Gmail, MacInvisible data removed; only fallback recovered

A separate custom-font experiment preserved a private-use character in a Claude title but displayed it as an empty box in Safari; that result is not a successful text-transport route.

These tests establish only whether the transported bytes survived particular workflows. They do not establish that unsupported applications displayed the actual mark. Other services, devices, editing workflows and future updates may behave differently.

12. Implementation status and versioning

This document specifies a proposed binary version 1 within the broader v0.1 draft of the Openrune format. These are different version numbers: the first identifies encoded bytes; the second identifies the maturity of the specification.

An initial JavaScript implementation, openrune-core.js, and round-trip tests exist. Independent implementers will need publicly available conformance vectors and cross-implementation tests. This draft is not yet a completed, independently verified interoperability standard.

Corner-to-corner bridges (added 21 October 2026, before any decoders shipped). Bridge entries gained a second form that joins circles at opposite corners of a 2 × 2 block. It uses the top bit of the entry, which was previously invalid, so every rune made without such a bridge keeps exactly the same bytes and the same identifier. A decoder that predates this change will reject a rune that uses one and show its fallback character. Example: a 2 × 2 rune with a full-size circle in the top-left and bottom-right cells, joined by a medium bridge, encodes in 14 bytes as a10e020200460000460137800038.

Two tones (added 21 October 2026). A cell code gained a tone bit (0x08), which was previously unused. Every rune made without hatching keeps exactly the same bytes and the same identifier, and hatching costs no extra bytes. A decoder that predates this change reads a hatched cell as an unknown shape, rejects the rune and shows its fallback character. Example: a 2 × 1 rune with a solid square then a hatched square encodes in 9 bytes as a1090201001018009a.

Square sizes and square bridges; circles from 50% (added 22 October 2026, before any decoders shipped). A square gained a size, using the lower bits of its cell code that were previously required to be zero (0x10 is still a full-size square), and squares can now be bridged to other squares. The smallest circle is now 50%, so the code that meant 40% is invalid. Every rune made without these features keeps exactly the same bytes and the same identifier. A decoder that predates this change rejects a rune that uses a smaller square or a square bridge, and shows its fallback character. Example: two 80% squares side by side, joined by a medium bridge, encode in 12 bytes as a10c0201001212013700008f.

Any incompatible change to binary parsing, canonicalisation or geometry requires explicit versioning and matching test vectors. Experimental transport details and Hyperrune behaviour may be revised as security and interoperability testing continues.

Project: openrune.co
Specification text: CC BY 4.0 (as stated by the project)
Reference implementation: Apache 2.0 where published under that licence