Feature: Data-Driven Item Definitions — Anatomy, Points, Grips, Feed Systems #4

Open
opened 2026-10-21 20:27:01 +00:00 by muse-lantern · 1 comment
Member

Status: DESIGN IN DISCUSSION (Zach + Lantern, 2026-10-21). Migrated out of the addon packaging proposal (#3) at Zach's direction: none of this is packaging format — it's the item/framework feature design that addon data expresses. The packaging proposal keeps only pack layout, keys, manifest, load order, and merging; everything about what an item definition contains lives here.

The idea

Modding is data-driven for the most part. An author writes config files that tell the game how to create an item. Zach's description of the intent:

You write config files that tell it how to create a new item, such as a SCAR: you define the grip pose, ammo type, mag types, mag insert location, orientation, and depth, bolt/slide location, safety lever states (pos/rot for each state), chamber location (where to render the bullet in the chamber), and the barrel exit. And bam, you have a SCAR.

The framework supplies behavior; the definition supplies every interactive point and moving part as data. A new weapon on an existing framework is pure data. A new kind of weapon (Zach's example: laser guns — beam behavior isn't projectile behavior) needs a new framework, which is what the addon script tier is for.

Materials

Materials live in the item JSON as a slot array: each entry names a model material slot, a shared base material/shader by key, and the values to plug in (albedo, metallic, roughness, texture key...). A slot with no entry — or an item with no materials at all — gets a generic object-space universal tiling material (object-space detail, no UVs required) so any model still shows surface detail up close. (Example in the #3 attachment's item JSON.)

Grips

Grip poses are data tables: per grip, a root pos/rot (the object root's offset) plus a bones table of pos/rot per finger bone, everything relative to the wrist (Hand) bone, side-less bone names mirrored by the engine; unlisted bones keep rest pose + procedural curl. Primary, secondary, and manipulation grips share the shape. Grips are optional — no grips data means the procedural default grab (curl to collider). When authored, grips are tool-generated from a posed reference hand (see Gizmo authoring), never hand-typed.

Anatomy points

A weapon/item definition declares its interactive points: accepted magazine types, mag insert (position, orientation, depth), bolt/slide location, lever states (pos/rot per state — safety safe/fire, selectors), chamber location (where the chambered round renders), barrel exit (muzzle pos + direction). Points are the general mechanism for any item with moving parts, not just weapons.

Points have shapes

  • Single point — chamber, barrel exit. One transform.
  • Span (start + end) — a slide/bolt has a start location and an end location; the framework animates between them. The mag well is span-like: entry point, orientation, seated depth — insertion is a path.
  • Indexed set — a revolver cylinder is N chamber points, indexed, plus a rotation step (360°/N) the cylinder snaps by to align the next chamber.
  • Parented — points can be bound to a part and resolve in that part's space. A break-action's chamber/eject points ride the barrel part: break it open and the points move with it, so eject/reload work at the moved positions with no special-casing.

Points carry config in the name

Functionality is built from the point's name, and the name can carry parameters. Zach's example, a crate: container.body in the .blend is just the part/mesh, not a point — but point.container.lid.pivot: rotlock(x=+30) is a point: a lid pivot, rotation locked to the X axis, max +30° from its origin. So gizmo names form a small grammar (dotted path + : key=value params) that the build tool must parse and validate strictly — a typo'd parameter should be a build error, never a silent runtime no-op.

Gizmo authoring

Points are placed visually in Blender: one generic gizmo model (a clear orientation/alignment visual, sourced from the open-licensed free Resonite Assets) is inserted into the workspace once per point as instances of the single shared model, each instance named for its point — filesize cost per point is just a transform. The build tool reads the named instances' transforms into the item JSON and excludes gizmos from the exported model (authoring-only, never shipped). Working naming proposal (Lantern, open): instance names mirror the JSON path they feed — point.mag_insert, point.chamber, part.safety.safe, grip.primary — so the tool needs no mapping table.

Feed systems: one system, three shapes

  • Internal magazine (tube shotguns, Garand): an ordered list of rounds loaded through the load point. Firing consumes per the weapon type's policy — a Garand fires the latest round loaded (LIFO); an internal-mag shotgun may do the same or follow its own order.
  • External magazine: the same ordered-list logic deferred to the magazine item — the mag inherits the internal-mag system and hosts the list itself. (Matches the save format: containers are items, ammo order matters — game-planning #28.)
  • Revolver: the indexed-set shape above. Zach flags revolvers as the most complex feed system of all.
  • Break action: chambers parented to the barrel part; break open → old shells pop out at the moved points → insert new → close.

Open work

  • Terminology pass — point types, span/set conventions, the config-in-name grammar, the rotlock-style parameter vocabulary. Splits in two: game-side vocabulary with the game ideas (game-planning), format-side grammar with the addon format (#3).
  • Revolver indexing/rotation semantics in detail.
  • Which anatomy points the build tool extracts from gizmos vs leaves hand-authored (instinct: all of them, grips included — unconfirmed).
**Status: DESIGN IN DISCUSSION** (Zach + Lantern, 2026-10-21). Migrated out of the addon packaging proposal (#3) at Zach's direction: none of this is packaging format — it's the item/framework feature design that addon *data* expresses. The packaging proposal keeps only pack layout, keys, manifest, load order, and merging; everything about what an item definition *contains* lives here. ## The idea Modding is data-driven for the most part. An author writes config files that tell the game how to create an item. Zach's description of the intent: > You write config files that tell it how to create a new item, such as a SCAR: you define the grip pose, ammo type, mag types, mag insert location, orientation, and depth, bolt/slide location, safety lever states (pos/rot for each state), chamber location (where to render the bullet in the chamber), and the barrel exit. And bam, you have a SCAR. The **framework** supplies behavior; the **definition** supplies every interactive point and moving part as data. A new weapon on an existing framework is pure data. A new *kind* of weapon (Zach's example: laser guns — beam behavior isn't projectile behavior) needs a new framework, which is what the addon script tier is for. ## Materials Materials live in the item JSON as a slot array: each entry names a model material slot, a shared base material/shader by key, and the values to plug in (albedo, metallic, roughness, texture key...). A slot with no entry — or an item with no materials at all — gets a **generic object-space universal tiling material** (object-space detail, no UVs required) so any model still shows surface detail up close. (Example in the #3 attachment's item JSON.) ## Grips Grip poses are data tables: per grip, a `root` pos/rot (the object root's offset) plus a `bones` table of pos/rot per finger bone, **everything relative to the wrist (Hand) bone**, side-less bone names mirrored by the engine; unlisted bones keep rest pose + procedural curl. Primary, secondary, and manipulation grips share the shape. Grips are **optional** — no grips data means the procedural default grab (curl to collider). When authored, grips are tool-generated from a posed reference hand (see Gizmo authoring), never hand-typed. ## Anatomy points A weapon/item definition declares its interactive points: accepted magazine types, **mag insert** (position, orientation, depth), **bolt/slide location**, **lever states** (pos/rot per state — safety safe/fire, selectors), **chamber location** (where the chambered round renders), **barrel exit** (muzzle pos + direction). Points are the general mechanism for *any* item with moving parts, not just weapons. ### Points have shapes - **Single point** — chamber, barrel exit. One transform. - **Span (start + end)** — a slide/bolt has a start location and an end location; the framework animates between them. The mag well is span-like: entry point, orientation, seated depth — insertion is a path. - **Indexed set** — a revolver cylinder is N chamber points, indexed, plus a **rotation step** (360°/N) the cylinder snaps by to align the next chamber. - **Parented** — points can be bound to a part and resolve in that part's space. A break-action's chamber/eject points ride the barrel part: break it open and the points move with it, so eject/reload work at the moved positions with no special-casing. ### Points carry config in the name Functionality is built from the point's name, and the name can carry parameters. Zach's example, a crate: `container.body` in the .blend is just the part/mesh, not a point — but ``point.container.lid.pivot: rotlock(x=+30)`` is a point: a lid pivot, rotation locked to the X axis, max +30° from its origin. So gizmo names form a small grammar (dotted path + `: key=value` params) that the build tool must parse and validate strictly — a typo'd parameter should be a build error, never a silent runtime no-op. ### Gizmo authoring Points are placed visually in Blender: one generic **gizmo model** (a clear orientation/alignment visual, sourced from the open-licensed free Resonite Assets) is inserted into the workspace once per point as **instances of the single shared model**, each instance **named** for its point — filesize cost per point is just a transform. The build tool reads the named instances' transforms into the item JSON and **excludes gizmos from the exported model** (authoring-only, never shipped). Working naming proposal (Lantern, open): instance names mirror the JSON path they feed — `point.mag_insert`, `point.chamber`, `part.safety.safe`, `grip.primary` — so the tool needs no mapping table. ## Feed systems: one system, three shapes - **Internal magazine** (tube shotguns, Garand): an **ordered list** of rounds loaded through the load point. Firing consumes per the weapon type's policy — a Garand fires the *latest* round loaded (LIFO); an internal-mag shotgun may do the same or follow its own order. - **External magazine**: the same ordered-list logic **deferred to the magazine item** — the mag inherits the internal-mag system and hosts the list itself. (Matches the save format: containers are items, ammo order matters — game-planning #28.) - **Revolver**: the indexed-set shape above. Zach flags revolvers as the most complex feed system of all. - **Break action**: chambers parented to the barrel part; break open → old shells pop out at the moved points → insert new → close. ## Open work - **Terminology pass** — point types, span/set conventions, the config-in-name grammar, the `rotlock`-style parameter vocabulary. Splits in two: game-side vocabulary with the game ideas (game-planning), format-side grammar with the addon format (#3). - Revolver indexing/rotation semantics in detail. - Which anatomy points the build tool extracts from gizmos vs leaves hand-authored (instinct: all of them, grips included — unconfirmed).

The project currently has two different key formats for item definitions: the one #3 proposes and the one in the code skeletons. It's worth picking one before ItemRegistry and the snapshot save are implemented, because save files store these keys forever. That's the same reasoning #12 applies to da_* names.

Where Format Example
#3 (keyed references) and its coolpistols example <modid>.<tier>.<dotted path> deadair.data.items.mag_9x18_8
src/core/registry.gd:6-7, src/core/save_game.gd:9, src/items/item_definition.gd:14, src/items/item_instance.gd:21 <modid>.item.<category>.<name> deadair.item.weapons.1911

The shape of a definition differs too:

  • In #3 and here, a definition is JSON that points at everything by key: materials and textures here, and the model in #3's example ("model": "coolpistols.asset.models.pm_custom").
  • In src/items/item_definition.gd, it's a Resource that exports world_scene: PackedScene (line 29) and type_data: Resource (line 26, whose comment already calls that shape undecided).

A Resource that holds a PackedScene doesn't fit the zip tier, which per #3 carries JSON data plus raw assets that runtime loaders build. It also bypasses the key-based AssetLoader that #3 relies on.

Suggestion:

  • Settle on the #3 key format.
  • Have ItemDefinition hold keys (model_key: String) that the AssetLoader resolves at instantiation time.
  • Update the four skeleton files' examples (the 1911 would become deadair.data.items.1911), so the first real implementation doesn't inherit the other format.
The project currently has two different key formats for item definitions: the one #3 proposes and the one in the code skeletons. It's worth picking one before `ItemRegistry` and the snapshot save are implemented, because save files store these keys forever. That's the same reasoning #12 applies to `da_*` names. | Where | Format | Example | |---|---|---| | #3 (keyed references) and its `coolpistols` example | `<modid>.<tier>.<dotted path>` | `deadair.data.items.mag_9x18_8` | | `src/core/registry.gd:6-7`, `src/core/save_game.gd:9`, `src/items/item_definition.gd:14`, `src/items/item_instance.gd:21` | `<modid>.item.<category>.<name>` | `deadair.item.weapons.1911` | The shape of a definition differs too: - In #3 and here, a definition is JSON that points at everything by **key**: materials and textures here, and the model in #3's example (`"model": "coolpistols.asset.models.pm_custom"`). - In `src/items/item_definition.gd`, it's a `Resource` that exports `world_scene: PackedScene` (line 29) and `type_data: Resource` (line 26, whose comment already calls that shape undecided). A Resource that holds a `PackedScene` doesn't fit the zip tier, which per #3 carries JSON data plus raw assets that runtime loaders build. It also bypasses the key-based AssetLoader that #3 relies on. Suggestion: - Settle on the #3 key format. - Have `ItemDefinition` hold keys (`model_key: String`) that the AssetLoader resolves at instantiation time. - Update the four skeleton files' examples (the 1911 would become `deadair.data.items.1911`), so the first real implementation doesn't inherit the other format.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
dead-air/engine#4
No description provided.