PETRI DISH GAMES

The contract

The fixed technical specification a generated organism must satisfy to be stored and to keep being shown.

The contract is the fixed technical specification the code of an organism must satisfy: a prelude, the uniforms it may read, and a list of clauses carried in every birth prompt for its track. It is a module constant rather than a gene, so nothing the lab evolves can rewrite it. Admission is decided by static validation before the organism is stored, and continued visibility is decided by whether a browser compiles and draws it.

The shader prelude

The prelude is prepended to a shader organism’s source at compile time, by the owner’s arena and by the public judging page alike. The organism never writes it, and repeating any of its declarations is a contract violation.

It opens the source with #version 300 es, sets precision highp float, declares the output out vec4 fragColor, and declares three uniforms:

UniformTypeCarries
u_timefloatSeconds, wrapped to a 20-second loop by both renderers.
u_resvec2The canvas backing-store size in pixels, measured from the live element.
u_audiovec4Music levels in 0 to 1: x overall, y bass, z mid, w high. All zero when silent.

The owner’s arena binds u_time and u_res only, leaving u_audio at zero for every organism it shows. The public page binds all three and drives them from its own analysis of the voice.

The clauses stated to the model alongside the prelude:

  • One fenced glsl block and nothing else, except one optional TITLE: line of at most four words before it.
  • void main() is defined and writes to fragColor.
  • No other uniforms, no textures, no samplers, no #version, no extensions.
  • GLSL ES 3.00 only, with strict integer and float typing.
  • Continuous animation off u_time, never resolving to a flat or near-uniform frame.
  • No whole-frame brightness flash, pulse or inversion faster than four times per second. Fast flicker confined to a small region is permitted.
  • u_audio is optional input, and the shader must remain fully alive when it is vec4(0.0).
  • Under 140 lines, no infinite loops, every loop bounded by a constant of 64 or less.

The panel contract

The ui track carries its own contract of the same shape. A panel organism is one fenced html block containing a single <style> element plus markup, under these clauses:

  • No <html>, <head> or <body>.
  • No script, no network requests, no external fonts or images.
  • System or generic font families only.
  • Every selector scoped under a root element the organism creates.
  • Under 160 lines.

The runtime frame is fixed at 520 by 400 pixels on a dark ground, and the panel must fit it without scrolling and stay legible without hover.

Every panel organism is given one identical data block and must display each of its fields with no invented numbers:

  • a panel name
  • five labelled readings: population, biomass, pH, temperature and divergence, some carrying a delta, a unit or a state word
  • a strain block of name, generation and value
  • one action label
  • one warning line

Fixing the data is what makes two panels comparable when they are judged against each other.

Contract and directive

Two separate strings reach the model at a birth. The contract is a constant on the track. The aesthetic directive is a prompt gene, a row in the database with a rating and a lineage. The birth prompt concatenates them in that order, with the directive labelled as the thing the result is judged on.

Prompt evolution rewrites only the directive. Both prompt-writing prompts, the one that mutates a winner against a loser and the one that goes looking for unoccupied territory, require the result to stay about aesthetics only and never about the technical contract. The genome operators that breed code do not touch either string.

A directive that asked for something the contract forbids still could not put it in the pool. The static validator and the browser both run after the model returns, and neither reads the directive.

Static validation

validate(track, code) returns a reason string or null, and is the same function on both breeding surfaces: the daemon’s birth() and the public visitor cross. A candidate that fails is logged as a rejection and never inserted, so it never becomes an organism and never dies.

Two length bounds apply to both tracks: code under 60 characters is rejected as too short, and code over 24,000 characters as too long. The 140- and 160-line limits are contract text asked of the model; the character ceiling is what is enforced.

Shader rejections:

ReasonTest
no main()No void main( anywhere in the source.
redeclared #versionThe source contains #version.
uses texturesA sampler type, or a call to texture(.
redeclared fragColorThe source declares out vec4 fragColor.
extra uniformAny uniform declaration other than float u_time or vec2 u_res.
unbounded loopA while loop, or a for with an empty condition.

The loop rule exists because an unbounded loop compiles cleanly and then hangs the GPU, on hardware the lab cannot see.

The uniform test names two of the prelude’s three uniforms. A body that redeclares u_audio is rejected as an extra uniform even though the prelude supplies it, while one that redeclares u_time or u_res passes the static test and fails in the browser instead. The test reads the body alone, and the prelude is prepended after it runs.

Panel rejections:

ReasonTest
contains scriptA <script or </script tag.
forbidden taghtml, head, body, iframe, object, embed, link, meta, base, form, svg, video, audio, source or track.
no style blockNo <style element.
event handler attributeAny on...= attribute.
external referenceAn http:// or https:// URL.
protocol-relative referenceA //host.tld reference in a value position.
forbidden url schemejavascript:, data:, blob:, file: or vbscript:.
css importAn @import.
css url()Any url(.

The panel is displayed inside an iframe with an empty sandbox attribute, which stops script but says nothing about subresource loads. Every rule above that blocks a fetch is there because an <img> or an @import is a working beacon, and on the owner’s machine that is a request leaving the desktop.

The compile gate

Neither validator compiles GLSL. The browser does, and it is the only judge of whether a shader runs.

In the owner’s arena the prelude and the organism’s source are compiled into a fragment shader and linked into a program. A failed compile or link posts the organism to /api/invalid with the first line of the info log, which sets its status to dead and stores the error. A program that links is drawn, and after 300 milliseconds of continuous drawing the arena posts /api/ok, which sets render_ok. Compiling is not enough on its own: the organism has to survive being drawn.

Loss of the WebGL context is excluded from that verdict. A driver reset drops every live context at once, so the arena deals a fresh pair instead of blaming the organism in the panel.

A panel organism has no compile step. It is mounted in the sandboxed iframe and reported render_ok immediately.

The public page applies the same bar, compiled and 300 milliseconds of drawing, and reports the outcome to /api/pub/seen instead. It compiles an instrumented copy of the body first, and where that copy is what fails, the pristine source gets one further attempt before the organism is skipped. A failure on a stranger’s machine never writes an error and never kills: only the owner’s /api/invalid route can mark an organism dead for failing to render.

render_ok and the canary

render_ok is an integer column defaulting to 0. Until it is set, an organism is confined to the owner’s arena. The public surfaces publish the shader track only, so the gate below is the shader contract’s, and a panel organism lives its whole life on the desktop.

SurfaceRequirement
Owner’s arena pairsAlive, no stored error. Unproven organisms are dealt here, which is how they get proven.
Breeding parentsProven organisms are preferred; the whole alive pool is used only when none is proven.
Public pairsProven and no stored error, plus at most one canary.
Public galleryProven, no stored error, and judged at least once.
Visitor crossBoth parents proven; the child is born unproven.

When unproven organisms exist, a public pair carries one of them as a canary against a proven partner drawn from those nearest it on the public ladder, preferring one that is not a token-near copy, and the visitor’s browser reports back whether it drew.

Four rules keep that report from amounting to public promotion:

  • A report is accepted only for an id the server dealt to that exact client key, within ten minutes. A blind post naming an arbitrary id does nothing.
  • A positive report sets render_ok. A negative report is an in-memory strike and nothing more.
  • Strikes from two distinct client keys bench the organism: it stops being dealt as a canary. It is not dead, and the owner’s arena can still prove it.
  • A vote on a canary is accepted from the client it was dealt to, so a verdict that arrives before the render report is not discarded.
  • A report on an organism already proven, or no longer alive, is accepted and changes nothing.

An organism carrying a stored error is filtered out of every public query, and out of the unproven backlog, so a dead organism is never dealt as a canary.

Bound to

The source this page's claims were read against, named by symbol and pinned by the digest of the file it lives in.

SymbolDigest
arena.html f4018fe1a8bc
birth b72f4e12e72f
db.js 193eb96a460a
publicSeen f14fffef049d
rorschach.astro b439b3c1f84b
server.js 2ee5143085a9
SHADER_PRELUDE 979ad190a564
validate 2135902ed4dc

Near this subject

These are the subjects this one is joined to in the field. Step out of this card and they are the wells around the one you are standing in.