# Jovian Tag System

A hierarchical, strongly-typed tag system for Unity. Define tags as dot-delimited hierarchies (`Damage.Fire.AOE`), auto-generate type-safe C# constants, and query relationships like ancestor/descendant/sibling at runtime with zero allocations.

## Table of Contents
- [Installation](#installation)
- [Quick Start](#quick-start)
- [**Important: Tags Are Not Inherited**](#important-tags-are-not-inherited)
- [Hierarchical Tags](#hierarchical-tags)
- [Tag Editor Window](#tag-editor-window)
- [Using Tags in Code](#using-tags-in-code)
- [Inspector Integration](#inspector-integration)
- [Tag Picker Popup](#tag-picker-popup)
- [Code Generation](#code-generation)
- [Tag Containers](#tag-containers)
- [Tag Containers with Data](#tag-containers-with-data)
- [Runtime API Reference](#runtime-api-reference)
- [Performance](#performance)

## Quick Start

### 1. Create a Tag Settings asset
Right-click in the Project window > `Create` > `Jovian` > `Tag System` > `Tag Settings`

### 2. Add tags
Open the Tag Editor via `Jovian` > `Tag System` > `Tag Editor...`

Add tags like:
```
Character
Character.Player
Character.Enemy
Character.Enemy.Boss
Damage
Damage.Fire
Damage.Ice
Status
Status.Burning
Status.Frozen
```

### 3. Click Save & Generate
This creates a `GameTags.cs` file with strongly-typed constants:

```csharp
// Auto-generated
public static partial class GameTags {
    public static class Character {
        public static JovianTag Character_Tag;
        public static class Enemy {
            public static JovianTag Enemy_Tag;
            public static JovianTag Boss_Tag;
        }
        public static JovianTag Player_Tag;
    }
    public static class Damage {
        public static JovianTag Damage_Tag;
        public static JovianTag Fire_Tag;
        public static JovianTag Ice_Tag;
    }
}
```

### 4. Use in code
```csharp
if (projectile.IsDescendantOf(GameTags.Damage.Damage_Tag)) {
    // This is any damage type (Fire, Ice, etc.)
}
```

---

## Important: Tags Are Not Inherited

> **TL;DR — Tagging an object with `Enemy.Name.Butcher.Size.Large` does NOT automatically assign `Enemy.Name.Butcher` or `Enemy` to that object.**

Each dot-delimited path is a **distinct, independent tag**. The hierarchy is **structural** — it enables tree queries — but it does **not** imply membership.

### Concrete Example

Register this tag in the Tag Editor:
```
Enemy.Name.Butcher.Size.Large
```

The system creates **5 separate tag definitions** (one per segment), parented in the tree:
- `Enemy`
- `Enemy.Name`
- `Enemy.Name.Butcher`
- `Enemy.Name.Butcher.Size`
- `Enemy.Name.Butcher.Size.Large`

Now tag a game object with `Enemy.Name.Butcher.Size.Large`. On that object, **only that one tag is set.** The ancestor tags exist as definitions, but they are not active on the object.

### What This Means in Practice

| You want to match... | Do this |
|----------------------|---------|
| Only Large Butcher | Tag object with `Enemy.Name.Butcher.Size.Large`, query with `Contains` |
| Any Butcher (any size) via separate tag | Add a second tag `Enemy.Name.Butcher` to the object |
| Any Butcher (any size) via hierarchy | Tag with `Enemy.Name.Butcher.Size.Large`, query with `ContainsDescendantOf(GameTags.Enemy.Name.Butcher_Tag)` |

### Exact vs Hierarchy Queries

| Query | Behavior |
|-------|----------|
| `Contains(tag)` | **Exact match only** — matches the literal tag you assigned |
| `ContainsDescendantOf(tag)` | Matches any descendant of `tag`, including `tag` itself |
| `ContainsAncestorOf(tag)` | Matches any ancestor of `tag` |

**The tag itself does not decide matching behavior — the query does.** Choose the query type based on your intent: `Contains` for "this exact thing," `ContainsDescendantOf` for "anything in this category."

---

## Hierarchical Tags

Tags are organized in a tree using dot-delimited names. Each segment creates a level in the hierarchy.

```
Damage              ← root tag (depth 0)
├── Damage.Fire     ← child of Damage (depth 1)
│   └── Damage.Fire.AOE  ← child of Fire (depth 2)
├── Damage.Ice      ← child of Damage (depth 1)
└── Damage.Lightning ← child of Damage (depth 1)
```

### Hierarchy Queries

Every tag knows its parent. This enables three types of queries:

**IsDescendantOf** — "Is this tag a child/grandchild/etc. of another?"
```csharp
var fire = GameTags.Damage.Fire_Tag;
var damage = GameTags.Damage.Damage_Tag;
var aoe = GameTags.Damage.Fire.AOE_Tag;

fire.IsDescendantOf(damage);    // true — Fire is under Damage
aoe.IsDescendantOf(damage);     // true — AOE is under Damage (via Fire)
aoe.IsDescendantOf(fire);       // true — AOE is directly under Fire
damage.IsDescendantOf(fire);    // false — Damage is above Fire
fire.IsDescendantOf(fire);      // true — a tag is a descendant of itself
```

**IsAncestorOf** — "Is this tag a parent/grandparent/etc. of another?"
```csharp
damage.IsAncestorOf(fire);      // true
damage.IsAncestorOf(aoe);       // true
fire.IsAncestorOf(damage);      // false
```

**IsSiblingTo** — "Do these tags share the same parent?"
```csharp
var fire = GameTags.Damage.Fire_Tag;
var ice = GameTags.Damage.Ice_Tag;
var player = GameTags.Character.Player_Tag;

fire.IsSiblingTo(ice);           // true — both under Damage
fire.IsSiblingTo(player);        // false — different parents
```

### Practical Use Cases

**Category matching** — check if something belongs to a broad category:
```csharp
// Does this entity have ANY damage tag?
if (entity.tags.ContainsDescendantOf(GameTags.Damage.Damage_Tag)) {
    ApplyDamageEffect();
}
```

**Specific matching** — check for an exact tag:
```csharp
if (entity.tags.Contains(GameTags.Damage.Fire_Tag)) {
    ApplyBurnEffect();
}
```

**Resistance system** — use hierarchy for type matching:
```csharp
// Entity is immune to all fire damage (Fire, Fire.AOE, etc.)
if (incomingDamage.IsDescendantOf(GameTags.Damage.Fire_Tag) && entity.HasResistance(GameTags.Damage.Fire_Tag)) {
    return 0;
}
```

## Tag Editor Window

Open via `Jovian` > `Tag System` > `Tag Editor...`

### Features
- **Hierarchical tree view** — tags displayed as an expandable/collapsible tree
- **Add root tags** — type a name in the "New Tag" field at the bottom and click "+ Add"
- **Add child tags** — click the green **+** button on any tag to add a child under it
- **Delete tags** — click the red **✕** button. If the tag has children, you'll be asked to confirm deletion of the entire branch
- **Search** — filter tags by name
- **Save & Generate** — saves all changes and regenerates the C# constants file
- **Vertical indent guides** — visual lines showing parent-child relationships

### Adding hierarchical tags
You can type full paths in the "New Tag" field:
```
Damage.Fire.AOE
```
This automatically creates `Damage` and `Damage.Fire` as parents if they don't exist.

Or use the **+** button on an existing tag to add a child — a popup asks for just the child name.

### Validation
Tags must:
- Start with a letter
- Contain only letters, digits, or underscores
- Not be a C# reserved keyword (`class`, `int`, `static`, etc.)

Invalid names are rejected with an error message. Duplicates are detected and shown as warnings.

## Using Tags in Code

### Generated Constants
After clicking **Save & Generate**, use the generated constants:

```csharp
using Jovian.TagSystem;

public class Projectile : MonoBehaviour {
    public void OnHit(JovianTag targetTag) {
        if (targetTag.IsDescendantOf(GameTags.Character.Enemy.Enemy_Tag)) {
            DealDamage();
        }
    }
}
```

### JovianTag Struct
The core type — 8 bytes, zero heap allocation:

```csharp
JovianTag tag = GameTags.Damage.Fire_Tag;

tag.IsValid();                    // true (not the empty/None tag)
tag.IsNone();                     // false
tag.Id;                           // unique int identifier
tag.ParentId;                     // parent's int identifier (0 = root)
tag.ToString();                   // "Damage.Fire" (when manager is initialized)
tag.IsDescendantOf(otherTag);     // hierarchy query
tag.IsAncestorOf(otherTag);       // hierarchy query
tag.IsSiblingTo(otherTag);        // same parent check
tag == otherTag;                  // equality by ID
```

## Inspector Integration

### JovianTagsGroup
Use `JovianTagsGroup` on any MonoBehaviour or ScriptableObject to select tags in the inspector:

```csharp
public class Enemy : MonoBehaviour {
    public JovianTagsGroup tags;

    private void Start() {
        // Check tags at runtime
        if (tags.ContainsDescendantOf(GameTags.Character.Enemy.Enemy_Tag)) {
            Debug.Log("This is an enemy!");
        }

        // Convert to a runtime container for efficient repeated queries
        var container = tags.ToContainer();
    }
}
```

### Inspector Display
- **Collapsible** — shows `Tags (3)` when collapsed, full list when expanded
- **Tag list** — each tag shown with a red ✕ remove button
- **+ Add Tags** button — opens the tag picker popup
- **Edit Tags** button — opens the Tag Editor window

## Tag Picker Popup

The picker popup opens when you click **+ Add Tags** in the inspector. Features:

- **Hierarchical tree with checkboxes** — check/uncheck any tag at any depth
- **Search** — filter by name
- **+all / -all buttons** — bulk select/deselect all children of a parent tag
- **Already-selected tags** are pre-checked when the popup opens
- **Confirm / Cancel** — apply or discard the selection
- **Edit Tags** button — jump to the Tag Editor window

## Code Generation

### How It Works
The Tag Editor generates a C# file with:
1. **Nested static classes** mirroring the tag hierarchy
2. **Static `JovianTag` fields** for each tag (postfixed with `_Tag`)
3. **`[RuntimeInitializeOnLoadMethod]`** that registers all tags and resolves the fields at startup
4. **`[InitializeOnLoadMethod]`** (editor-only) for editor play mode

### Manual Edits
You can add fields by hand to the generated file. They will be preserved on regeneration as long as the file compiles.

### Regenerate Without the Window
Use `Jovian` > `Tag System` > `Regenerate Tag Constants` to regenerate from the menu without opening the Tag Editor.

### Settings Inspector
The `JovianTagsSettings` ScriptableObject inspector has:
- Default array editor for direct tag editing
- **Save & Generate Tags** button
- **Open Tag Editor** button
- Validation errors shown inline (invalid names, duplicates)

## Tag Containers

### JovianTagsContainer (tags only)
A runtime collection for holding and querying multiple tags:

```csharp
var container = new JovianTagsContainer(4);
container.Add(GameTags.Damage.Fire_Tag);
container.Add(GameTags.Status.Burning_Tag);

container.Contains(GameTags.Damage.Fire_Tag);                  // true
container.ContainsDescendantOf(GameTags.Damage.Damage_Tag);    // true
container.ContainsAncestorOf(GameTags.Damage.Fire.AOE_Tag);    // true
container.ContainsSibling(GameTags.Damage.Ice_Tag);            // true (Fire and Ice are siblings)
container.Count;                                                // 2

container.Remove(GameTags.Damage.Fire_Tag);
container.Clear();
```

### From JovianTagsGroup
Convert a serialized selection to a runtime container:
```csharp
public JovianTagsGroup selectedTags;

void Start() {
    var container = selectedTags.ToContainer();
    // Use container for efficient queries
}
```

## Tag Containers with Data

### JovianTagsContainer\<T\> (tags + values)
Pair tags with typed data — like a dictionary with hierarchy-aware queries:

```csharp
// Damage resistances
var resistances = new JovianTagsContainer<float>(4);
resistances.Add(GameTags.Damage.Fire_Tag, 0.5f);    // 50% fire resistance
resistances.Add(GameTags.Damage.Ice_Tag, 0.75f);     // 75% ice resistance

// Exact lookup
if (resistances.TryGetValue(GameTags.Damage.Fire_Tag, out float fireRes)) {
    Debug.Log($"Fire resistance: {fireRes}");  // 0.5
}

// Hierarchy query — do I resist ANY damage type?
resistances.ContainsDescendantOf(GameTags.Damage.Damage_Tag);  // true

// Get all resistances under a parent
var results = new List<TagEntry<float>>();
resistances.GetByAncestor(GameTags.Damage.Damage_Tag, results);
// results contains Fire(0.5) and Ice(0.75)
```

### TagEntry\<T\>
Each entry in a generic container:
```csharp
TagEntry<float> entry = ...;
entry.Tag;                              // the JovianTag
entry.Value;                            // the float value
entry.IsDescendantOf(someAncestor);     // hierarchy query on the tag
entry.Is(someTag);                      // exact match
```

## Runtime API Reference

### JovianTagsHandler (static)
The central tag registry. Initialized automatically by the generated code.

| Method | Description |
|--------|-------------|
| `Initialize()` | Reset and initialize the registry |
| `RegisterTag(string)` | Register a dot-delimited tag and all parents |
| `GetTag(string)` | Get tag by full name |
| `GetTag(int)` | Get tag by ID |
| `TryGetGameTag(string, out JovianTag)` | Safe lookup by name |
| `TagToString(JovianTag)` | Get the full name of a tag |
| `DisplayName(string)` | Get the last segment (`"Damage.Fire"` → `"Fire"`) |
| `IsInitialized` | Whether the registry is ready |

### JovianTag (struct, 8 bytes)

| Member | Description |
|--------|-------------|
| `Id` | Unique integer identifier |
| `ParentId` | Parent's ID (0 = root) |
| `IsValid()` | Not the empty tag |
| `IsNone()` | Is the empty tag |
| `IsDescendantOf(tag)` | Hierarchy: child/grandchild check |
| `IsAncestorOf(tag)` | Hierarchy: parent/grandparent check |
| `IsSiblingTo(tag)` | Same parent check |
| `==`, `!=`, `Equals()` | Equality by ID |

### JovianTagsGroup (serializable struct)

| Member | Description |
|--------|-------------|
| `tags` | `string[]` of tag names (serialized) |
| `ToContainer()` | Resolve to `JovianTagsContainer` |
| `Contains(tag)` | Check if any tag matches |
| `ContainsDescendantOf(tag)` | Hierarchy query |
| `ContainsAncestorOf(tag)` | Hierarchy query |
| `HasAny()` | True if any tags selected |

## Performance

- **JovianTag** — 8 bytes (`int id` + `int parentId`), no heap allocation
- **Hierarchy queries** — O(depth) parent chain walk via `JovianTagsHandler.GetTag(int)` dictionary lookup. Typical depth is 1-4 hops.
- **Equality** — O(1) integer comparison
- **Container queries** — O(n) linear scan, optimal for typical small tag counts (<10 per entity)
- **ToString** — uses cached static `StringBuilder` to avoid per-call allocation
- **Registration** — uses `ReadOnlySpan<char>` for segment parsing to minimize string allocations
