Frogtoss Labs - title image with flowers
*

Grain and Value Specialization

Thu 30 April 2026
Michael Labbe
#grain code 

A designer who worked on a AAA open-world game once remarked that he spent weeks implementing a sweeping behaviour change across every traffic light in the world. A design change made it necessary to re-code every traffic intersection in the game to support right-on-red traffic.

When he proposed that the next game be built with common traffic light logic that applied to all lights, the counterargument was: “if it breaks in one place, it’ll break everywhere”. His response: “If it breaks everywhere, we’ll know exactly where to go to fix it.”

Practising developers find that the truth lies somewhere in between, and it depends on your project. Your tools must empower you to be productive wherever on that spectrum you find yourself.

Grain DDL 0.0.2 (pre-alpha test) now offers a technique called value specialization. This allows you to declare a type that has the exact same fields, in the same order as a base struct, but with the ability to explicitly override the values. No new values can be added.

struct A {  i32 a = 10,  i32 b = 20}struct B copies A {  // a is copied from A.a  i32 b = 30}

This produces the following values:

A.a = 10A.b = 20B.a = 10B.b = 30 

This is “value specialization for PODs” — a flat memory model where only plain old data is subject to being overridden.

Grain DDL is designed to let you generate anything you want from its source data and types. But it keeps you in check about what you can define to keep you generating simple, effective code. In order to use value specialization to its maximum advantage, only the values can change, and not the types. Consider:

struct C copies A {  // error: A.c does not exist and new fields not possible  i32 c = 10,  // error: A.b exists, but it is i32, not f32  f32 b = 10,}

In the case of C.b, you might be asking “why are you asking the user to restate the type in the derived struct, if it has to match? “

When creating just a few instances by copies, this will seem like an unnecessary burden. However, once you have quite a few, a sense of base type calcification naturally occurs: if I change the type of this field in the base, how many downstream things could break?

Grain DDL has implicit casting of expressions resulting in scalar values — albeit with full checks for overflow and underflow at compile time. However, in the event that a type changes in a copied struct, it becomes important to comb over the code and ensure that all of the values are also correctly and adequately typed. This requires a touchpoint from a developer. No silent truncation is possible.

The compiler catches the type mismatch until you visit the line. By changing the type, you are signing off that the declared value is compatible with the type change.

Late-Stage Binding

One of the timeless functions of spreadsheets is relative referencing. If you have a formula like =A1+B1 and drag the selection box downwards, the next field will be =A2+B2. This is used to great effect in calculating the same formula for a different set of values.

Grain DDL uses late-stage binding. When you have a base struct with a formula that has field names in it and it is derived via copies, the formula is calculated in the context of the values in the derived struct.

Consider how sum behaves on override of b:

struct A {  i32 a = 10,  i32 b = 20,  i32 sum = a + b}struct B copies A {    i32 b = 30,  // instead of 20  // sum calculated here with 'b' overridden to 30}

This produces the following values:

A.a = 10A.b = 20A.sum = 30    <- sum of A.a + A.bB.a = 10B.b = 30B.sum = 40    <- sum of B.a + B.b

This is a powerful feature that permits you to model relationships between values in one place, and have that cascade. It is designed to run as a precompile step, so the result of your computation is simply placed in your code and values are easily reviewable.

Said another way, the values are computed at emit time, and can be reviewed as plain numbers.

Putting it Together With Codegen Templates

Grain DDL lets you generate anything you need by specifying a .cgtmpl file at compile time.

When iterating over structs, .copy_name gets the name of the struct:

[_ range .structs  if .copy_name    .name ` copies ` .copy_name ``.  else    .name ` does not copy`.  endend_]

Produces

A does not copyB copies A

Live Sample Code

See example “Copies Keyword” in the Grain IDE for a demonstration of how weapons in a game use value specialization to create one type, calculate DPS from a set of factors, and produce simple C definitions that reduce the complexity of a game runtime.

On Traffic Lights and Best Practices

I believe my colleague’s desire to have all of the traffic lights in an AAA open world operate from the same common logic was directionally correct. However, if you are building a hundred intersections in an open world, I would recommend using a system like Grain DDL’s value specialization to define them, keeping in mind:

  • Code-Generate a single base structure — all traffic lights are one fat struct, where the difference is in the values rather than through tagged discrimination.

  • Use Grain DDL’s late-stage bindings to formulate values and then generate compile-time asserts to ensure they remain in acceptable ranges to catch problems at compile time rather than QA time.

  • Consider that some traffic intersections may be so different they should be special-case coded and not part of the system.

  • Use Grain DDL’s type checking system to make incremental, verified structural alterations as needed.

Help Develop Grain

Grain is currently in closed pre-alpha testing. If you would like to try it or share feedback, email grain _at_ frogtoss dot com.