# Cross-Session Data Sharing

MudBringer lets you run multiple characters simultaneously in the same profile. With **Cross-Session Data Sharing**, those characters can transparently read and write each other's variables, lists, and arrays â€” no manual piping, ask/answer scripts, or heavyweight setup required.

---
## Overview: Where Does Data Live?

MudBringer organizes data into three tiers, each with a different lifetime:

| Tier | Example | Lifetime |
|---|---|---|
| **Local** | `$myhp` | Dies when the session closes |
| **Shared** | `$shared.tradedata` | Lives for the entire app session |
| **Cross-Session** | `$Baldy.myhp` | Lives as long as that character's session is open |

On top of these, **Group Storage** provides variables, lists, and arrays scoped to delegation groups. And the **Poll Suite** lets you aggregate data across all characters in a group â€” with full support for group expressions, deferred evaluation, and overlap control.

---
## Local Variables (Existing)

These work exactly as they always have. A variable set on one session is private to that session:

```
/var {myhp} {500}
/showme {My HP is $myhp}
```

Nothing changes here. Every existing script works as before.

---
## Shared Variables

Shared variables are prefixed with `shared.` and live for the entire time MudBringer is running. They survive individual sessions being opened or closed.

**Setting a shared variable:**
```
/var {shared.bestroute} {Rivercross-to-Trellborg}
/var {shared.profitmargin} {45000}
```

**Reading a shared variable:**
```
/showme {Best trade route: $shared.bestroute ($shared.profitmargin gold)}
```

**Why use shared?** Imagine you run a complex trade route analysis on one character that takes 10 minutes of crunching. If you store the results in local variables (`$bestroute`), they vanish when that character disconnects. If you store them in `$shared.bestroute`, they persist â€” any character you log in later can read them, dashboards can display them, and they remain available until you close MudBringer.

Shared lists and arrays work the same way:
```
/listadd {shared.traderoutes}
/itemadd {shared.traderoutes} {Rivercross-to-Trellborg}
/itemadd {shared.traderoutes} {Medievia-to-Trellborg}
/showme {Route count: @GetCount(shared.traderoutes)}
/showme {First route: @GetItem(shared.traderoutes, 1)}
```

---
## Cross-Session Variables

You can read any other active session's variables by prefixing with their **Session Name**:

```
/showme {Baldy's HP: $Baldy.myhp}
/showme {Blindy's location: $Blindy.currentzone}
```

Session resolution matches by **Session Name** first, then **Character Name** as a fallback (case-insensitive). Since the wizard defaults Session Name to match Character Name, this works seamlessly in most cases.

> [!IMPORTANT]
> **Dot (`.`) Character Restriction**: Because the dot character is used as the structural hierarchy delimiter for cross-session lookups and dashboard targeting, neither Session Names nor Character Names can contain dots (`.`). This is strictly enforced by application validators upon creation and rename.

**Setting a remote variable:**
```
/var {Baldy.alert} {You're being attacked!}
```

This writes directly to Baldy's local `$alert` variable. If Baldy has an action that watches `$alert`, it fires immediately.

**Cross-session lists and arrays** work through existing procedures:
```
@GetItem(Baldy.inventory, 3)          â†’ item 3 from Baldy's "inventory" list
@GetCount(Baldy.inventory)            â†’ number of items in Baldy's "inventory" list
@GetArray(Baldy.stats, 1, 2)          â†’ stats[1,2] from Baldy's engine
@InList(Baldy.inventory, Longsword)   â†’ "1" if Baldy has a Longsword, "0" if not
```

---
## Group Storage

Group-level variables, lists, and arrays are scoped to a **delegation group** (A, B, C, D, or custom). They live at the application level â€” no single session owns them, and they survive individual sessions being opened or closed.
### Group Variables

**Setting and reading:**
```
/groupvar {A} {healingtarget} {Jimmy}

cast heal @GroupVar(A, healingtarget)
â†’ cast heal Jimmy
```

**Listing and removing:**
```
/groupvars {A}                    â†’ shows all group A variables
/ungroupvar {A} {healingtarget}   â†’ removes the variable
```
### Group Lists

```
/grouplistadd {A} {targets}
/groupitemadd {A} {targets} {Baldy}
/groupitemadd {A} {targets} {Blindy}
/showme {Targets: @GroupCount(A, targets) members}
/showme {First target: @GroupItem(A, targets, 1)}
/groupclearlist {A} {targets}
/grouplistdelete {A} {targets}
```
### Group Arrays

```
/grouparray {A} {formation} {5,5}
/grouparrayset {A} {formation} {1,1} {Tank}
/grouparrayset {A} {formation} {1,2} {Healer}
/showme {Position 1,1: @GroupArray(A, formation, 1, 1)}
```

---
## The Poll Suite

The most powerful multiplay feature. Poll procedures let you **aggregate data across all sessions in a delegation group** in real time.
### Basic Polls

| Procedure | What it does |
|---|---|
| `@Poll(Group, var)` | Returns every session's value as CSV |
| `@PollMin(Group, var)` | Returns the lowest numeric value |
| `@PollMax(Group, var)` | Returns the highest numeric value |
| `@PollMed(Group, var)` | Returns the median value |
| `@PollAvg(Group, var)` | Returns the average value |
| `@PollTotal(Group, var)` | Returns the sum of all values |

**Example:** Three characters in group A have `$myhp` set to 450, 320, and 180:
```
@Poll(A, myhp)       â†’ "450,320,180"
@PollMin(A, myhp)    â†’ "180"
@PollMax(A, myhp)    â†’ "450"
@PollMed(A, myhp)    â†’ "320"
@PollAvg(A, myhp)    â†’ "316"
@PollTotal(A, myhp)  â†’ "950"
```
### PollWho â€” Finding a Specific Session

`@PollWho` tells you **which session** has a particular value:

| Mode | Syntax | Returns |
|---|---|---|
| Lowest | `@PollWho(A, myhp, min)` | `"Baldy"` |
| Highest | `@PollWho(A, myhp, max)` | `"Jimmy"` |
| Median | `@PollWho(A, myhp, med)` | `"Blindy"` |
| Below threshold | `@PollWho(A, myhp, below, 200)` | `"Baldy"` (CSV if multiple) |
| Above threshold | `@PollWho(A, myhp, above, 400)` | `"Jimmy"` (CSV if multiple) |
| Exact match | `@PollWho(A, stance, match, sitting)` | `"Baldy,Blindy"` (CSV if multiple) |

If no session matches, all PollWho modes return an empty string.

**Practical example â€” an auto-healer script:**

```
/var {target} {@PollWho(Tanks, myhp, min)}
/if {$target != ""} {cast heal $target}
```

This finds whoever has the lowest HP in the "Tanks" group and heals them. No ask/answer, no piping â€” one line.
### The ALL Group

There's a special group called `ALL` that **always** contains every active session, regardless of their delegation group settings. You never need to configure it.

```
@Poll(ALL, myhp)              â†’ HP from every character
@PollWho(ALL, myhp, min)      â†’ who has the lowest HP across all characters
@PollTotal(ALL, gold)          â†’ total gold across all characters
```

---
## Group Expressions

What if you need to poll **more than one group**, or exclude specific groups or people? Every poll procedure accepts a **group expression** with `+` (union) and `-` (exclusion) operators.
### Basic Combinations

```
@PollMin(A+B, myhp)         â†’ lowest HP across groups A and B combined
@PollMin(ALL-C, myhp)       â†’ lowest HP across everyone EXCEPT group C
@PollTotal(A+B+D, gold)     â†’ total gold across groups A, B, and D
```
### Including/Excluding Specific Sessions

You can also use **session names** in group expressions to pin specific people in or out:

```
@Poll(A+Jimmy, myhp)          â†’ group A, plus Jimmy no matter what
@Poll(ALL-Suzie, myhp)        â†’ everyone except Suzie specifically
@Poll(A-B+Jimmy-Suzie, myhp)  â†’ group A, minus group B, ensure Jimmy is in, ensure Suzie is out
```
### How It Works

The expression is evaluated **left to right**. Each term is resolved in this order:

1. **Reserved keyword** â€” Is it `ALL`? â†’ every active session
2. **Session name** â€” Does it exactly match a session? â†’ that single session
3. **Group name** â€” Does it match a delegation group? â†’ all sessions in that group

Then the `+` or `-` operator applies:

| Step | Token | Resolution | Operation | Set Contents |
|---|---|---|---|---|
| 1 | `A` | Group â†’ `{Alice, Bob, Carol, Jimmy, Suzie}` | Union | `{Alice, Bob, Carol, Jimmy, Suzie}` |
| 2 | `-B` | Group â†’ `{Bob, Carol}` | Subtract | `{Alice, Jimmy, Suzie}` |
| 3 | `+Jimmy` | Session â†’ `{Jimmy}` | Union | `{Alice, Jimmy, Suzie}` |
| 4 | `-Suzie` | Session â†’ `{Suzie}` | Subtract | `{Alice, Jimmy}` |
### Overlapping Group Membership

A person who belongs to **multiple groups** gets removed entirely when any of their groups is subtracted:

```
@Poll(ALL-B, myhp)
```

If Carol is in both groups B and C, she gets removed by `-B`. Being in group C does not save her. If you want her back because of group C, add it explicitly:

```
@Poll(ALL-B+C, myhp)
```

This removes everyone in B, then adds everyone in C back â€” including Carol.
### Name Collisions

Session names are checked **before** group names. If you have both a session named "Suzie" and a group named "Suzie", the token "Suzie" resolves to the session as long as she's logged in. If she logs off, it falls through to the group. You can always change a session's name (independently of the character name) to avoid collisions.

---
## Advanced: Deferred Expression Polling

Here's where it gets powerful. What if you don't want to poll a simple variable â€” you want to poll a **computed value** from each session?

Wrap the expression in **braces** `{}`:

```
@PollMax(A, {@GetCount(inventory)})
```

This does NOT evaluate `@GetCount(inventory)` on your local session. Instead, it sends the expression to **each session in group A**, evaluates it on **their** engine with **their** data, and returns the maximum result.

**How it works:** MudBringer's scripting parser already treats braces as "deferred evaluation" â€” content inside braces is passed through without being resolved. The poll procedure strips the braces and evaluates the expression in each remote session's context.

**More examples:**

```
@PollMax(A, {@math($myhp - $mana)})
â†’ For each session: compute HP minus mana. Return the highest result.

@PollWho(A, {@GetCount(inventory)}, max)
â†’ Which session has the most items in their inventory?

@PollTotal(ALL, {@math($myhp * 100 / $maxhp)})
â†’ Sum of everyone's HP percentage

@PollWho(ALL, {@GetCount(kills)}, max)
â†’ Who has the most kills?

@PollWho(A+B-C, {@math($myhp * 100 / $maxhp)}, below, 50)
â†’ Who in groups A and B (but not C) is below 50% HP?
```
### Deferred vs. Non-Deferred

Without braces, the expression is evaluated **locally first**:

```
@PollMax(A, @GetCount(inventory))
```

This evaluates `@GetCount(inventory)` on YOUR engine (returning, say, `5`), then calls `@PollMax(A, 5)` â€” which tries to read a variable called `5` from each session. That's probably not what you want.

**Rule of thumb:** If the second argument to a poll procedure contains `@` or `$`, wrap it in braces.

Use case for local evaluation:

```
@PollWho(A+B-C, {@math($myhp * 100 / $maxhp)}, below, $myhppercent)
```

This evaluates `$myhppercent` on YOUR engine (returning, say, `65`), then calls `@PollWho(A+B-C, {@math($myhp * 100 / $maxhp)}, below, 65)` on each remote session. This lets you evaluate your local variables in poll expressions.

**Rule of thumb:** `{ }` braces mean "defer this â€” evaluate on each REMOTE session's engine." No braces means "evaluate on YOUR engine before the poll runs."

---
## Building a Multiplay Dashboard

Here's a complete example: a dashboard on your main character that shows stats from all characters.

**Status bar items:**
```
/baritem {hp_range} {HP: @PollMin(A, myhp)/@PollMax(A, myhp)} {1} {10}
/baritem {gold} {Gold: @PollTotal(ALL, gold)} {1} {40}
/baritem {lowest} {Heal: @PollWho(A, myhp, min)} {1} {60}
```

**Auto-heal action:**
```
/action {^Your group is under attack!} {cast group heal} {A}
/action {^Someone is bleeding!} {/var {target} {@PollWho(ALL-C, myhp, min)};cast heal $target}
```

The second action heals the lowest-HP person who ISN'T in group C (the tanks who can take care of themselves).

**Trade route storage (survives session changes):**
```
/var {shared.bestroute} {Rivercross-to-Trellborg}
/showme {Best route is $shared.bestroute}
```

**Group coordination:**
```
/groupvar {A} {formation} {defensive}
/showme {All of group A is now in @groupvar(A, formation) formation}
```

---
## Quick Reference
### Variable Syntax
| Syntax | Meaning |
|---|---|
| `$varname` | Local variable |
| `$shared.varname` | Global shared variable |
| `$SessionName.varname` | Another session's variable |
### Group Expression Syntax
| Expression | Meaning |
|---|---|
| `A` | All sessions in group A |
| `ALL` | Every active session |
| `A+B` | Union of groups A and B |
| `ALL-C` | Everyone except group C |
| `A-B+Jimmy` | Group A, minus group B, plus Jimmy specifically |
| `ALL-B+C-Suzie` | Everyone, minus B, plus C back, minus Suzie specifically |
### Commands
| Command | Description |
|---|---|
| `/var {shared.X} {val}` | Set shared variable |
| `/var {Session.X} {val}` | Set remote session variable |
| `/groupvar {G} {key} {val}` | Set group variable |
| `/groupvars {G}` | List group variables |
| `/ungroupvar {G} {key}` | Remove group variable |
| `/grouplistadd {G} {name}` | Create group list |
| `/groupitemadd {G} {name} {item}` | Add to group list |
| `/groupitemdelete {G} {name} {item}` | Remove from group list |
| `/groupclearlist {G} {name}` | Clear group list |
| `/grouplistdelete {G} {name}` | Delete group list |
| `/grouparray {G} {name} {r,c}` | Create group array |
| `/grouparrayset {G} {name} {r,c} {val}` | Set group array cell |
### Procedures
| Procedure | Description |
|---|---|
| `@GroupVar(G, key)` | Read group variable |
| `@SetGroupVar(G, key, val)` | Write group variable |
| `@GroupItem(G, list, idx)` | Read group list item |
| `@GroupCount(G, list)` | Count group list items |
| `@GroupArray(G, arr, r, c)` | Read group array cell |
| `@GroupInList(G, list, val)` | Check group list membership |
| `@Poll(G, var)` | All values as CSV |
| `@PollMin(G, var)` | Minimum value |
| `@PollMax(G, var)` | Maximum value |
| `@PollMed(G, var)` | Median value |
| `@PollAvg(G, var)` | Average value |
| `@PollTotal(G, var)` | Sum of all values |
| `@PollWho(G, var, mode, ...)` | Session name(s) by criteria |
