GenoCode Getting startedThe languageCreatures Open the app

The GenoCode language

Version 0.9, as built by @genocode/core 0.9.

This document is the definition of GenoCode. When the implementation and this document disagree, this document is right and the implementation is fixed, unless the document is changed first. Parts marked provisional may still change.

Contents

  1. Overview
  2. A first creature
  3. Files
  4. Lexical structure
  5. Values and types
  6. Genes
  7. Variables
  8. Expressions
  9. Control flow
  10. Parts and functions
  11. The turtle
  12. Shapes
  13. Colour and material
  14. Textures
  15. Backgrounds
  16. Time and music
  17. Dance styles
  18. Library
  19. Genetics
  20. Building a creature
  21. Blocks
  22. Grammar
  23. Not yet settled

1. Overview

GenoCode is a small language for describing creatures. A GenoCode program drives a 3D turtle that moves, turns, grows and lays down simple shapes: balls, cubes, cones, tubes, rings and donuts. The shapes it lays make up the creature.

Some values in a program are declared as genes. The app can mutate genes (nudge them), breed them (mix two parents) and morph them (slide smoothly from one creature to another). Everything else in the program stays the same. A program therefore describes a whole family of related creatures, and each set of gene values is one member of that family.

Creatures can react to time and music, so how a creature dances can be written into its genes as well. A creature file can also carry the creature's background and its dance style.

Creatures can be written as text or built from blocks. Both are views of the same program, and every construct in the language corresponds to exactly one block.

Principles

2. A first creature

creature Flower
// Petals laid round the centre, each a squashed cone, in rings that shrink and turn.

gene rings  = 3   in 1..6 step 1  // how many rings of petals
gene shrink = 0.7 in 0.4..0.95    // each ring's size compared with the ring outside it
gene twist  = 29  in 0..180       // how far each ring turns from the last
gene heart  = hsv(47, 0.9, 1) in hsv(0, 0.3, 0.5)..hsv(360, 1, 1)

branch {
  shape ball
  colour heart
  grow 1.6
  lay
}

let size = 1
repeat rings {
  roll twist
  whorl(size)
  size *= shrink
}

part whorl(scale) {
  gene petals = 7   in 3..16 step 1
  gene reach  = 4   in 1.5..7   // how long each petal is
  gene width  = 1.4 in 0.3..3
  gene cup    = 23  in -57..69  // how far the petals curl
  gene petal  = hsv(324, 0.7, 1) in hsv(0, 0.3, 0.5)..hsv(360, 1, 1)

  colour petal
  shape cone
  repeat petals as i {
    branch {
      roll 360 * i / petals
      pitch 90 - cup
      grow scale
      forward reach / 2
      lay { grow (width, width * 0.3, reach) }
    }
  }
}

3. Files

GenoCode files have the extension .genie and are encoded in UTF-8. There are four kinds, depending on the first line.

Creature files start with creature <Name>. After the header, in any order:

Background files start with background <Name> and hold the contents of one background block.

Dance files start with dance <Name> and hold the contents of one dance block.

Pattern files start with pattern <Name> and hold the contents of one pattern block.

The name after creature, background, dance or pattern is a name in the sense of section 4. Comment lines directly below the header are the file's description, which the app shows.

Parts and functions can be used before they are defined. There are no forward declarations.

4. Lexical structure

Statements end at the end of a line or at ;. A line continues onto the next when it ends inside unclosed ( ) or [ ], or ends with an operator, a comma, .. or =. Brackets can't hold braces, so a { or } ends any brackets left open before it. Blocks are written with { }. An opening { must be on the same line as the statement it belongs to.

Comments are // to the end of the line, or /* … */, which may span lines but doesn't nest. A // comment at the end of a gene line is that gene's description.

Names are made of letters, digits and _, and don't start with a digit. Case matters: Size and size are different names.

Numbers are written in decimal: 3, 0.25, 12.5e-3. A decimal point must have a digit on both sides, so 1..6 reads as 1, .., 6. A number may be followed by °, which has no effect (angles are always degrees): 90° is 90. There are no negative literals; -3 is the operator - applied to 3.

Hex colours are #rrggbb or #rrggbbaa, in hexadecimal digits.

Strings are written in double quotes, with the escapes \", \\ and \n. They are only allowed as arguments to print.

Keywords can't be used as names:

creature  gene  part  function  return  let  if  else  repeat  as  for  from  to  by  while
branch  lay  reshape  symmetric  shape  colour  color  shine  sharpness  texture  wear
background  fill  dance  forward  back  move  pitch  yaw  roll  rotate  around  grow  mirror  in  step  log
at  and  or  not  true  false  print  turtle  time  beat  music  variant

Some words are only special in one place, and are ordinary names elsewhere:

Built-in names, such as the library functions, constants, colour names, shape names and pattern kinds, and the words gradient and spectrum, can't be used as names of genes, variables, parameters, parts or functions.

colour and color mean the same thing everywhere, including in turtle.colour / turtle.color.

5. Values and types

GenoCode is dynamically typed: values have types, and variables don't. Using a value of the wrong type is an error, reported with its line and column, either when the file is checked or when the creature is built.

Type Written as Notes
number 3, 0.25, 1.5e3 64-bit floating point. There's no separate integer type
bool true, false
vector (1, 0, 0.5) three numbers: a position, direction or size per axis. (a) is just a in brackets
colour red, hsv(200, 0.8, 1), rgb(1, 0.5, 0), #ff8000 section 13
gradient gradient(red, yellow at 0.3, blue) a colour that varies from 0 to 1 (section 13.3)
shape ball, cube, cone, tube, ring, donut section 12
pattern pattern(marble, 200, 7), none a generated texture (section 14)
pattern kind marble, cells, … the first argument of pattern
scheme jewel, dusk, … a pattern's colour scheme
finish matte, pearl, … a pattern's finish
way of mixing warp, blend, mask, carve how mixed adds a second pattern
list [1, 2, 3], [] values in order, read by position: l[0] (section 8.3)
picture clouds(6, 4), picture { … } a grey picture, in pattern code only (section 14.1)

Whole numbers. Where a whole number is needed (a repeat count, a shape number), the value is rounded to the nearest whole number, with halves rounded up (2.5 → 3, -2.5 → -2). Nothing else rounds: 7 / 2 is 3.5.

Parts of values are read with a dot. They're read-only; build a new value to change one.

Type Parts
vector .x, .y, .z
colour .hue (0–360), .sat, .val, .alpha (0–1)

6. Genes

A gene is a named value that the app is allowed to change. The value written in the declaration is the creature's current value. Section 19 describes exactly how genes are mutated, bred and morphed.

6.1 Declaring genes

gene twist   = 29 in 0..180                                   // number
gene petals  = 7 in 3..16 step 1                              // number, in whole steps
gene size    = 1 in 0.1..10 log                               // number, works by ratios
gene stretch = (1, 1, 0.2) in (1, 1, 0.1)..(1, 1, 3)          // vector
gene skin    = hsv(120, 0.6, 0.9) in hsv(60, 0.3, 0.5)..hsv(180, 1, 1)   // colour
gene spiky   = true                                           // bool
gene body    = cone from [ball, cone, tube]                   // choice
gene limb    = leg from [leg, fin, wing]                      // part choice
Kind Form Rules
number = v in min..max min ≤ v ≤ max, min < max
stepped number = v in min..max step s s > 0. v should be min + k·s for a whole k (a warning otherwise)
log number = v in min..max log 0 < min. Can't be combined with step
vector = v in min..max each axis separately: min ≤ v ≤ max on every axis. An axis whose min and max are equal stays fixed. log is allowed if every axis's range is above zero
colour = c in min..max each channel separately, as for vectors, with hue on a circle (section 6.4)
bool = true or = false no range
choice = v from [a, b, …] at least two options, all of one type: numbers, vectors, colours, shapes, pattern kinds, schemes, finishes or ways of mixing. v must be one of them
part choice = p from [p1, p2, …] options are names of parts that all take the same number of parameters (section 6.5)

Values, bounds and options must be constant expressions: literals, built-in constants, colour and shape names, and arithmetic and built-in maths functions applied to constant expressions. 360 / 7 and hsv(30, 1, 1) are constant; another gene, a variable, noise, random, time and music are not.

6.2 Where genes can go

A gene can be declared at the top level of a file, or directly inside a part, function, background or dance body. It can't be declared inside if, a loop, branch, symmetric, lay { } or reshape { }.

A gene has one value for the whole creature. A gene declared in a part has the same value every time the part runs, however many times it's called. To vary something from call to call, pass a parameter, or use noise.

Genes can't be assigned to: twist = 5 is an error.

6.3 Full names

A gene's full name is its name, prefixed by where it's declared:

Declared in Full name
top level twist
part or function whorl whorl.petals
background block background.hue
dance block dance.drive

Full names must be unique within a file. The app uses full names to match genes between creatures when breeding and morphing, so renaming a gene cuts it off from other creatures' versions of it.

The full name seed is reserved (section 18.5).

6.4 Colour genes

A colour gene's range applies to each channel separately. Saturation, value and alpha are ordinary ranges. Hue is a circle (0 and 360 are the same hue). A range's hue bounds are used as written, before any wrapping:

6.5 Part genes

A part choice gene names a part. Calling the gene calls whichever part it currently names:

gene limb = leg from [leg, fin, wing]

symmetric x {
  limb(2, side)
}

part leg(reach, side)  { … }
part fin(reach, side)  { … }
part wing(reach, side) { … }

6.6 Variants

A creature can keep other sets of gene values alongside its own: its variants. Together with the creature itself, they form a family of related creatures that share one program, such as the members of an evolved population.

creature Cobra

gene hood  = 1.2 in 0.5..2  // how wide the hood spreads
gene coils = 3   in 1..6 step 1
gene skin  = hsv(120, 0.7, 0.8) in hsv(60, 0.3, 0.5)..hsv(180, 1, 1)

grow hood
repeat coils {
  colour skin
  forward 1
  lay
}

variant Coiled { coils = 6; hood = 0.8 }

variant Striker {
  hood = 2
  skin = hsv(70, 0.9, 1)  // bright, to warn others off
}

7. Variables

let size = 1          // create a variable
size = size * 0.9     // change it
size *= 0.9           // the same. Also += -= /=

8. Expressions

8.1 Operators

From lowest to highest precedence:

Operators Meaning Operands
or true if either side is bools
and true if both sides are bools
not (prefix) the opposite bool
== != equal, not equal two values of the same type: numbers, bools, vectors, colours, shapes, and the pattern words (kinds, schemes, finishes, ways of mixing)
< <= > >= comparisons numbers
+ - add, subtract numbers, or two vectors; + also joins two lists
* / % multiply, divide, remainder numbers; vector with number; two vectors (8.2)
- (prefix) negate number or vector
^ power numbers
.name f(…) skin(t) l[i] part of a value, call, sampling a gradient (13.3) or a picture, an item of a list (8.3)

8.2 Vector arithmetic

Expression Result
vector + vector, vector - vector per axis
vector * number, number * vector, vector / number every axis scaled
vector * vector, vector / vector per axis
- vector every axis negated

Colours, gradients, shapes and patterns don't take arithmetic operators. Use the library functions instead.

8.3 Lists

A list holds values in order. Lists are written in square brackets, and grow by joining:

let points = []
repeat 20 {
  points += [(random(-1, 1), random(-1, 1), 0)]
}
let first = points[0]
let count = length(points)

9. Control flow

if petals > 10 {
  shape ball
} else if petals > 5 {
  shape cone
} else {
  shape cube
}

repeat 5 { … }                       // 5 times
repeat petals as i { … }             // i = 0, 1, … petals − 1

for i from 1 to segments - 1 { … }   // i = 1, 2, … segments − 1
for a from 0 to 360 by 45 { … }      // a = 0, 45, … 360

while size > 0.1 { … }

10. Parts and functions

part leg(reach, bend) {        // draws
  forward reach
  pitch bend
  lay
}

function wave_height(t, h) {    // calculates
  return sin(t * 360) * h
}

leg(2, 30)
let h = wave_height(0.25, 3)

Parts are pieces of creature. A part call is a statement. A part may move the turtle, lay shapes, call parts and functions, and change globals. return (with no value) leaves it early. A part with no parameters may be defined and called without brackets: part tail { … }, tail.

Functions calculate values. A function call is an expression, and a function must end by returning a value with return <expression>. A function may read the turtle, globals and genes, and call other functions, but it must not change the turtle, lay shapes, call parts or change globals. Doing so is an error.

11. The turtle

11.1 State and directions

The world is right-handed: x to the right, y up and z towards the viewer. The turtle has:

State Starts as Changed by
position (0, 0, 0) forward, back, move
side axis (x), up axis (y), heading (z) the world's x, y and z axes pitch, yaw, roll, rotate, mirror, grow with a negative value
size per axis (1, 1, 1) grow
mirrored false mirror, grow with a negative value
shape transform none (identity) reshape, lay { }
shape ball shape
colour white colour
shine, sharpness 0, 0 shine, sharpness
texture none texture
wear 1 wear

The turtle faces along its heading. Its size is kept per axis: the x size belongs to the side axis, the y size to the up axis and the z size to the heading, and each stays with its axis when the turtle turns.

11.2 Moving

Command Effect
forward d move d × size.z along the heading
back d forward -d
move v move v.x × size.x along the side axis, v.y × size.y along the up axis and v.z × size.z along the heading

11.3 Turning

Angles are in degrees. Each turn is a rotation about one of the turtle's own axes, using the right-hand rule: with the right thumb along the axis, positive angles turn the way the fingers curl.

Command Rotates about Positive angles
pitch a the side axis tip the heading towards the turtle's down (nose down)
yaw a the up axis turn the heading towards the side axis
roll a the heading turn the side axis towards up
rotate a around v the axis v.x·side + v.y·up + v.z·heading right-hand rule about that axis. A zero vector does nothing

When the turtle is mirrored, its axes form a left-handed set, so the same commands produce the mirror image of the turns they would make unmirrored. This is what makes mirrored halves match.

11.4 Growing and mirroring

Command Effect
grow k multiply the size of every axis by abs(k). A negative k mirrors all three axes
grow v multiply each axis's size by the absolute value of the matching part of v. Each negative part reverses that axis and flips mirrored
mirror x, mirror y, mirror z reverse that axis and flip mirrored. mirror x is the same as grow (-1, 1, 1)

Size affects everything after it: later moves go further and later shapes are bigger.

11.5 Branches

branch {
  forward 2
  lay
}

branch saves the whole turtle state (every row of the table in 11.1), runs the block, and restores the state, whatever the block did.

11.6 Symmetry

symmetric x {
  wing(side)
}

symmetric a { body } runs body twice, each time inside its own branch: first unchanged, then after mirror a. Inside the body, the read-only variable side is 1 on the first run and -1 on the second. The axis is x, y or z.

11.7 The shape transform

Besides the turtle, there's a shape transform: an extra position, set of axes and size that is applied to each shape as it's laid, without moving the turtle.

reshape { grow (1, 0.3, 4); pitch 10 }
lay
lay                               // both shapes squashed and tilted

lay { grow (1, 0.3, 4) }          // only this shape

A shape is placed by taking its geometry (section 12), applying the shape transform, and then applying the turtle (its size, its axes and its position, in that order).

12. Shapes

Command Effect
shape s choose the shape for later lays. s is a shape, or a number: rounded, then wrapped into 0–5 by abs(n) % 6, in the order tube, cube, cone, ball, ring, donut
lay lay the current shape
lay { … } lay it with extra shape transform commands (11.7)

Each laid shape records its placement, shape, colour, shine, sharpness, texture and wear at the moment it's laid.

Geometry. Every shape fits in a 1×1×1 box centred on the turtle. Its length axis, where it has one, is the heading.

Shape Geometry
cube a cube with sides of 1
ball a sphere 1 across
tube a cylinder 1 across and 1 long, along the heading
cone a cone 1 long along the heading, with a base 1 across at the back and its tip at the front
ring a thin torus around the heading, 1 across and 0.2 thick
donut a fat torus around the heading, 1 across and 1/3 thick

13. Colour and material

13.1 Colours

Form Example Notes
name red red, orange, yellow, green, cyan, blue, purple, magenta, pink, brown, white, grey / gray, black, transparent
HSV hsv(h, s, v), hsv(h, s, v, a) hue in degrees (wrapped into 0–360), saturation, value and alpha 0–1 (clamped)
RGB rgb(r, g, b), rgb(r, g, b, a) each 0–1 (clamped)
hex #ff8000, #ff800080 the last pair, if there is one, is alpha

Whichever form it's written in, a colour is a value of hue, saturation, value and alpha. Genes, ranges, blending and gradients all work on those four channels, with hue on a circle. Alpha is opacity: 1 is solid and 0 is invisible.

Blending two colours (in mix, smoothmix, gradients and morphing) blends saturation, value and alpha in a straight line, and hue the short way round the circle. Hues exactly 180 degrees apart blend in the increasing direction. Spectrums (13.3) are the one exception.

The named colours are:

Name Value Name Value
red hsv(0, 1, 1) magenta hsv(300, 1, 1)
orange hsv(30, 1, 1) pink hsv(330, 0.4, 1)
yellow hsv(60, 1, 1) brown hsv(30, 0.75, 0.45)
green hsv(120, 1, 1) white hsv(0, 0, 1)
cyan hsv(180, 1, 1) grey, gray hsv(0, 0, 0.5)
blue hsv(240, 1, 1) black hsv(0, 0, 0)
purple hsv(270, 1, 0.8) transparent hsv(0, 0, 1, 0)

13.2 Material

Command Effect
colour c colour for later shapes. c is a colour (to use a gradient, sample it: colour skin(t))
shine x strength of highlights, 0–1 (clamped)
sharpness x how tight the highlights are, 0–1 (clamped)
texture p texture for later shapes: a pattern, or none (section 14)
wear x how much of their texture later shapes wear, 0–1 (clamped): 0 the plain colour, 1 all texture

13.3 Gradients

let skin = gradient(black, red at 0.2, yellow, white at 1)
repeat 20 as i {
  colour skin(i / 19)
  forward 1
  lay
}

Spectrums. spectrum(…) is written and sampled like gradient(…), with one difference: between two stops, hue moves in a straight line from one number to the other, never across 0°. So spectrum(blue, red) passes through cyan, green and yellow (240° down to 0°), where gradient(blue, red) goes the short way, through magenta.

let flame = spectrum(hsv(60, 1, 1), hsv(0, 1, 1), hsv(280, 1, 0.5))   // yellow, red, then back through every hue to violet

14. Textures

Textures are generated from a recipe of a few values, so they can be genes and evolve with the creature.

gene skin_kind   = marble from [marble, cells, aurora, plasma]
gene skin_hue    = 200 in 0..360
gene skin_seed   = 7 in 0..99 step 1
gene skin_scheme = dusk from [jewel, duotone, split, analogous, pastel, dusk, electric]
gene skin_finish = pearl from [matte, metallic, iridescent, pearl, satin]

gene skin        = 1 in 0..1   // how much of its texture it wears

texture pattern(skin_kind, skin_hue, skin_seed, skin_scheme, skin_finish)
wear skin
lay
texture mixed(pattern(cells, 40, 3), warp, vortex, 0.6)
lay
texture none
Scheme Colours
jewel deep, rich colours beside the hue, from dark to glowing (the default)
duotone two hues far apart, meeting in a soft neutral
split the hue with the two beside its opposite, such as teal with coral and gold
analogous close hues flowing into each other
pastel light and soft
dusk cool shadows and warm lights
electric near-black to a blazing colour and white, like neon
Finish Surface
matte plain colours (the default)
metallic bright bands and white highlights, like polished metal
iridescent the hue slides along, like oil on water or a beetle's back
pearl pale, with a soft pink-green-blue play of colour
satin a soft band of highlight
Way Mixing
warp the second pattern pushes the first one around
blend the two patterns blended
mask the first pattern shows where the second is bright
carve the second pattern is cut out of the first

14.1 Your own patterns

A creature can bring its own pattern kinds, written in GenoCode. A pattern block names a new kind and draws its grey picture; pattern(…) then uses it like any built-in kind, coloured by a hue, a scheme and a finish:

creature Shell

pattern Ripples {
  gene rings  = 8   in 2..20  // how many rings
  gene wobble = 0.3 in 0..1

  let turn = random(0, 360)
  let lumps = clouds(5, 3)
  value picture {
    let ripple = sin(r * rings * 180 + turn + lumps(x, y) * wobble * 360)
    value ripple * 0.5 + 0.5
  }
}

shape ball
texture pattern(Ripples, 30, 4, dusk, pearl)
lay

Pattern files. The contents of a pattern block, with the header pattern <Name> in place of the braces, form a pattern file. A pattern file and a pattern block convert into each other without any other change. Putting a pattern file into a creature adds its block, or replaces the block of the same name.

pattern Ripples
// Rings that wobble.

gene rings  = 8   in 2..20
gene wobble = 0.3 in 0..1

let turn = random(0, 360)
let lumps = clouds(5, 3)
value picture {
  let ripple = sin(r * rings * 180 + turn + lumps(x, y) * wobble * 360)
  value ripple * 0.5 + 0.5
}

GenoCode's own pattern kinds are written as pattern files too, in the patterns folder, as examples to read and change.

15. Backgrounds

A creature can carry the background it's shown against:

background {
  gene kind  = aurora from [aurora, plasma, marble, cells]
  gene hue   = 260 in 0..360
  gene seed  = 12 in 0..99 step 1
  gene pulse = 30 in 0..90      // how far the colours shift with the bass

  fill pattern(kind, hue + pulse * music.bass, seed)
}

Background files. The contents of a background block, with the header background <Name> in place of the braces, form a background file:

background Northern_Lights

gene kind  = aurora from [aurora, plasma, marble, cells]
gene hue   = 260    in 0..360
gene seed  = 12     in 0..99 step 1
gene pulse = 30     in 0..90

fill pattern(kind, hue + pulse * music.bass, seed)

A background file and a background block convert into each other without any other change. Putting a background file into a creature replaces the creature's background block. Its genes keep their background. full names.

16. Time and music

A creature can react to time and music through these read-only values:

Name Range Meaning
time 0 upwards seconds since the creature started being shown
beat 0 → 1 position within the current beat: 0 on the beat, rising towards 1 just before the next
music.bass 0–1 level of the low frequencies
music.mid 0–1 level of the middle frequencies
music.high 0–1 level of the high frequencies
music.level 0–1 overall loudness
music.hit 0–1 jumps up on each onset (a drum hit, a note starting) and falls back
music.bass_hit 0–1 jumps up on each low onset (a kick drum, a bass note) and falls back
music.mid_hit 0–1 jumps up on each onset in the middle (a snare, a voice) and falls back
music.high_hit 0–1 jumps up on each high onset (a hi-hat, a cymbal) and falls back
gene flap = 30 in 0..70       // how hard the wings beat with the bass
gene sway = 15 in 0..40       // how far the body sways, music or not

yaw sway * wave(time / 4)
symmetric x {
  wing(side)
}

part wing(side) {
  roll side * (10 + flap * music.bass)
  …
}

A creature whose code (outside its background and dance blocks) never reads time, beat or music is static; one that does is live (section 20).

17. Dance styles

The app can move a creature as a whole while music plays. A dance style says how the music drives each of these effects:

Effect What it does
morph moves the creature along its morph (the loop through a set of related creatures): the louder the input, the faster, and it coasts to a stop when the input is quiet
zoom brings the creature towards the viewer as the input rises; it drifts back when the input falls
spin sets how fast the creature spins about its spin axis, between a minimum and a maximum
turn gives the spin axis a new direction when the input changes enough
reverse reverses the direction of the morph when the input drops, in bursts
flat turns the creature face-on and spins it flat, then back to tumbling in 3D, in bursts

A creature can carry its dance style in a dance block:

dance {
  gene drive = 1.1 in 0.3..3  // how fast the music pushes the morph, in creatures a second

  morph   follows music.mid  speed drive  settle 2
  zoom    follows music.level  amount 0.5  settle 15
  spin    follows music.bass_hit  from 45 to 170  smooth 0.4
  turn    on music.mid changes  by 1
  reverse on music.bass drops  by 1  on 4 off 4
  flat    on music.level drops  by 0.86  on 24 off 48
}

Effect lines. Each line is the effect's name, then words, each followed by a value, except changes, drops and rises, which stand alone. The words can come in any order after the first. Values are numbers. Settings left out take the default shown.

Effect Line Settings
morph morph follows x speed 1 settle 2 speed: creatures a second at full input. settle: seconds to coast to a stop from full speed once the input falls; it speeds up ten times faster
zoom zoom follows x amount 0.5 settle 15 amount: how much nearer the creature comes at full input, from 0 (not at all) to 1 (as near as it can). settle: seconds to drift all the way back; it comes in fifty times faster
spin spin follows x from 45 to 170 smooth 0.4 the spin speed in degrees a second, from from (input 0) to to (input 1). The input is read each time the spin axis turns, or all the time if there's no turn line. smooth: seconds to swing smoothly to a new axis and speed; 0 switches at once
turn turn on x changes by 0.5 gives the spin a new, random axis each time the input triggers
reverse reverse on x drops by 0.5 on 4 off 4 reverses the morph each time the input triggers
flat flat on x drops by 0.5 on 24 off 48 while on, the creature turns face-on and spins flat; each trigger reverses the flat spin

x is the effect's input, a number from 0 to 1 (values outside are clamped).

Triggers. turn, reverse and flat act when their input triggers: when it has moved by at least by since the last trigger. After on x, one word says which moves count: changes (either way), drops (only down) or rises (only up); changes is the default. While flat is on, turn only counts drops, whatever its line says.

Bursts. on n off m makes an effect work in bursts: it works for n triggers, then rests for m, and so on, starting at work. For flat, working means being flat, and it starts at rest: the creature tumbles in 3D for m triggers before it first goes flat. Without on … off …, reverse always works, and flat uses on 24 off 48.

18. Library

All angles are in degrees.

18.1 Numbers

Function Result
abs(x) absolute value
sign(x) −1, 0 or 1
floor(x), ceil(x) round down, round up
round(x) nearest whole number, halves rounded up
frac(x) x minus its whole part, keeping the sign: frac(-1.25) is -0.25
min(a, b), max(a, b) smaller, larger
clamp(x, lo, hi) x limited to lo..hi
wrap(x, lo, hi) x wrapped round into lo..hi, so that hi wraps to lo
bounce(x, lo, hi) x folded back and forth between lo and hi
sqrt(x), exp(x), ln(x), log10(x)
sin(a), cos(a), tan(a) a in degrees
asin(x), acos(x), atan(x), atan2(y, x) results in degrees
degrees(r), radians(d) convert radians to degrees and back
wave(t) sin(t × 360): one full wave (0, 1, 0, −1, 0) as t goes from 0 to 1
bump(t) sin(t × 180): rises from 0 to 1 and back to 0 as t goes from 0 to 1
mix(a, b, t) (1 − t)·a + t·b. Works on numbers, vectors and colours (13.1). t isn't clamped for numbers and vectors; for colours it's clamped to 0–1
smoothmix(a, b, t) mix(a, b, u) with u = (1 − cos(180 × t)) / 2, after clamping t to 0–1: eases in and out
mandel(x, y) how quickly the point x + iy escapes under z → z² + c, starting from z = 0: the number of steps before the size of z exceeds 2, up to 64, divided by 64. 1 means it never escaped

Constants: pi (3.14159…), tau (2π), golden (1.61803…), sqrt2, sqrt3, ln2.

18.2 Vectors

Function Result
vec(x, y, z) the same as (x, y, z)
length(v) length (and length(l), a list's number of items)
normalize(v) v scaled to length 1. The zero vector stays zero
dot(a, b), cross(a, b) dot and cross products

Constants: x_axis (1, 0, 0), y_axis (0, 1, 0), z_axis (0, 0, 1).

18.3 Colours

hsv(…), rgb(…), mix(a, b, t), smoothmix(a, b, t), gradient(…) and spectrum(…), plus the colour names in 13.1 and the parts .hue, .sat, .val and .alpha.

18.4 Reading the turtle

Name Type Meaning
turtle.position vector position in world space
turtle.heading vector the heading, as a unit vector in world space
turtle.up vector the up axis, as a unit vector
turtle.side vector the side axis, as a unit vector
turtle.size vector size per axis
turtle.mirrored bool whether the turtle is mirrored
turtle.shape shape current shape
turtle.colour colour current colour
turtle.shine, turtle.sharpness number current material
turtle.wear number how much of its texture a shape wears
turtle.texture pattern current texture

These describe the turtle, not the shape transform.

18.5 Noise and randomness

Function Result
noise(x), noise(v) smooth noise from −1 to 1 that changes gently as x or v changes. It is Perlin's improved gradient noise (2002) with his reference permutation table, scaled to −1..1; noise(x) is noise((x, 0, 0))
fbm(x), fbm(v) fractal noise: five layers of noise, each at twice the frequency and half the strength of the last, scaled to −1..1
random(lo, hi) a random number from lo up to (but not including) hi

noise and fbm always give the same result for the same input.

random is deterministic too. Each build starts a fresh sequence, seeded from the creature's seed gene, and every call to random takes the next number in that sequence. A creature that uses random automatically has the gene seed: a whole number from 0 to 4,294,967,295, starting at 0, which mutation replaces with a new random value rather than nudging, and which doesn't blend when morphing (section 19). The sequence is generated by mulberry32 (a 32-bit generator):

next():  state = (state + 0x6D2B79F5) mod 2^32
         z = state
         z = (z xor (z >>> 15)) × (z or 1)          (32-bit)
         z = z xor (z + (z xor (z >>> 7)) × (z or 61))   (32-bit)
         return (z xor (z >>> 14)) / 2^32            (a number from 0 up to 1)
random(lo, hi) = lo + (hi − lo) × next()

The state starts as the seed gene's value. Use noise for variation that should change smoothly when a creature morphs.

In a pattern (section 14.1), the sequence starts from the recipe's seed instead, afresh for each picture, and random doesn't give the creature a seed gene.

18.6 Lists

Function Result
length(l) how many items the list l has

18.7 Pictures

These make and change pictures. They can only be used in pattern code, outside picture { } (section 14.1). They're built-in names only there: elsewhere, a creature may use stretch, power or blur as names of its own.

Function Result
clouds(), clouds(o, s) fractal noise: o layers (6 if left out) of random values on a grid, the first s × s (4 if left out), each layer twice as fine and half as strong as the last, smoothly enlarged and added up, then stretched to 0..1
blur(p, w), blur(p, w, h) p blurred, w across and h down (w if left out). Widths are in pixels of a picture 512 across, so a blur looks the same at any size
zoom_blur(p, s) p streaked out from the middle: the average of p enlarged from 1 to 1 + s times about the middle
stretch(p) p stretched so its darkest point is 0 and its lightest 1
power(p, e) every value of p raised to the power e

How they work, so that a pattern always draws the same picture:

18.8 Debugging

print(a, b, …) writes its arguments, separated by spaces, to the app's console. It accepts any values, and strings in double quotes.

19. Genetics

This section defines what the app does to genes. The app chooses when to do it, for example when the user presses Mutate.

19.1 Mutation

Mutation has a strength from 0 to 1, chosen by the user, and a gene rate, which is ½ unless the app lets the user change it. Each gene is considered separately: with probability equal to the gene rate it's changed, as follows. Otherwise it's left alone. Locked genes are never changed.

Gene Change
number Let r = strength × (max − min). The new value is uniformly random between max(min, v − r) and min(max, v + r)
stepped number as a number, then rounded to the nearest min + k·step that lies within the range
log number as a number, but on the natural logarithms of the value and bounds, then converted back
vector each axis as a number, using its own range (and log, if declared)
colour saturation, value and alpha as numbers. Hue: the window v ± r is taken round the circle and limited to the range's arc, where r is strength times the arc's length. A whole-circle range wraps round instead of being limited
bool flipped with probability strength
choice, part choice with probability strength, replaced by one of the other options, chosen uniformly
seed with probability strength, replaced by a new uniformly random seed

19.2 Breeding

A child has exactly two parents, A and B. The child's program is A's program, with its genes set as follows. A gene of A's is matched if B has a gene with the same full name and the same kind; unmatched genes keep A's value.

Gene Matched value
number with probability ½, a uniformly random point between A's and B's values; otherwise A's or B's value with equal probability (¼ each overall)
stepped number as a number, then rounded to the nearest step within the range
log number as a number, with the random point taken between the logarithms
vector, colour each axis or channel separately as a number. A random point between two hues is taken along the short way round
bool, choice, part choice, seed A's or B's value, with equal probability

Every result is kept within A's range for that gene.

19.3 Morphing

Morphing makes the in-between creature at position t (from 0 to 1) between A and B. Its program is A's when t < ½ and B's when t ≥ ½. Each gene in that program takes the value below, using the other creature's matched gene (same full name and kind); an unmatched gene keeps its own value.

Gene Value at t
number, stepped number (1 − t)·A + t·B. Stepped numbers are not rounded, so morphs are smooth
log number A × (B / A)^t
vector, colour each axis or channel separately. Hue goes the short way round
bool, choice, part choice, seed A's value when t < ½, B's when t ≥ ½

Values aren't limited to either creature's range while morphing.

A morph through several creatures (a loop) is a chain of these morphs between neighbours. When A and B share a program, a morph is smooth all the way; when their programs differ, the creature changes shape at the halfway point. A creature with variants (6.6) has a loop of its own: the creature itself, then each variant in the order they're written.

19.4 Saving

A creature that has been mutated, bred or morphed is saved as an ordinary creature file: its program, with each gene's new value written into the gene's line. Nothing else in the program changes, except that the file is laid out by the formatter, and a gene whose value hasn't changed keeps the text it was written with. Variants are kept as they are. The new values are written readably: stepped numbers exactly, other numbers to four significant figures, colours as hsv(…), and choices by name, always within the gene's range. The automatic seed gene has no line of its own, so it isn't saved yet: a saved creature that uses random starts again from seed 0.

20. Building a creature

  1. Checking. The file is read and checked: syntax, names, gene rules, function rules, and types wherever they can be known without running the program. Problems are reported with a line and column. Errors stop the build; warnings don't. Besides the warnings given elsewhere, the checker warns about two things that build but rarely mean what they say:
    • a shape with nothing laid before the shape is set again, or before the end of the creature, a branch or a symmetric body. It usually belongs before the lay it was meant for.
    • a part call among the creature's top-level statements, followed by more of them that may lay shapes, when the part moves, turns, grows or mirrors the turtle outside a branch. Everything after the call starts where the part ended; a branch around the call stops that.
  2. Building. The top-level statements run once, from the top, with the creature's gene values and the current time and music inputs. Every lay adds one shape. The result is a list of shapes, each with its placement, shape, colour, shine, sharpness, texture and wear.
  3. Showing. The app draws the shapes, with the background and dance effects.
    • A static creature (one that doesn't read time, beat or music) is built once for each set of gene values. Morph frames can be built in advance.
    • A live creature is rebuilt every frame with the current inputs, and while morphing, with the in-between genes for that frame.

Limits. A build stops with a warning, keeping the shapes laid so far, if it lays more than 50,000 shapes, runs more than 10,000,000 steps (statements and expression evaluations), or nests part and function calls more than 200 deep.

Numbers that go wrong. Arithmetic is ordinary 64-bit floating point: 1 / 0 is infinity and sqrt(-1) is not a number. A shape whose placement isn't finite is left out, with a warning, and the rest of the creature is still built. In-between creatures in a morph sometimes do this.

21. Blocks

The block editor shows the same program as the text. Both are built from one syntax tree: text is parsed into the tree and shown as blocks, and blocks are turned back into the tree and printed as text by the standard formatter.

Construct Block
creature Name and the top-level statements the creature's main block, with the statements stacked below it
part, function a "define" block with the parameters. Parts then appear as stacking blocks in the palette, and functions as value blocks
gene a gene block with a slider (numbers), colour pickers (colours), range handles, a switch (bools) or a dropdown (choices) and a description
background { } a background block holding its genes and one fill block, with a live preview and an export button
dance { } a dance block holding its genes and one block per effect, with an export button
let, assignment, turtle commands, shape, colour, shine, sharpness, texture, wear, lay, part calls, return, print stacking blocks with slots for values. Labels can be friendlier than the keywords, such as "turn left/right" for yaw
if, repeat, for, while, branch, symmetric, lay { }, reshape { } blocks that wrap around other blocks
expressions value blocks that plug into slots: round for numbers, pointed for bools, swatches for colours

Keeping the two in step:

22. Grammar

EBNF. { x } means zero or more, [ x ] means optional. NL is the end of a statement: a line end (section 4) or ;.

file          = creature_file | background_file | dance_file | pattern_file ;
creature_file = "creature" NAME NL { item } ;
background_file = "background" NAME NL bg_body ;
dance_file    = "dance" NAME NL dance_body ;
pattern_file  = "pattern" NAME NL pattern_body ;

item          = gene | part | function | statement
              | "background" "{" bg_body "}"
              | "dance" "{" dance_body "}"
              | "pattern" NAME "{" pattern_body "}"
              | "variant" NAME "{" { gene_ref "=" expr NL } "}" ;
gene_ref      = ( NAME | "background" | "dance" ) { "." NAME } ;

gene          = "gene" NAME "=" expr [ gene_range ] NL ;
gene_range    = "in" expr ".." expr [ "step" expr | "log" ]
              | "from" "[" expr { "," expr } "]" ;

part          = "part" NAME [ "(" [ params ] ")" ] body ;
function      = "function" NAME "(" [ params ] ")" body ;
params        = NAME { "," NAME } ;
body          = "{" { gene | statement } "}" ;
block         = "{" { statement } "}" ;

statement     = ( simple | compound ) ;
simple        = "let" NAME "=" expr NL
              | NAME assign_op expr NL
              | NAME [ "(" [ args ] ")" ] NL
              | "return" [ expr ] NL
              | "print" "(" [ args ] ")" NL
              | "lay" NL
              | turtle_cmd NL
              | material_cmd NL ;
compound      = "if" expr block { "else" "if" expr block } [ "else" block ]
              | "repeat" expr [ "as" NAME ] block
              | "for" NAME "from" expr "to" expr [ "by" expr ] block
              | "while" expr block
              | "branch" block
              | "symmetric" axis block
              | "lay" block
              | "reshape" block ;
assign_op     = "=" | "+=" | "-=" | "*=" | "/=" ;

turtle_cmd    = ( "forward" | "back" | "move" | "pitch" | "yaw" | "roll" | "grow" ) expr
              | "rotate" expr "around" expr
              | "mirror" axis ;
material_cmd  = ( "shape" | "colour" | "color" | "shine" | "sharpness" | "texture" | "wear" ) expr ;
axis          = "x" | "y" | "z" ;

bg_body       = { gene | bg_stmt } "fill" expr NL ;
bg_stmt       = "let" NAME "=" expr NL
              | NAME assign_op expr NL
              | "if" expr bg_block { "else" "if" expr bg_block } [ "else" bg_block ] ;
bg_block      = "{" { bg_stmt } "}" ;

dance_body    = { gene | "let" NAME "=" expr NL | effect NL } ;
effect        = effect_name { setting } ;
setting       = "changes" | "drops" | "rises" | word expr ;
effect_name   = "morph" | "zoom" | "spin" | "turn" | "reverse" | "flat" ;
word          = NAME | "from" | "to" | "by" ;

pattern_body  = { gene | pattern_stmt } "value" expr NL ;
pattern_stmt  = "let" NAME "=" expr NL
              | NAME assign_op expr NL
              | "if" expr pattern_block { "else" "if" expr pattern_block } [ "else" pattern_block ]
              | "repeat" expr [ "as" NAME ] pattern_block
              | "for" NAME "from" expr "to" expr [ "by" expr ] pattern_block
              | "while" expr pattern_block ;
pattern_block = "{" { pattern_stmt } "}" ;
picture_body  = { pattern_stmt } "value" expr NL ;

expr          = or_expr ;
or_expr       = and_expr { "or" and_expr } ;
and_expr      = not_expr { "and" not_expr } ;
not_expr      = "not" not_expr | compare ;
compare       = sum [ ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) sum ] ;
sum           = product { ( "+" | "-" ) product } ;
product       = unary { ( "*" | "/" | "%" ) unary } ;
unary         = "-" unary | power ;
power         = postfix [ "^" unary ] ;
postfix       = primary { "." NAME | "(" [ args ] ")" | "[" expr "]" } ;
primary       = NUMBER | HEX_COLOUR | STRING | "true" | "false"
              | NAME
              | "turtle" "." NAME | "time" | "beat" | "music" "." NAME
              | "(" expr ")"
              | "(" expr "," expr "," expr ")"
              | ( "gradient" | "spectrum" ) "(" stop "," stop { "," stop } ")"
              | "[" [ args ] "]"
              | "picture" "{" picture_body "}" ;
stop          = expr [ "at" expr ] ;
args          = expr { "," expr } ;

Rules the grammar doesn't express are given in the sections above: where genes may go (6.2), what may appear inside lay { } and reshape { } (11.7), what functions may do (section 10), constant expressions (6.1), where strings are allowed (section 4), and what pattern code and picture { } may use (section 14.1). "picture" and "value" are only words in pattern code.

23. Not yet settled