ddonche/goblin-lang
0.46.24
1
0
docs reference
[[object-matrices]]

Goblin Object Matrices


Object matrices are Goblin's bulk object-construction syntax for simulations, games, balancing systems, RPG stat blocks, procedural content, and large sets of related structured data.

Instead of repeatedly building related objects one at a time:

usa | Entity("USA", 100, 60, 50, 70)
france | Entity("France", 70, 40, 70, 50)
russia | Entity("Russia", 90, 65, 50, 80)
usa | Entity("USA", 100, 60, 50, 70)
france | Entity("France", 70, 40, 70, 50)
russia | Entity("Russia", 90, 65, 50, 80)

Goblin lets you define the same objects side-by-side in a readable matrix:

<>Entity matrix
    id: "{id}", usa, france, russia
    power: 100, nc, 70, 90
    aggression: 50, 60, 40, 65
    cooperation: 50, nc, 70, nc
    opportunism: 50, 70, nc, 80
end
<>Entity matrix
    id: "{id}", usa, france, russia
    power: 100, nc, 70, 90
    aggression: 50, 60, 40, 65
    cooperation: 50, nc, 70, nc
    opportunism: 50, 70, nc, 80
end

This keeps fields vertical, objects horizontal, defaults centralized, and comparisons easy.

Object matrices complement normal object construction. They do not replace it.


Basic Syntax

A matrix starts with a class name and the matrix keyword:

<>Entity matrix
    id: "{id}", usa, france, russia
    power: 100, nc, 70, 90
    aggression: 50, 60, 40, 65
    cooperation: 50, nc, 70, nc
    opportunism: 50, 70, nc, 80
    resilience: 50, 80, 65, 75
    influence: 50, 90, 70, 85
    stability: 50, 65, 80, 55
    alive: true, nc, nc, nc
end
<>Entity matrix
    id: "{id}", usa, france, russia
    power: 100, nc, 70, 90
    aggression: 50, 60, 40, 65
    cooperation: 50, nc, 70, nc
    opportunism: 50, 70, nc, 80
    resilience: 50, 80, 65, 75
    influence: 50, 90, 70, 85
    stability: 50, 65, 80, 55
    alive: true, nc, nc, nc
end

The matrix is self-contained. It defines the schema, sets defaults, and creates all instances in one block.

If Entity already exists, the matrix uses that class. If it does not exist yet, Goblin infers the class schema from the matrix rows.


Matrix Rows

Each row follows this structure:

field_name: default_value, object1, object2, object3
field_name: default_value, object1, object2, object3

The first value after : is the default.

Each remaining value corresponds to an object column.

aggression: 50, 60, 40, 65
aggression: 50, 60, 40, 65

This means:

usa >> aggression    /// 60
france >> aggression /// 40
russia >> aggression /// 65
usa >> aggression    /// 60
france >> aggression /// 40
russia >> aggression /// 65


No Change Values

Use nc or :: when an object should inherit the row default.

cooperation: 50, nc, 70, nc
cooperation: 50, nc, 70, nc

This means:

usa >> cooperation    /// 50
france >> cooperation /// 70
russia >> cooperation /// 50
usa >> cooperation    /// 50
france >> cooperation /// 70
russia >> cooperation /// 50

nc reads as "no change." It says: use the default for this field.


The ID Row

The id row is special.

id: "{id}", usa, france, russia
id: "{id}", usa, france, russia

The column names determine the objects created by the matrix:

usa
france
russia
usa
france
russia

The "{id}" placeholder means: use each column name as the value for that object's id field.

So usa receives:

usa >> id /// "usa"
usa >> id /// "usa"

and france receives:

france >> id /// "france"
france >> id /// "france"


Equivalent Expansion

This matrix:

<>Entity matrix
    id: "{id}", usa, france
    power: 100, nc, 70
    aggression: 50, 60, 40
end
<>Entity matrix
    id: "{id}", usa, france
    power: 100, nc, 70
    aggression: 50, 60, 40
end

is equivalent to defining the class shape and constructing the objects separately:

<>Entity
    id: ""
    power: 100
    aggression: 50
end

usa | Entity
    id: "usa"
    power: 100
    aggression: 60
end

france | Entity
    id: "france"
    power: 70
    aggression: 40
end
<>Entity
    id: ""
    power: 100
    aggression: 50
end

usa | Entity
    id: "usa"
    power: 100
    aggression: 60
end

france | Entity
    id: "france"
    power: 70
    aggression: 40
end

The matrix simply does both jobs in one readable block.


Expressions in Matrices

Matrix cells can contain pure expressions.

power: 100, 70 + 5, nc, 90 * 1.1
power: 100, 70 + 5, nc, 90 * 1.1

This turns matrices into balancing tools rather than static data dumps.


Field References

A matrix cell can reference fields declared above it in the same object column.

<>Character matrix
    id: "{id}", warrior, rogue, wizard
    strength: 10, 18, 12, 8
    dexterity: 10, 8, 18, 12
    threat: 0, strength + dexterity, strength + dexterity, strength + dexterity
end
<>Character matrix
    id: "{id}", warrior, rogue, wizard
    strength: 10, 18, 12, 8
    dexterity: 10, 8, 18, 12
    threat: 0, strength + dexterity, strength + dexterity, strength + dexterity
end

Results:

warrior >> threat /// 26
rogue >> threat   /// 30
wizard >> threat  /// 20
warrior >> threat /// 26
rogue >> threat   /// 30
wizard >> threat  /// 20

Field references only look upward. A field can reference fields declared before it, not fields declared later.


Default References

Use default to reference the row's default value.

power: 100, default + 10, 70, default - 5
power: 100, default + 10, 70, default - 5

This produces:

object1 >> power /// 110
object2 >> power /// 70
object3 >> power /// 95
object1 >> power /// 110
object2 >> power /// 70
object3 >> power /// 95


Dice Notation

Object matrices support Goblin's native dice notation.

This makes matrices useful for RPG systems, enemy generation, procedural stats, loot tables, and simulations.

<>Character matrix
    id: "{id}", rogue, wizard, barbarian
    strength: 10, 1d8+2, 1d4+1, 2d8
    dexterity: 10, 2d10, 1d6, 1d8
    wisdom: 10, 1d20+1, 2d10+2, 1d6
end
<>Character matrix
    id: "{id}", rogue, wizard, barbarian
    strength: 10, 1d8+2, 1d4+1, 2d8
    dexterity: 10, 2d10, 1d6, 1d8
    wisdom: 10, 1d20+1, 2d10+2, 1d6
end

Dice expressions in ordinary stat fields are evaluated once when the object is created.

Example results:

rogue >> strength /// 7
wizard >> wisdom  /// 16
rogue >> strength /// 7
wizard >> wisdom  /// 16


Persistent Roll Expressions

Some systems need the dice expression itself to stay dynamic.

For example, a weapon's damage roll should usually be rolled later during gameplay, not once when the weapon is created.

<>Weapon matrix
    id: "{id}", longsword, dagger, warhammer
    attack_roll: 1d4, 1d8+2, 1d4+1, 2d6+3
end
<>Weapon matrix
    id: "{id}", longsword, dagger, warhammer
    attack_roll: 1d4, 1d8+2, 1d4+1, 2d6+3
end

The expression can be stored and rolled later:

damage | hero >> weapon >> attack_roll >> roll
damage | hero >> weapon >> attack_roll >> roll


Matrix Evaluation Rules

Allowed inside matrix cells:

  • literals
  • nc / ::
  • pure expressions
  • upward field references
  • dice notation

Not allowed:

  • side effects
  • arbitrary actions
  • mutation logic
  • execution blocks

power: 100, default + 10       /// valid
threat: 0, power + aggression  /// valid
strength: 10, 1d8+2            /// valid

power: 100, attack(enemy)      /// invalid
name: "{id}", save_file()      /// invalid
power: 100, default + 10       /// valid
threat: 0, power + aggression  /// valid
strength: 10, 1d8+2            /// valid

power: 100, attack(enemy)      /// invalid
name: "{id}", save_file()      /// invalid

Matrices are for structured object construction, not arbitrary execution.


Simulation Use

In simulations, matrices define seed state.

<>Entity matrix
    id: "{id}", kingdom_a, kingdom_b, kingdom_c
    aggression: 50, 70, 40, 80
    cooperation: 50, 20, 90, 10
    stability: 50, 40, 80, 30
    famine_risk: 10, 20, 50, 70
end
<>Entity matrix
    id: "{id}", kingdom_a, kingdom_b, kingdom_c
    aggression: 50, 70, 40, 80
    cooperation: 50, 20, 90, 10
    stability: 50, 40, 80, 30
    famine_risk: 10, 20, 50, 70
end

Simulation logic can then mutate runtime copies over time:

if famine_risk > stability
    unrest | unrest + 20
end
if famine_risk > stability
    unrest | unrest + 20
end

A simulation log might later show:

Round 12:
russia power:     90 -> 96
france stability: 80 -> 63
usa influence:    90 -> 97
Round 12:
russia power:     90 -> 96
france stability: 80 -> 63
usa influence:    90 -> 97

The matrix is the starting board, not permanent truth.


Intended Use Cases

Object matrices are ideal for:

  • simulation entities and factions
  • RPG stats, enemies, and NPC archetypes
  • weapons, armor, and loot tables
  • balancing systems
  • procedural seeds
  • test fixtures
  • configuration profiles

Why Object Matrices Exist

Positional constructors become unreadable after enough fields.

usa | Entity("usa", 100, 60, 50, 70, 80, 90, 65)
usa | Entity("usa", 100, 60, 50, 70, 80, 90, 65)

After enough values, you forget what each position means.

Object matrices keep the field names visible and the objects comparable.

Most languages force this kind of data into giant JSON blobs, unreadable constructors, ECS boilerplate, or spreadsheets disconnected from code. Goblin keeps structure, tuning, generation, and simulation inside one coherent syntax.


Design Philosophy

Object matrices are designed around readability, balancing clarity, and side-by-side comparison.

They are especially suited for emergent simulations, RPG systems, dynamic world modeling, and systemic game design.

Rather than treating data as disconnected objects, matrices let you think in terms of systems, relationships, balance, and evolving state.


Tip
Object matrices are designed for readability first.

If you can visually compare objects side-by-side, balancing and systemic tuning become dramatically easier.