Item JSON format v2: a_object / b_properties / c_assembly / d_grips (+ generic_pistol → generic_handgun rename) #47

Open
opened 2026-11-14 04:22:58 +00:00 by muse-lantern · 1 comment
Member

The plan

What we're doing, and why

We're rebuilding the item JSON format: every item file, both loaders, and the editor writer.

The current format has the data in the wrong places. The properties table is barely used. Weapon numbers, reference points, and states are scattered across flat top-level keys. The grip pose lives in two places at once (model.grip_offset / grip_rotate plus grip_poses). And file paths are fragile relative paths (../../../assets/...).

The new format puts each kind of data in exactly one place, namespaced so any module can address it, and ordered so a person reading the file sees what the object is before they see the pose data.

The generic pistol becomes the generic handgun in the same change. Its id, its mag's id, its file, and its model are all being renamed anyway — doing the rename separately would mean touching every reference twice.

The new shape, in words

An item file has four sections, in this order:

  • a_object — what the thing is. The controller that runs it, its display name, its model, its tags, its value and weight (both optional, default 0), and its colliders. a_object alone is a valid basic object — you can define a thing in the game with just this section.
  • b_properties — what the thing does, per type. Keyed by type (weapon_onehand). Each table holds the numbers (data), the positional references (refpoints), the state hook (states), and what it accepts (accepts_mag, accepts_ammo).
  • c_assembly — the moving parts. Each part is bound to a model object with an origin and a travel: the fire-select lever, the slide, the trigger. The fire-select modes and their lever poses live here.
  • d_grips — where hands go. Grip points keyed by name. Each has a role, the model object it attaches to, and per-hand pose + finger driver + finger states. A grip point is a grab trigger — it says "this spot can be grabbed" — and the pose stays the same while the part moves. Pose data goes last in the file on purpose — you read what the object is first.

Colliders live in a_object as a colliders table. It replaces model.colliders — the editor points at a_object.colliders instead. The table supports every collider type: box, mesh, convex, sphere, cylinder. A box entry has a size ([Length, Width, Height]), a position, and a rotation, and its origin is the center of its volume. Colliders show in the debug view when it is enabled.

Handedness and slot are separate properties. A weapon can be one-handed but a primary, or two-handed but a secondary.

Namespacing follows the module that owns the thing. Objects in the devkit carry the devkit: prefix (devkit:weapon/gun/generic_handgun, devkit:mag/generic_handgun, devkit:ammo.9mm). The controller carries the deadair: prefix (deadair:controller/generic_weapon) because it is deadair code, not devkit code.

The work, in order

Phase 0 — Settle the shape (this issue). The shape is settled. Nothing is built until the open questions are answered, because every item file and both loaders change in the same move.

Phase 1 — Loaders first. Teach both loaders to read the new shape before any item file is converted, so the game never has a half-migrated state.

  • ItemDefinition.from_dict() and ItemSpec learn the four sections, the namespaced paths, and the new controller field. (from_dict() does not copy grip_poses today, so the new top-level keys need plumbing in both loaders, plus ItemSpec @exports and to_item_definition().)
  • The loader resolves extension keys: weapon_onehand must count as weapon for is_type("weapon"). Today that is an exact key match.
  • The controller field resolves through the new deadair:controller/... registry. When the field is absent, it defaults to generic_object.
  • The old shape keeps loading until the migration lands (a fallback compose layer for grip_offset / grip_rotate). Then the fallback is removed.

Phase 2 — Migrate the items. Convert every item file (11+ items carry model.grip_offset, 3 carry grip_rotate). Scripted or editor-assisted — not by hand.

  • Fold grip_offset / grip_rotate into explicit grip_points primary poses. The editor already computes the composed pose, so the fold is mechanical.
  • Move each item's numbers into b_properties under its type key, its reference points into refpoints, and its states into states.
  • Drop reload_time and stack (see "What changes" below for why).
  • Move value / weight into a_object (both optional, default 0).
  • Rename the pistol file to generic_handgun.json and the mag file to match, and rename the GLB and its textures. Ids become devkit:weapon/gun/generic_handgun and devkit:mag/generic_handgun.
  • Move the colliders to a_object.colliders, replacing model.colliders. The table supports box, mesh, convex, sphere, and cylinder.
  • Clean up over-precise dev values where they are noise (for example the slide origin z 0.00004 → 0.0).
  • Remove travel from the grip points. Travel lives only in assembly now.

Phase 3 — Editor. The editor writes and reads the new shape, and the dock is rebuilt to match the Inspector.

  • Fixed key order for grip_points internals (role, object, mirror, then the hand entries) instead of an alphabetical sort. The a_ / b_ / c_ / d_ section prefixes already survive sorting.
  • Editing for all four sections, including the colliders table, with colliders visible in the debug view.
  • Dock rebuilt on Inspector patterns: section headers become left-aligned bold with the collapse arrow at the left; marker rows are selectable by clicking (the per-row "Select" button goes away — the color dot, name, and dimmed status stay); pos/rot/size become Vector3 rows (label left, three fields right) instead of three stacked full-width LineEdits; colliders become expandable rows with a small remove affordance; adding a marker or collider becomes a low-key "+ Add" row in the Inspector's "+ Add Metadata" style; the top actions (Open staging scene / Save JSON) become a slim toolbar; the saved-path status becomes a small dimmed label. The Grip preview section already follows the pattern and stays, tightened.
  • Click-to-select in the staging scene: clicking a visible marker or collider in the 3D viewport selects it (gizmo handles or a viewport raycast — implementer's call). Selection syncs three ways: viewport, dock list, selected-properties tab. The Inspector tab stays exactly as it is — this does not recreate it.
  • A second tab, "selected properties": it shows the selected thing's item-data properties, for configuring that item. It is not node properties — that is the Inspector's job and it stays untouched.
  • Round-trip: open an item, save it, and the file comes back with the same values and the same order.

Phase 4 — Rename blast radius. Follow the renames everywhere they are referenced: the registry, tests and suites, spawn lists, and the editor. The choke points are ItemDefinition.from_dict() and the registry rebuild — if those two are right, the references follow.

Phase 5 — Verify. The format is done when all of these are true:

  • The full headless battery is green, with the check count verified (not just the verdict), on the new main.
  • tests/grip_pose_check.gd (40 checks) still passes. The slice-1 behaviours survive: right hand as mirror master, 5 mm centeredness epsilon, derived poses not stored, poses in model space plus the scale rule.
  • The editor round-trips the generic handgun with no diff beyond the intended changes.
  • A bridge end-to-end run passes, and the handgun grabs and racks in-headset.

What changes

  • Item files gain four sections (a_object, b_properties, c_assembly, d_grips) and lose the flat top-level tables. model.grip_offset / grip_rotate fold into the primary grip pose; properties.weapon is replaced by b_properties.weapon_onehand.
  • References are namespaced by the module that owns them: devkit: for devkit objects, deadair: for the controller (deadair code).
  • The generic pistol is renamed to the generic handgun: file, item id, mag id, GLB, and every reference.
  • accepts splits into accepts_mag (which mags fit) and accepts_ammo (which caliber).
  • holster.hip is gone. The slot is devkit:weapon.secondary.
  • reload_time is removed. It does nothing for a VR game — a fixed reload time is an FPS-game holder, and in VR reload is as fast as the person can actually reload.
  • stack is removed. There is no stacking items in inventory — either you physically have the object or you don't. Storage containers define what they can hold, not the objects.
  • value and weight move into a_object (optional, default 0).
  • Colliders move to a_object.colliders, replacing model.colliders, and the table supports box, mesh, convex, sphere, and cylinder.
  • Travel lives only in assembly now. Grip points no longer carry travel — they are grab triggers, and the pose stays the same while the part moves.
  • The fire-select lever keeps its modes and poses in assembly.fire_select. The trigger gains a travel (rotation, 30°). The slide keeps its travel (0.05 m rearward).
  • The slide is physically rackable: a secondary grip point on Handgun_Upper with a slide_grip_pose driver. Physical racking replaces button-based racking, and the freed button cycles fire-select when holding a gun. That last part is an input binding change, not grip data.
  • The object controller and the grip controller are one thing. a_object.controller selects the controller for the object, and that controller also interprets the grips. The per-hand drivers (gun_grip_pose, slide_grip_pose) are the per-point interpreters it dispatches to.
  • The dock is rebuilt to match the Inspector: left-aligned bold section headers, click-to-select marker rows, Vector3 rows for pos/rot/size, expandable collider rows, low-key "+ Add" rows, and a slim toolbar for the top actions. The Grip preview section already follows the pattern and stays.
  • The staging scene supports click-to-select: clicking a visible marker or collider in the 3D viewport selects it. Selection syncs between the viewport, the dock list, and the selected-properties tab. The Inspector tab stays as it is — nothing recreates it.
  • A second tab, "selected properties", shows the selected thing's item-data properties for configuring it. It is not a second Inspector — the Inspector keeps showing node properties.

What does not change

  • The modes-as-data-overrides idea (a mode altering a data field by path) stays parked. Fire-select modes stay in assembly.fire_select as they are.
  • Burst fire: no handlers exist. The modes are semi, safe, auto, shotgun.
  • The object.type taxonomy is still undecided. The slide is the case that will define it.
  • Hand physics, the #42 H5 curl calibration, and the H6 pivot split are separate threads — not this build.
  • The #45 slice-1 behaviours listed in Phase 5 must survive unchanged.

Decisions already made

  • grip_controller ≡ driver ≡ a_object.controller. One controller, unified, reusable across multiple items (Zach, 2026-11-13). For the handgun it is generic_weapon. (generic_gun in the first draft was a typo.)
  • Controller hierarchy. generic_object is the base and the default. generic_weapon and generic_utility extend it. controller is a new addressable kind (deadair:controller/...).
  • Vectors stay arrays. Rotation stays Euler degrees (pitch, yaw, roll) in JSON. Quaternions are Godot-internal only.
  • Pose frame. A grip pose is relative to its assigned object's default position and orientation. Item root when no object is assigned.
  • Tag spelling vs property-key spelling are different by design. Tag key: weapon.onehand (dot). Properties key: weapon_onehand (underscore). The #33 type-table spelling weapon_one_hand is superseded by weapon_onehand.
  • reload_time and stack dropped. value / weight into a_object (optional, default 0). weapon.secondary replaces the holster tag.
  • Slide origin z 0.00004 → 0.0 is a deliberate cleanup, not a retype. That much precision is not useful for dev data.
  • The pistol grip_offset.y "discrepancy" is not real (verified 2026-11-13). -0.0607 is Zach's own value from commit 8d466cf, identical at PR base 6eec0f2. The "-0.03 on main" was a misattribution — that is the bullet's and the mag's y. Search tip: the stored value is full-precision -0.0606891736388206, so searching "0.0607" misses it.
  • Key order. Pose data at the bottom of each grip point: role, object, mirror, then hand entries. Inside a hand entry: pose, driver, state. schema first in grip_points.
  • The slide grip point must track Handgun_Upper's live transform. GrabPoints are static today and the slide does not move at runtime. The slide grip pose [0, 0.01, -0.05] is illustrative only, not authored or measured.
  • Travel lives only in assembly. Grip points are grab triggers; the pose stays the same while the part travels (Zach, 2026-11-13).
  • a_object.colliders replaces model.colliders. The editor points at a_object.colliders instead, and the table supports box, mesh, convex, sphere, and cylinder (Zach, 2026-11-13).
  • Namespacing by owner. Devkit objects carry devkit:; the controller carries deadair: because it is deadair code (Zach, 2026-11-13). The handgun takes devkit:ammo.9mm; a future 9x18 ammo would be deadair:ammo.9x18.

The full example (appendix — the Generic Handgun)

{
	"a_object": { // global stuff for the object, this alone can be defined to have a basic object in the game
		"controller": "deadair:controller/generic_weapon", // code that controls this object, `generic_object` is the base and default. `generic_weapon` `generic_utility` both extend off that type for custom logic
		"name": "Generic Handgun",
		"model": "devkit:/assets/items/weapon/gun/generic_handgun.glb", // the model lives in the devkit, so it carries the devkit: prefix
		"tags": [
			"devkit:weapon", // object type
			"devkit:weapon.onehand", // one-handed property
			"devkit:weapon.secondary" // "secondary weapon" property — which holster/slot it goes in
		],
		"value": 350.0, // optional, defaults to 0
		"weight": 0.9, // optional, defaults to 0 — a general weight rating
		"colliders": [
			// each box entry: { "size": [Length, Width, Height], "pos": [...], "rot": [...] }
			// the origin is the center of volume of the box; colliders show with the debug view enabled
			// the table also supports mesh, convex, sphere, and cylinder entries
		]
	},
	"b_properties": {
		"weapon_onehand": {
			"accepts_mag": "devkit:mag/generic_handgun",
			"accepts_ammo": "devkit:ammo.9mm",
			"data": {
				"damage": 28.0,
				"falloff": {
					"min": 0.5,
					"max": 25.0
				},
				"recoil": {
					"degrees": 3.0,
					"recover": 18.0
				},
				"rpm": 300.0,
				"spread_deg": 0.6,
				"projectiles": 1.0,
				"range": 60.0
			},
			"refpoints": {
				"chamber": [
					0.0,
					0.0109035111963749,
					0.0382033847272396
				],
				"magwell": {
					"ref_a": {
						"pos": [
							0.0,
							0.0,
							0.0599999986588955
						],
						"rot": [
							-12.0,
							0.0,
							0.0
						]
					},
					"ref_b": {
						"pos": [
							0.0,
							-0.0489073619246483,
							0.0703955739736557
						]
						// inherits rot from ref_a; a ref_b that declares its own rot overrides
					}
				},
				"muzzle": [
					0.0,
					0.0109035111963749,
					-0.113071337342262
				]
			},
			"states": {} // generic, can apply to other items such as flashlights, handled by the controller
		}
	},
	"c_assembly": {
		"fire_select": {
			"modes": {
				"safe": {
					"pos": [0.0, 0.0, 0.0],
					"rot": [0.0, 0.0, 0.0]
				},
				"semi": {
					"pos": [0.0, 0.0, 0.0],
					"rot": [0.0, 0.0, 0.0]
				}
			},
			"object": "Handgun_Safety",
			"origin": {
				"pos": [0.00005, -0.006296, 0.010929],
				"rot": [0.0, 90.0, 0.0]
			}
		},
		"slide": {
			"object": "Handgun_Upper",
			"origin": {
				"pos": [0.0, 0.0, 0.0],
				"rot": [0.0, 90.0, 0.0]
			},
			"travel": {
				"pos": [0.0, 0.0, 0.05],
				"rot": [0.0, 0.0, 0.0]
			}
		},
		"trigger": {
			"object": "Handgun_Trigger",
			"travel": {
				"rot": [ // rotation from how much the trigger can rotate; the origin is not needed to be defined as its already *right there* where it needs to be
					30.0,
					0.0,
					0.0
				]
			}
		}
	},
	"d_grips": {
		"grip_points": {
			"schema": 1,
			"primary": {
				"role": "primary",
				"object": "Handgun_Body_Lower",
				"both": {
					"pose": {
						"pos": [0.0, -0.0606891736388206, 0.0534712076187134],
						"rot": [90.0, 0.0, 0.0]
					},
					"driver": "gun_grip_pose",
					"state": {
						"default": {
							"fingers": {
								"index": { "curl": 0.1 },
								"middle": { "curl": 0.9 },
								"pinky": { "curl": 1.0 },
								"ring": { "curl": 0.95 },
								"thumb": { "curl": 0.6, "opposition": 0.8 }
							},
							"spread": 0.2
						},
						"trigger_full": {
							"fingers": { "index": { "curl": 0.9 } }
						},
						"trigger_rest": {
							"fingers": { "index": { "curl": 0.0 } }
						}
					}
				}
			},
			"slide": {
				"role": "secondary",
				"object": "Handgun_Upper",
				"both": {
					"pose": {
						"pos": [0.0, 0.01, -0.05],
						"rot": [0.0, 0.0, 0.0]
					},
					"driver": "slide_grip_pose",
					"state": {
						"default": {
							"fingers": {
								"index": { "curl": 0.8 },
								"middle": { "curl": 0.8 },
								"pinky": { "curl": 0.8 },
								"ring": { "curl": 0.8 },
								"thumb": { "curl": 0.5, "opposition": 0.6 }
							},
							"spread": 0.1
						}
					}
				}
			}
		}
	}
}

References

  • PR #45 (per-hand grip poses, slice 1) — green at 15dc67a, verified in-editor and in-headset 2026-11-13, awaiting merge. This build starts after #45 merges (it touches the same files; after it lands, close PR #44).
  • Issue #46 (grip_points system design) — subsumed by this format. d_grips is the recommended shape from the 2026-11-13 grip-data-shape investigation.
  • Grip-data-shape investigation (2026-11-13): the 13 things the data must express, the holes in the first sketch, the worked slide-rack example, and the key-order decision.

Answered questions

These were the open questions. Zach answered all six in comment 1016 (2026-11-14). They stay at the bottom as the record.

  1. value / weight placement. Into a_object, for simplicity. Both optional — value defaults to 0, weight is a general weight rating.
  2. GLB rename? Yes, the GLB and its textures get renamed too.
  3. Id prefix devkit: vs deadair:. The deadair: in the first draft was for reference. The objects are still in the devkit, so they carry the devkit: prefix. The controller carries the deadair: prefix because it is deadair code, not devkit code. (The model path and tags follow the same rule in the example above — devkit: for devkit things.)
  4. Caliber: 9mm vs 9x18. The Generic Handgun uses devkit:ammo.9mm. Later, when new ammo types are defined, the 9x18 ammo would be deadair:ammo.9x18.
  5. Point travel vs assembly.slide.travel. Remove the travel values from the grip points — they live in assembly now. Grip points only serve as "bool" triggers of "spot is grabbed". Some interactions (slide / racking a round) allow travel, but the grip pose in those instances remains the same.
  6. a_object.colliders vs model.colliders. Make the editor point to a_object.colliders instead. Effectively the same thing, moved to a dedicated table in the object instead of sitting willy-nilly. a_object.colliders still supports the other collider definitions: box, mesh, convex, sphere, cylinder.
## The plan ### What we're doing, and why We're rebuilding the item JSON format: every item file, both loaders, and the editor writer. The current format has the data in the wrong places. The properties table is barely used. Weapon numbers, reference points, and states are scattered across flat top-level keys. The grip pose lives in two places at once (`model.grip_offset` / `grip_rotate` plus `grip_poses`). And file paths are fragile relative paths (`../../../assets/...`). The new format puts each kind of data in exactly one place, namespaced so any module can address it, and ordered so a person reading the file sees what the object *is* before they see the pose data. The generic pistol becomes the generic handgun in the same change. Its id, its mag's id, its file, and its model are all being renamed anyway — doing the rename separately would mean touching every reference twice. ### The new shape, in words An item file has four sections, in this order: - **`a_object` — what the thing is.** The controller that runs it, its display name, its model, its tags, its value and weight (both optional, default 0), and its colliders. `a_object` alone is a valid basic object — you can define a thing in the game with just this section. - **`b_properties` — what the thing does, per type.** Keyed by type (`weapon_onehand`). Each table holds the numbers (`data`), the positional references (`refpoints`), the state hook (`states`), and what it accepts (`accepts_mag`, `accepts_ammo`). - **`c_assembly` — the moving parts.** Each part is bound to a model object with an origin and a travel: the fire-select lever, the slide, the trigger. The fire-select modes and their lever poses live here. - **`d_grips` — where hands go.** Grip points keyed by name. Each has a role, the model object it attaches to, and per-hand pose + finger driver + finger states. A grip point is a grab trigger — it says "this spot can be grabbed" — and the pose stays the same while the part moves. Pose data goes last in the file on purpose — you read what the object is first. Colliders live in `a_object` as a `colliders` table. It replaces `model.colliders` — the editor points at `a_object.colliders` instead. The table supports every collider type: box, mesh, convex, sphere, cylinder. A box entry has a size (`[Length, Width, Height]`), a position, and a rotation, and its origin is the center of its volume. Colliders show in the debug view when it is enabled. Handedness and slot are separate properties. A weapon can be one-handed but a primary, or two-handed but a secondary. Namespacing follows the module that owns the thing. Objects in the devkit carry the `devkit:` prefix (`devkit:weapon/gun/generic_handgun`, `devkit:mag/generic_handgun`, `devkit:ammo.9mm`). The controller carries the `deadair:` prefix (`deadair:controller/generic_weapon`) because it is deadair code, not devkit code. ### The work, in order **Phase 0 — Settle the shape (this issue).** The shape is settled. Nothing is built until the open questions are answered, because every item file and both loaders change in the same move. **Phase 1 — Loaders first.** Teach both loaders to read the new shape before any item file is converted, so the game never has a half-migrated state. - `ItemDefinition.from_dict()` and `ItemSpec` learn the four sections, the namespaced paths, and the new `controller` field. (`from_dict()` does not copy `grip_poses` today, so the new top-level keys need plumbing in both loaders, plus `ItemSpec` `@export`s and `to_item_definition()`.) - The loader resolves extension keys: `weapon_onehand` must count as `weapon` for `is_type("weapon")`. Today that is an exact key match. - The `controller` field resolves through the new `deadair:controller/...` registry. When the field is absent, it defaults to `generic_object`. - The old shape keeps loading until the migration lands (a fallback compose layer for `grip_offset` / `grip_rotate`). Then the fallback is removed. **Phase 2 — Migrate the items.** Convert every item file (11+ items carry `model.grip_offset`, 3 carry `grip_rotate`). Scripted or editor-assisted — not by hand. - Fold `grip_offset` / `grip_rotate` into explicit `grip_points` primary poses. The editor already computes the composed pose, so the fold is mechanical. - Move each item's numbers into `b_properties` under its type key, its reference points into `refpoints`, and its states into `states`. - Drop `reload_time` and `stack` (see "What changes" below for why). - Move `value` / `weight` into `a_object` (both optional, default 0). - Rename the pistol file to `generic_handgun.json` and the mag file to match, and rename the GLB and its textures. Ids become `devkit:weapon/gun/generic_handgun` and `devkit:mag/generic_handgun`. - Move the colliders to `a_object.colliders`, replacing `model.colliders`. The table supports box, mesh, convex, sphere, and cylinder. - Clean up over-precise dev values where they are noise (for example the slide origin z `0.00004` → `0.0`). - Remove `travel` from the grip points. Travel lives only in `assembly` now. **Phase 3 — Editor.** The editor writes and reads the new shape, and the dock is rebuilt to match the Inspector. - Fixed key order for `grip_points` internals (role, object, mirror, then the hand entries) instead of an alphabetical sort. The `a_` / `b_` / `c_` / `d_` section prefixes already survive sorting. - Editing for all four sections, including the colliders table, with colliders visible in the debug view. - Dock rebuilt on Inspector patterns: section headers become left-aligned bold with the collapse arrow at the left; marker rows are selectable by clicking (the per-row "Select" button goes away — the color dot, name, and dimmed status stay); pos/rot/size become Vector3 rows (label left, three fields right) instead of three stacked full-width LineEdits; colliders become expandable rows with a small remove affordance; adding a marker or collider becomes a low-key "+ Add" row in the Inspector's "+ Add Metadata" style; the top actions (Open staging scene / Save JSON) become a slim toolbar; the saved-path status becomes a small dimmed label. The Grip preview section already follows the pattern and stays, tightened. - Click-to-select in the staging scene: clicking a visible marker or collider in the 3D viewport selects it (gizmo handles or a viewport raycast — implementer's call). Selection syncs three ways: viewport, dock list, selected-properties tab. The Inspector tab stays exactly as it is — this does not recreate it. - A second tab, "selected properties": it shows the selected thing's item-data properties, for configuring that item. It is not node properties — that is the Inspector's job and it stays untouched. - Round-trip: open an item, save it, and the file comes back with the same values and the same order. **Phase 4 — Rename blast radius.** Follow the renames everywhere they are referenced: the registry, tests and suites, spawn lists, and the editor. The choke points are `ItemDefinition.from_dict()` and the registry rebuild — if those two are right, the references follow. **Phase 5 — Verify.** The format is done when all of these are true: - The full headless battery is green, with the check count verified (not just the verdict), on the new main. - `tests/grip_pose_check.gd` (40 checks) still passes. The slice-1 behaviours survive: right hand as mirror master, 5 mm centeredness epsilon, derived poses not stored, poses in model space plus the scale rule. - The editor round-trips the generic handgun with no diff beyond the intended changes. - A bridge end-to-end run passes, and the handgun grabs and racks in-headset. ### What changes - Item files gain four sections (`a_object`, `b_properties`, `c_assembly`, `d_grips`) and lose the flat top-level tables. `model.grip_offset` / `grip_rotate` fold into the primary grip pose; `properties.weapon` is replaced by `b_properties.weapon_onehand`. - References are namespaced by the module that owns them: `devkit:` for devkit objects, `deadair:` for the controller (deadair code). - The generic pistol is renamed to the generic handgun: file, item id, mag id, GLB, and every reference. - `accepts` splits into `accepts_mag` (which mags fit) and `accepts_ammo` (which caliber). - `holster.hip` is gone. The slot is `devkit:weapon.secondary`. - `reload_time` is removed. It does nothing for a VR game — a fixed reload time is an FPS-game holder, and in VR reload is as fast as the person can actually reload. - `stack` is removed. There is no stacking items in inventory — either you physically have the object or you don't. Storage containers define what they can hold, not the objects. - `value` and `weight` move into `a_object` (optional, default 0). - Colliders move to `a_object.colliders`, replacing `model.colliders`, and the table supports box, mesh, convex, sphere, and cylinder. - Travel lives only in `assembly` now. Grip points no longer carry `travel` — they are grab triggers, and the pose stays the same while the part moves. - The fire-select lever keeps its modes and poses in `assembly.fire_select`. The trigger gains a `travel` (rotation, 30°). The slide keeps its travel (0.05 m rearward). - The slide is physically rackable: a secondary grip point on `Handgun_Upper` with a `slide_grip_pose` driver. Physical racking replaces button-based racking, and the freed button cycles fire-select when holding a gun. That last part is an input binding change, not grip data. - The object controller and the grip controller are one thing. `a_object.controller` selects the controller for the object, and that controller also interprets the grips. The per-hand `driver`s (`gun_grip_pose`, `slide_grip_pose`) are the per-point interpreters it dispatches to. - The dock is rebuilt to match the Inspector: left-aligned bold section headers, click-to-select marker rows, Vector3 rows for pos/rot/size, expandable collider rows, low-key "+ Add" rows, and a slim toolbar for the top actions. The Grip preview section already follows the pattern and stays. - The staging scene supports click-to-select: clicking a visible marker or collider in the 3D viewport selects it. Selection syncs between the viewport, the dock list, and the selected-properties tab. The Inspector tab stays as it is — nothing recreates it. - A second tab, "selected properties", shows the selected thing's item-data properties for configuring it. It is not a second Inspector — the Inspector keeps showing node properties. ### What does not change - The modes-as-data-overrides idea (a mode altering a data field by path) stays parked. Fire-select modes stay in `assembly.fire_select` as they are. - Burst fire: no handlers exist. The modes are semi, safe, auto, shotgun. - The `object.type` taxonomy is still undecided. The slide is the case that will define it. - Hand physics, the #42 H5 curl calibration, and the H6 pivot split are separate threads — not this build. - The #45 slice-1 behaviours listed in Phase 5 must survive unchanged. ### Decisions already made - **`grip_controller` ≡ `driver` ≡ `a_object.controller`.** One controller, unified, reusable across multiple items (Zach, 2026-11-13). For the handgun it is `generic_weapon`. (`generic_gun` in the first draft was a typo.) - **Controller hierarchy.** `generic_object` is the base and the default. `generic_weapon` and `generic_utility` extend it. `controller` is a new addressable kind (`deadair:controller/...`). - **Vectors stay arrays.** Rotation stays Euler degrees (pitch, yaw, roll) in JSON. Quaternions are Godot-internal only. - **Pose frame.** A grip pose is relative to its assigned object's default position and orientation. Item root when no object is assigned. - **Tag spelling vs property-key spelling are different by design.** Tag key: `weapon.onehand` (dot). Properties key: `weapon_onehand` (underscore). The #33 type-table spelling `weapon_one_hand` is superseded by `weapon_onehand`. - **`reload_time` and `stack` dropped.** `value` / `weight` into `a_object` (optional, default 0). `weapon.secondary` replaces the holster tag. - **Slide origin z `0.00004` → `0.0` is a deliberate cleanup**, not a retype. That much precision is not useful for dev data. - **The pistol `grip_offset.y` "discrepancy" is not real** (verified 2026-11-13). `-0.0607` is Zach's own value from commit `8d466cf`, identical at PR base `6eec0f2`. The "-0.03 on main" was a misattribution — that is the bullet's and the mag's y. Search tip: the stored value is full-precision `-0.0606891736388206`, so searching `"0.0607"` misses it. - **Key order.** Pose data at the bottom of each grip point: role, object, mirror, then hand entries. Inside a hand entry: pose, driver, state. `schema` first in `grip_points`. - **The slide grip point must track `Handgun_Upper`'s live transform.** GrabPoints are static today and the slide does not move at runtime. The slide grip pose `[0, 0.01, -0.05]` is illustrative only, not authored or measured. - **Travel lives only in `assembly`.** Grip points are grab triggers; the pose stays the same while the part travels (Zach, 2026-11-13). - **`a_object.colliders` replaces `model.colliders`.** The editor points at `a_object.colliders` instead, and the table supports box, mesh, convex, sphere, and cylinder (Zach, 2026-11-13). - **Namespacing by owner.** Devkit objects carry `devkit:`; the controller carries `deadair:` because it is deadair code (Zach, 2026-11-13). The handgun takes `devkit:ammo.9mm`; a future 9x18 ammo would be `deadair:ammo.9x18`. ### The full example (appendix — the Generic Handgun) ```jsonc { "a_object": { // global stuff for the object, this alone can be defined to have a basic object in the game "controller": "deadair:controller/generic_weapon", // code that controls this object, `generic_object` is the base and default. `generic_weapon` `generic_utility` both extend off that type for custom logic "name": "Generic Handgun", "model": "devkit:/assets/items/weapon/gun/generic_handgun.glb", // the model lives in the devkit, so it carries the devkit: prefix "tags": [ "devkit:weapon", // object type "devkit:weapon.onehand", // one-handed property "devkit:weapon.secondary" // "secondary weapon" property — which holster/slot it goes in ], "value": 350.0, // optional, defaults to 0 "weight": 0.9, // optional, defaults to 0 — a general weight rating "colliders": [ // each box entry: { "size": [Length, Width, Height], "pos": [...], "rot": [...] } // the origin is the center of volume of the box; colliders show with the debug view enabled // the table also supports mesh, convex, sphere, and cylinder entries ] }, "b_properties": { "weapon_onehand": { "accepts_mag": "devkit:mag/generic_handgun", "accepts_ammo": "devkit:ammo.9mm", "data": { "damage": 28.0, "falloff": { "min": 0.5, "max": 25.0 }, "recoil": { "degrees": 3.0, "recover": 18.0 }, "rpm": 300.0, "spread_deg": 0.6, "projectiles": 1.0, "range": 60.0 }, "refpoints": { "chamber": [ 0.0, 0.0109035111963749, 0.0382033847272396 ], "magwell": { "ref_a": { "pos": [ 0.0, 0.0, 0.0599999986588955 ], "rot": [ -12.0, 0.0, 0.0 ] }, "ref_b": { "pos": [ 0.0, -0.0489073619246483, 0.0703955739736557 ] // inherits rot from ref_a; a ref_b that declares its own rot overrides } }, "muzzle": [ 0.0, 0.0109035111963749, -0.113071337342262 ] }, "states": {} // generic, can apply to other items such as flashlights, handled by the controller } }, "c_assembly": { "fire_select": { "modes": { "safe": { "pos": [0.0, 0.0, 0.0], "rot": [0.0, 0.0, 0.0] }, "semi": { "pos": [0.0, 0.0, 0.0], "rot": [0.0, 0.0, 0.0] } }, "object": "Handgun_Safety", "origin": { "pos": [0.00005, -0.006296, 0.010929], "rot": [0.0, 90.0, 0.0] } }, "slide": { "object": "Handgun_Upper", "origin": { "pos": [0.0, 0.0, 0.0], "rot": [0.0, 90.0, 0.0] }, "travel": { "pos": [0.0, 0.0, 0.05], "rot": [0.0, 0.0, 0.0] } }, "trigger": { "object": "Handgun_Trigger", "travel": { "rot": [ // rotation from how much the trigger can rotate; the origin is not needed to be defined as its already *right there* where it needs to be 30.0, 0.0, 0.0 ] } } }, "d_grips": { "grip_points": { "schema": 1, "primary": { "role": "primary", "object": "Handgun_Body_Lower", "both": { "pose": { "pos": [0.0, -0.0606891736388206, 0.0534712076187134], "rot": [90.0, 0.0, 0.0] }, "driver": "gun_grip_pose", "state": { "default": { "fingers": { "index": { "curl": 0.1 }, "middle": { "curl": 0.9 }, "pinky": { "curl": 1.0 }, "ring": { "curl": 0.95 }, "thumb": { "curl": 0.6, "opposition": 0.8 } }, "spread": 0.2 }, "trigger_full": { "fingers": { "index": { "curl": 0.9 } } }, "trigger_rest": { "fingers": { "index": { "curl": 0.0 } } } } } }, "slide": { "role": "secondary", "object": "Handgun_Upper", "both": { "pose": { "pos": [0.0, 0.01, -0.05], "rot": [0.0, 0.0, 0.0] }, "driver": "slide_grip_pose", "state": { "default": { "fingers": { "index": { "curl": 0.8 }, "middle": { "curl": 0.8 }, "pinky": { "curl": 0.8 }, "ring": { "curl": 0.8 }, "thumb": { "curl": 0.5, "opposition": 0.6 } }, "spread": 0.1 } } } } } } } ``` ### References - PR #45 (per-hand grip poses, slice 1) — green at `15dc67a`, verified in-editor and in-headset 2026-11-13, awaiting merge. **This build starts after #45 merges** (it touches the same files; after it lands, close PR #44). - Issue #46 (grip_points system design) — subsumed by this format. `d_grips` is the recommended shape from the 2026-11-13 grip-data-shape investigation. - Grip-data-shape investigation (2026-11-13): the 13 things the data must express, the holes in the first sketch, the worked slide-rack example, and the key-order decision. ### Answered questions These were the open questions. Zach answered all six in comment 1016 (2026-11-14). They stay at the bottom as the record. 1. **`value` / `weight` placement.** Into `a_object`, for simplicity. Both optional — value defaults to 0, weight is a general weight rating. 2. **GLB rename?** Yes, the GLB and its textures get renamed too. 3. **Id prefix `devkit:` vs `deadair:`.** The `deadair:` in the first draft was for reference. The objects are still in the devkit, so they carry the `devkit:` prefix. The controller carries the `deadair:` prefix because it is deadair code, not devkit code. (The model path and tags follow the same rule in the example above — `devkit:` for devkit things.) 4. **Caliber: `9mm` vs `9x18`.** The Generic Handgun uses `devkit:ammo.9mm`. Later, when new ammo types are defined, the 9x18 ammo would be `deadair:ammo.9x18`. 5. **Point `travel` vs `assembly.slide.travel`.** Remove the travel values from the grip points — they live in `assembly` now. Grip points only serve as "bool" triggers of "spot is grabbed". Some interactions (slide / racking a round) allow travel, but the grip pose in those instances remains the same. 6. **`a_object.colliders` vs `model.colliders`.** Make the editor point to `a_object.colliders` instead. Effectively the same thing, moved to a dedicated table in the object instead of sitting willy-nilly. `a_object.colliders` still supports the other collider definitions: box, mesh, convex, sphere, cylinder.
Owner
  1. I feel like they should go into a_object for simplicity, they are optional values (default to 0 value, and a general weight rating)

  2. yes they get renamed

  3. okay my use of deadair: in those areas was for reference, since the objects are still in the devkit they would have the devkit: prefix, however the controller would use the deadair prefix as that is deadair code and not devkit code

  4. the Generic Handgun uses devkit:ammo.9mm ammo, however later on when new ammo types are defined, the 9x18 ammo would be deadair:ammo.9x18,

  5. this is a conundrum, and a complex situation to correctly do,
    grip_points is meant for placing the hand relative to the object defined, however in this case it has diplucated values, which we can remove the travel values from the grip points, as those are now apart of the assembly, and with the grip points only serving as "bool" triggers of "spot is grabbed" and some interactions (slide/racking a round) allow travel, but the grip pose in those instances would remain the same.

  6. make the editor point to a_object.colliders instead, effectively mean the same thing, we are moving the colliders to a dedicated table in the object instead of being there willy-nilly,
    of course the a_object.colliders should still support the other collider method definitions, (box, mesh, convex, sphere, cylinder) now that I think about it, makes it easier,

1) I feel like they should go into `a_object` for simplicity, they are optional values (default to 0 value, and a general weight rating) 2) yes they get renamed 3) okay my use of `deadair:` in those areas was for reference, since the objects are still in the devkit they would have the `devkit:` prefix, however the controller would use the `deadair` prefix as that is deadair code and not devkit code 4) the Generic Handgun uses `devkit:ammo.9mm` ammo, however later on when new ammo types are defined, the 9x18 ammo would be `deadair:ammo.9x18`, 5) this is a conundrum, and a complex situation to correctly do, grip_points is meant for placing the hand relative to the object defined, however in this case it has diplucated values, which we can remove the travel values from the grip points, as those are now apart of the assembly, and with the grip points only serving as "bool" triggers of "spot is grabbed" and some interactions (slide/racking a round) allow travel, but the grip pose in those instances would remain the same. 6) make the editor point to `a_object.colliders` instead, *effectively* mean the same thing, we are moving the colliders to a dedicated table in the object instead of being there willy-nilly, of course the `a_object.colliders` *should* still support the other collider method definitions, (box, mesh, convex, sphere, cylinder) now that I think about it, makes it easier,
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#47
No description provided.