HIERONYMUS
← Documentation

Writing Domain Encoders

A domain encoder is the only component that changes between application domains. It converts raw data into partition coordinates that the GPU pipeline can process.

Encoder Architecture

Every encoder has two parts that work together: a CPU preprocessor written in TypeScript and a GPU shader written in GLSL. The CPU part normalises and loads the data, the GPU part does the per-pixel encoding at massively parallel throughput.

CPU Preprocessor

  • • Load and validate input data
  • • Normalise to [0, 1] range
  • • Convert to RGBA pixel array
  • • Upload as WebGL texture

GPU Shader (GLSL)

  • • Read texel at current UV coordinate
  • • Compute local statistics (mean, variance)
  • • Derive partition features
  • • Output encoded vec4 for next pass

The S-Entropy Contract

Sk + St + Se = 1

Every encoder must produce outputs that satisfy this conservation law.

The three entropy components partition the total information content of each pixel:

  • S_k (kinetic) — gradient magnitude, translational disorder. How rapidly the signal changes in the local neighbourhood.
  • S_t (thermal) — local variance, configurational disorder. How much the signal varies within a local window.
  • S_e (emission) — ternary state probability from the oxygen model. The fraction of information encoded in the invisible modality.

The encoder does not need to compute S_k, S_t, S_e directly — that happens in the entropy pass (pass 4). But the encoder must output features from which these values can be derived, and the resulting triple must sum to unity.

Example: Microscopy Encoder

The built-in microscopy encoder (encode_microscopy.glsl) demonstrates the full pattern. It computes luminance, local statistics, and Shannon entropy from a 3x3 neighbourhood.

encode_microscopy.glsl — key functions

#version 300 es
precision highp float;
uniform sampler2D u_image;
uniform float u_nmax;
in vec2 v_uv;
out vec4 fragColor;

float luminance(vec3 c) {
  return dot(c, vec3(0.2126, 0.7152, 0.0722));
}

// Sample 3x3 neighbourhood for local statistics
void neighbourhood(sampler2D tex, vec2 uv, vec2 texelSize,
                   out float mean, out float variance) {
  float sum = 0.0, sum2 = 0.0;
  for (int dy = -1; dy <= 1; dy++) {
    for (int dx = -1; dx <= 1; dx++) {
      float v = luminance(
        texture(tex, uv + vec2(float(dx), float(dy)) * texelSize).rgb
      );
      sum  += v;
      sum2 += v * v;
    }
  }
  mean     = sum / 9.0;
  variance = sum2 / 9.0 - mean * mean;
}

// Shannon entropy from local 3x3 histogram (4 bins)
float localEntropy(sampler2D tex, vec2 uv, vec2 ts) {
  // ... bin pixel values, compute -sum(p * log2(p))
}

The encoder outputs a vec4 containing:

  • R: quantised principal level n (partition depth)
  • G: angular level l from local variance
  • B: magnetic number m from gradient direction
  • A: spin s from Shannon entropy

Writing Your Own Encoder

To add a new domain (e.g. spectroscopy, chromatography, genomics), you need to:

  1. 1. Create a GLSL file encode_<domain>.glsl in src/shaders/. It must accept u_image (sampler2D) and u_nmax (float), and output a vec4 of partition features.
  2. 2. Export the shader from src/shaders/index.ts.
  3. 3. Import the shader in ObservationEngine.ts and add it to the shader compilation map.
  4. 4. Write a CPU preprocessor function that converts your domain data into an RGBA pixel array (Uint8Array or Float32Array) suitable for texture upload.
  5. 5. Pass the encoder name to observe() to select it at runtime.

The rest of the pipeline — partition, interference, entropy, and display passes — is universal and does not change between domains. Only the encoder changes.