@@ -47,6 +47,10 @@ type SystemManifest struct {
4747 // enabling this module (e.g., "D&D Character" with predefined fields).
4848 EntityPresets []EntityPresetDef `json:"entity_presets,omitempty"`
4949
50+ // RelationPresets are relation type templates that campaigns can adopt when
51+ // enabling this module (e.g., "has-item" for inventory tracking).
52+ RelationPresets []RelationPresetDef `json:"relation_presets,omitempty"`
53+
5054 // FoundrySystemID is the Foundry VTT game.system.id that this system
5155 // corresponds to (e.g., "dnd5e", "pf2e"). When set, the Foundry module
5256 // can automatically match this Chronicle system to the running Foundry
@@ -129,10 +133,38 @@ type EntityPresetDef struct {
129133 // Color is the hex color for the entity type badge.
130134 Color string `json:"color"`
131135
136+ // Category classifies the preset for feature gating (e.g., "character",
137+ // "item", "creature"). Used to identify which entity types belong to
138+ // specific features like the Armory (items) or NPC gallery (characters).
139+ Category string `json:"category,omitempty"`
140+
132141 // Fields are the default field definitions for entities of this type.
133142 Fields []FieldDef `json:"fields,omitempty"`
134143}
135144
145+ // RelationPresetDef describes a relation type template that a module provides.
146+ // Used to create system-specific relation types (e.g., "has-item" for inventory).
147+ type RelationPresetDef struct {
148+ // Slug is the URL-safe identifier (e.g., "has-item").
149+ Slug string `json:"slug"`
150+
151+ // Name is the display name (e.g., "Has Item").
152+ Name string `json:"name"`
153+
154+ // ReverseName is the reverse direction label (e.g., "In Inventory Of").
155+ ReverseName string `json:"reverse_name"`
156+
157+ // MetadataSchema defines the JSON metadata fields for this relation.
158+ // Keys are field names, values describe type and default value.
159+ MetadataSchema map [string ]RelationFieldSchema `json:"metadata_schema,omitempty"`
160+ }
161+
162+ // RelationFieldSchema defines a single metadata field on a relation preset.
163+ type RelationFieldSchema struct {
164+ Type string `json:"type"` // "number", "boolean", "string"
165+ Default any `json:"default"` // Default value for new relations.
166+ }
167+
136168// CharacterPreset returns the first entity preset whose slug ends with
137169// "-character", or nil if no character preset is defined. Used by the
138170// sync API to expose character field templates for actor sync.
@@ -145,6 +177,45 @@ func (m *SystemManifest) CharacterPreset() *EntityPresetDef {
145177 return nil
146178}
147179
180+ // ItemPreset returns the first entity preset with category "item", or nil
181+ // if no item preset is defined. Used by the Armory plugin and item sync.
182+ func (m * SystemManifest ) ItemPreset () * EntityPresetDef {
183+ for i := range m .EntityPresets {
184+ if m .EntityPresets [i ].Category == "item" {
185+ return & m .EntityPresets [i ]
186+ }
187+ }
188+ return nil
189+ }
190+
191+ // ItemFieldsForAPI builds the API response for item preset fields.
192+ // Returns nil if no item preset exists. Mirrors CharacterFieldsForAPI.
193+ func (m * SystemManifest ) ItemFieldsForAPI () * CharacterFieldsResponse {
194+ preset := m .ItemPreset ()
195+ if preset == nil {
196+ return nil
197+ }
198+
199+ fields := make ([]CharacterFieldExport , len (preset .Fields ))
200+ for i , f := range preset .Fields {
201+ fields [i ] = CharacterFieldExport {
202+ Key : f .Key ,
203+ Label : f .Label ,
204+ Type : f .Type ,
205+ FoundryPath : f .FoundryPath ,
206+ FoundryWritable : f .FoundryPath != "" && f .IsFoundryWritable (),
207+ }
208+ }
209+
210+ return & CharacterFieldsResponse {
211+ SystemID : m .ID ,
212+ PresetSlug : preset .Slug ,
213+ PresetName : preset .Name ,
214+ FoundrySystemID : m .FoundrySystemID ,
215+ Fields : fields ,
216+ }
217+ }
218+
148219// CharacterFieldsResponse is the API response shape for the character
149220// fields endpoint, containing field definitions with Foundry annotations.
150221type CharacterFieldsResponse struct {
@@ -211,6 +282,12 @@ type ValidationReport struct {
211282 // CharacterFieldCount is the number of fields on the character preset.
212283 CharacterFieldCount int `json:"character_field_count"`
213284
285+ // HasItemPreset indicates an item-category preset was found.
286+ HasItemPreset bool `json:"has_item_preset"`
287+
288+ // ItemFieldCount is the number of fields on the item preset.
289+ ItemFieldCount int `json:"item_field_count"`
290+
214291 // FoundryCompatible indicates foundry_system_id is set.
215292 FoundryCompatible bool `json:"foundry_compatible"`
216293
@@ -257,6 +334,12 @@ func (m *SystemManifest) BuildValidationReport() *ValidationReport {
257334 }
258335 }
259336
337+ // Analyze item preset.
338+ if itemPreset := m .ItemPreset (); itemPreset != nil {
339+ r .HasItemPreset = true
340+ r .ItemFieldCount = len (itemPreset .Fields )
341+ }
342+
260343 // Generate warnings.
261344 if r .CategoryCount == 0 {
262345 r .Warnings = append (r .Warnings , "No reference data categories defined" )
0 commit comments