# Nàná Data Model, Pheme Voice & Hera Social Graph

A creator republic is not just a renderer and a netcode — it is the place where
a character _persists_, where money has to stay sound across thousands of
community-run economies, where two people standing in the same square can _hear_
each other, and where a crew that earned its reputation in one realm carries it
into the next. V7 ("Mawu") names those three planes **Nàná** (the character and
economy data model), **Pheme** (the proximity-and-radio voice architecture), and
**Hera** (the social-graph data model — crews, guilds, organizations). This page
is the engineering companion to the "Nàná — Character and Economy Data Model,"
"Pheme — Voice Architecture," and "Hera — Social Graph Data Model" summaries in
the orientation hub, and it sits beside the realm-server deep dive that owns the
authority these models are written through.

The grounding is real Rust and real Unreal C++, and it is dual-located the way
V7 always is: the canonical character primitive is the `v7-nana` crate
(`libs/v7/nana/src/lib.rs`); the economy, property, civic, and deletion-limit
machinery that surrounds it is in the realm server
(`apps/v7/moremi-realm-server/src/lib.rs`); the social graph is its own
`hera-social-service` crate (`apps/v7/hera-social-service/src/lib.rs`); voice is
both a Blueprint mix library in UE (`V7/ue/Source/MawuVoice`) and a wire channel
in `libs/v7/realm-protocol`; and the bridge that writes a character into durable
storage is `libs/v7/substrate-bridge`. Read the realm-server page for how
authority and netcode work; read this for what they carry. The section hub is
[../V7_ARCHITECTURE.md](../V7_ARCHITECTURE.md).

## What ships, honestly

These three planes are real, test-driven code — but they ship at three different
levels of assembly, and the monolith's prose runs slightly ahead of the structs
in two places. The honest picture:

- **Real and test-covered.** The Nàná character record + validation
  (`libs/v7/nana`, 2 tests), the **double-entry economy ledger** that refuses to
  post an unbalanced entry, the inflation auto-balancer with a sinkless-config
  gate, the Iris-consent-gated character store with steward-not-owner deletion
  limits, property ACLs with a Danu interior-handoff probe, and the
  civic-records model are all genuine algorithms inside the realm server's **69
  `#[test]` cases**. Hera is a real **1,085-line** social graph with role-gated
  treasury withdrawals, an audit ledger, a cross-realm reputation whitelist
  gate, an Ori group-presence passport, and a device-attestation ban-evasion
  eval (**7 tests**). The MawuVoice proximity/radio mix is real UE C++ math.
- **Library-grade, not a running service.** Both `moremi-realm-server` and
  `hera-social-service` expose their behaviour as libraries plus a **health-only
  daemon** — `hera-social-service`'s `main()` calls `run_service()`, which binds
  the shared `service_contract` health server (owner "Hera", port `47206`). The
  social graph is exercised by tests and eval functions, not by a live socket
  serving group mutations.
- **Spec-level where the monolith over-claims.** Pheme's **codec and SFU** (Opus
  over WebRTC) are reused V6 Egbe-Gateway infrastructure, not re-implemented
  here; the `MawuVoice` module depends on `Core`/`CoreUObject`/`Engine` only and
  is a per-emitter gain/pan _evaluator_, not the server-side SFU mix path, and
  the "≤40 audible streams per listener" AoI budget is a written target, not
  implemented culling. And two field names drift: Hera group kinds are
  `Crew | Guild | Organization` (the monolith's "gang"/"org" are not in the
  enum), and the rich
  `Character { aliases, appearance_ref, licenses[], relationships[] }` aggregate
  is split — the core crate holds only identity, employment, and balances;
  property/civic/job aggregates live in the realm server; appearance and
  free-form relationships are not yet structs.

## Nàná — the character and economy data model

### The character record

A Nàná character is the smallest stable thing a realm owns about a player.
`NanaCharacterRecord` (`libs/v7/nana/src/lib.rs`) is deliberately minimal: an
`ori_id` (the platform identity), a `realm_id` (the owning realm — characters
are realm-scoped aggregates), a `display_name`, an optional `NanaEmployment`
(`job_id`, `grade: u16`, `on_duty`), and `NanaBalances` (`cash_minor`,
`bank_minor`, `society_minor`, all `i64` minor currency units). The crate has an
**empty `[dependencies]` block** on purpose — it is the contract Moremi,
Nephthys, and future TS bindings agree on before anything richer is built. Two
methods carry weight: `stable_key()` returns the realm-local key
`nana:{realm_id}:{ori_id}`, and `validate()` returns a typed
`NanaValidationError` for a missing Ori id, missing realm, empty display name,
or **any negative balance** — the last is the load-bearing invariant a
server-authoritative economy depends on, and `rejects_negative_balances` proves
a `cash_minor = -1` is refused. Everything that follows treats this record as
the unit it persists, employs, taxes, and (carefully) deletes.

### The double-entry economy ledger

The economy is a realm-scoped, **server-authoritative double-entry ledger**, and
"double-entry" is enforced, not decorative. `MoremiNanaEconomyLedger`
(`apps/v7/moremi-realm-server/src/lib.rs:4379`) holds typed accounts — `Cash`,
`Bank`, `Society`, `Faucet`, `Sink` (`:4267`) — and a list of
`MoremiNanaLedgerEntry`s. Each entry's `balanced()` (`:4360`) requires the entry
be non-empty, every line be a debit _xor_ a credit (never both, never negative),
**and total debits equal total credits**; `post_entry` (`:4393`) rejects an
`ImbalancedEntry` or an entry touching an `UnknownAccount` before it appends,
and `account_balance_minor` (`:4411`) folds a balance as `Σ(debit − credit)`.
Entry kinds are the real economic events — `OpeningBalance`, `Payroll`, `Fine`,
`Purchase` (`:4322`) — and `MoremiNanaEconomyPolicy` (`:4440`) computes a
purchase's `purchase_tax_minor` from `purchase_tax_basis_points` so a sale
splits into seller revenue and a tax sink. The gate
`run_moremi_nana_economy_ledger_eval` (`:5585`) only passes
(`MoremiNanaEconomyLedgerEvalReport::passed`, `:4491`) when the authoritative
writer is literally `"moremi:server-authority"`, payroll **and** fine **and**
purchase entries posted, and the whole ledger's debits equal its credits with
`imbalance_minor == 0`. Two tests pin it:
`nana_economy_ledger_eval_balances_double_entry_to_the_cent` (`:14100`) and
`nana_economy_ledger_eval_runs_payroll_fine_and_purchase` (`:14127`).

### Inflation health and the sinkless-config gate

A realm owner tunes faucets and sinks, but the platform refuses to let them
build a sink-less hyperinflationary economy by accident. The sink primitives are
first-class — `RepairDegradation`, `PropertyUpkeep`, `TransactionTax`,
`ServiceFee` (`MoremiNanaSinkPrimitiveKind`, `:4505`) — and
`MoremiNanaInflationBalancerConfig::total_enabled_sink_basis_points` (`:4559`)
sums the active drains. A `MoremiNanaInflationBand` (`:4538`) declares a target
inflation corridor (`contains`, `:4543`), and the auto-balancer nudges the
transaction-tax rate toward it, capped by `max_adjustment_basis_points` — the
Alter-Aeon dynamic-control model. The interesting violation is
`SinklessConfigNotFlagged` (`:4596`): `run_moremi_nana_inflation_balancer_eval`
(`:5945`) is required to _detect_ a sink-less configuration, proven by
`nana_inflation_balancer_eval_flags_sinkless_config` (`:14161`), while
`nana_inflation_balancer_eval_holds_fixture_within_target_band` (`:14185`) shows
a healthy fixture stays in band. Inflation rate, sink coverage, and money supply
are emitted as a `MoremiNanaEconomyHealthSnapshot` (`:4577`) — per-realm health
metrics, not afterthoughts.

### Property, civic institutions, and the steward-not-owner deletion limit

Beyond money, the realm server models the FiveM-class RP surface as Nàná
aggregates. `MoremiNanaPropertyAsset` (`:4678`) is
`Housing | Business | Vehicle` (`:4629`) with a per-subject ACL of rights —
`Enter`, `Manage`, `StoreInventory`, `OperateBusiness`, `DriveVehicle` (`:4636`)
— and an optional `MoremiNanaPersistentInterior` whose state survives via a
`persistent_state_hash`. Crucially the interior is a **Danu handoff target, not
a loading-screen instance**: `run_moremi_nana_property_eval` (`:6174`) only
passes (`:4742`) when a stranger is _denied_ entry, and the
`MoremiNanaDanuInteriorHandoffProbe` (`:4698`) shows authority moving between
mesh nodes with the client set unchanged and **zero reconnects** — the seamless
interiors the meshing page makes possible. Civic life is modelled too:
`MoremiNanaCivicInstitutionKind` is `Law | EmsFire | Government` (`:4764`), and
`MoremiNanaCivicRecordKind` (`:4771`) enumerates the real records —
`CadMdtRecord`, `Warrant`, `ChargeBooking`, `Downed`, `Revive`, `Treatment`,
`License`, `Permit`, `BusinessRegistration` — sealed by the civic resource that
owns them.

The deletion posture is the most consent-shaped piece.
`MoremiNanaCharacterStore::persist_character` (`:3899`) refuses to store a
record without a `MoremiNanaIrisConsentGrant` that
`permits(... CharacterPersistence)`, and `request_deletion` (`:3954`) makes the
**steward-not-owner** rule literal: a `RealmOwner` actor with no deletion
consent is denied with `RealmOwnerDeletionRequiresIrisConsent` and an audit line
reading `denied:iris-consent-required` — a realm owner _cannot silently erase_ a
player's character. A permitted deletion tombstones (`deleted_at`, `:3997`)
rather than hard-deleting, and every persist/load/delete appends a
`MoremiNanaCharacterAuditEntry`. `run_moremi_nana_character_deletion_limit_eval`
(`:5162`) gates the whole policy.

### Where Nàná state lives

Moremi is the authority; it is not the store. `libs/v7/substrate-bridge` is how
a character reaches durable platform storage without re-implementing it. Its
`EgbeOriFacade` (`:1248`) implements `V7OriRecordFacade::write_character_record`
(`:1277`), turning a `NanaCharacterRecord` into a `nana_character_memory_event`
(`:1760`) written through the **reused V6 Ori event store**, stamped with an
Isis provenance bundle (`provenance:isis:v7:substrate-bridge:nana`), and read
back via the V6 operator-read projection path. `authenticate_and_sync_character`
(`:1343`) guards the write behind
`V7_ORI_WRITE_REQUIRED_SCOPE = "v7:ori:write"`, and
`rejects_authenticated_principal_without_ori_write_scope` (`:2284`) proves an
authenticated-but-unscoped principal is refused. Durable realm and character
state is event-sourced in Nephthys and made seamless across nodes by Danu — see
[./danu-meshing-and-nephthys-persistence.md](./danu-meshing-and-nephthys-persistence.md).

## Pheme — proximity and radio voice

### The proximity + radio mix

Pheme's most concrete code is the UE mix evaluator.
`UMawuVoiceMixLibrary::EvaluateVoiceEmitter`
(`V7/ue/Source/MawuVoice/Private/MawuVoiceMix.cpp`) takes an
`FMawuVoiceListener` (location, forward vector, `HearingRadiusCm` default 3200,
the set of `RadioChannels` it monitors) and an `FMawuVoiceEmitter` (location,
`VoiceRadiusCm` default 2400, `SpokenLevelDb` default −12, `bRadioTransmitting`,
`RadioChannel`) and returns an `FMawuVoiceMixResult` (`bAudible`,
`bRadioRouted`, `DistanceCm`, `GainLinear`, `Pan`). The proximity math is real:
the effective radius is `min(HearingRadiusCm, VoiceRadiusCm)`, the distance
alpha is `clamp(distance / radius, 0, 1)`, the **falloff is `(1 − alpha)²`** (a
smooth inverse-square-shaped curve), and the speech level converts from decibels
with `10^(clamp(SpokenLevelDb, −80, 12) / 20)`. Radio is a parallel,
distance-_independent_ path: when the emitter is transmitting on a non-empty
channel the listener monitors, the gain is floored at **0.18** regardless of
distance. Panning is equal-power-shaped — the listener's right vector is
`cross(Up, Forward)` and the pan is `dot(directionToEmitter, right)` clamped to
[−1, 1] — and a radio-routed voice is pulled toward center (`Pan *= 0.25`), the
familiar "in your head" radio feel. `bAudible` trips only above a `0.001`
linear-gain floor.

```mermaid
flowchart TD
    L["FMawuVoiceListener<br/>location · forward · HearingRadiusCm · RadioChannels[]"] --> EV[EvaluateVoiceEmitter]
    E["FMawuVoiceEmitter<br/>location · VoiceRadiusCm · SpokenLevelDb<br/>bRadioTransmitting · RadioChannel"] --> EV
    EV --> RM{transmitting AND<br/>listener monitors<br/>this channel?}
    RM -->|radio| RG["GainLinear = max(0.18, SpeechGain)<br/>bRadioRouted = true · Pan ×= 0.25"]
    RM -->|proximity| PG["alpha = dist / min(radius)<br/>GainLinear = (1−alpha)² · SpeechGain<br/>Pan = dot(dirToEmitter, right)"]
    RG --> OUT["FMawuVoiceMixResult<br/>bAudible = GainLinear > 0.001"]
    PG --> OUT
```

### Voice on the wire

Voice negotiation is a first-class citizen of the realm protocol.
`RealmWireMessageKind::VoiceSignal` (`libs/v7/realm-protocol/src/lib.rs:966`)
routes onto the dedicated `RealmWireChannel::Voice` (`:1525`) and is
**reliable-ordered** (`:1537`) — SDP offers and answers cannot be dropped the
way an `AuthoritativeDelta` can — with the wire label `"voice-signal"` (`:1549`)
and a distinct authority scope, `"realm.voice.signal"`, separate from the
state-mutation scope (`:4378`). It is one of the six required kinds the wire
conformance harness drives through reordering and loss, so the voice signalling
path is held to the same framing contract as movement and state. The substrate
boundary names this seam explicitly: substrate-bridge tags a "V6 realtime
gateway and Pheme transport boundary." The actual media — Opus packets across
the WebRTC SFU — rides the reused V6 gateway, which is why no codec lives in the
V7 crates.

### Accessibility parity and child-safety screening

Two platform-owned guarantees wrap the mix. Accessibility parity is linted in
contracts: `V7MawuVoiceRadioParitySpec`
(`libs/v7/contracts/src/accessibility-eval.ts`) and `lintVoiceRadioParity`
require captions, transcript, and speaker/range/channel/mute-state **visual
indicators** for both proximity and radio voice, emitting
`voice_radio_parity_missing:live_captions` when a surface omits them — the
`voice-radio-text-visual-parity` rule. Child-safety is never realm-delegated:
the Sekhmet scanner makes `ProximityVoice` a `MinorProtectionFeature`
(`apps/v7/sekhmet-scanner/src/lib.rs:1117`) and screens for a
`VoiceGroomingSignal` endangerment kind on a tight (2,400 ms) SLA. The mix
evaluator decides _what you hear_; Sekhmet and the parity linter decide _what
the platform must guarantee around it_ — detailed in the trust-boundary page.

## Hera — the social-graph data model

### Groups, roles, and role-gated treasury

A Hera group is a real aggregate, not a join table. `HeraGroup`
(`apps/v7/hera-social-service/src/lib.rs:259`) carries a `kind`
(`Crew | Guild | Organization`, `:164`), a `home_realm_id`, a map of
`HeraGroupRole`s (each a permission set drawn from `TreasuryDeposit`,
`TreasuryWithdraw`, `SharedAssetManage`, `ReputationRead`, `:170`), members with
their assigned `role_ids`, a `HeraTreasuryAccount` (an `aje_account_id` plus a
`balance_cents`), shared assets, and a `cross_realm_reputation_score`. The
treasury is **role-gated and audited**: `withdraw_from_treasury` (`:439`) checks
`actor_has_permission(... TreasuryWithdraw)` (`:574`) and either denies with
`missing_treasury_withdraw_permission` / `insufficient_treasury_funds` or debits
the balance — and **every attempt, permitted or not**, appends a sequenced
`HeraGroupAuditEntry` recording the actor, decision, reason, amount, and
post-balance. `publish_shared_asset` (`:479`) is gated the same way on
`SharedAssetManage`. The gate `run_hera_groups_eval` (`:828`) only passes
(`:675`) when a permissionless member's withdrawal is denied, a treasurer's
succeeds, a shared-asset publish succeeds, and the audit ledger shows at least
two permitted and one denied entry — proven by
`groups_eval_grants_role_gated_treasury_and_cross_realm_reputation` (`:990`).

### Cross-realm reputation and the whitelist gate

Reputation is the thing a group earns that another realm reads at its door.
`evaluate_cross_realm_whitelist_gate` (`:521`) compares a group's
`cross_realm_reputation_score` against a target realm's `required_minimum_score`
and returns a `HeraGroupWhitelistDecision` (`:346`) — the mechanism by which a
whitelisted RP realm admits an established crew without re-vetting every member.
A group is also a governance tier: the Eunomia service
(`apps/v7/eunomia-governance-service/src/service.ts`) makes `'guild'` a
first-class `EunomiaGovernanceTier` (parent tier `'server'`) with treasury-spend
and reputation-weighted voting models, so a guild's charter and treasury
decisions run through the same governance plane as the realm above it.

### Group presence travels: the Ori passport

The "a group travels into V2–V6" claim is implemented as a passport mint plus a
bridge. `mint_ori_group_presence_passport` (`:538`) snapshots each member's
`role_ids` into a `HeraOriGroupPresencePassport` and stamps a **continuity
hash** — a deterministic FNV-1a fold over the group id and the per-account role
assignments (`stable_group_presence_hash`, `:619`).
`bridge_group_presence_passport_to_v2_match` (`:603`) then projects that
passport into a `HeraV2MatchGroupRoster`, recomputing the same hash so a tamper
is detectable. `run_hera_cross_version_group_presence_eval` (`:751`) passes
(`:701`) only when the V2 roster preserves the member count, every role
assignment, and a **matching continuity hash** — group identity that survives
the version boundary, the Hera analogue of the Ori passport the substrate bridge
mints for individual characters.

### Ban propagation and device-attestation evasion

Hera also closes the smurf/ban-evasion loop FiveM left open. `PlatformBanLedger`
(`:113`) records a `PlatformBan` keyed on **both** a `platform_account_id` and a
`device_attestation_id`, and `evaluate_realm_join` (`:130`) returns a
`RealmJoinDecision` that blocks not only the banned account but a _fresh_
account joining from the **same attested device**. A ban also drives the group's
`cross_realm_reputation_score` negative. `run_ban_evasion_eval` (`:711`) passes
(`:652`) only when both the original account and a fresh same-device account are
blocked, propagation lands within `V7_BAN_PROPAGATION_SLA_MS` (5,000 ms), and
the reputation score is negative — pinned by
`device_attestation_blocks_fresh_account_realm_hop` (`:1056`) and
`ban_evasion_eval_blocks_platform_ban_and_device_hop` (`:1069`). This is the
trust-boundary's "platform owns identity and bans, realms cannot launder them"
posture made concrete in the social graph.

## How it connects

These three planes are the _payload_ the realm backbone carries. Every Nàná
balance mutation and Hera treasury debit is a server-authoritative write that
only happens after the cross-trust envelope validates — the authority path,
netcode, and the determinism contract are owned by
[./moremi-realm-server-and-netcode.md](./moremi-realm-server-and-netcode.md),
and the `VoiceSignal` wire kind described above is part of that same conformance
matrix. Character records, persistent property interiors, and group rosters are
made durable and seamless by
[./danu-meshing-and-nephthys-persistence.md](./danu-meshing-and-nephthys-persistence.md)
— the Danu interior-handoff probe in the property eval is exactly the meshing
guarantee in miniature. And the economy these models settle — payouts to the
crews and creators who run a realm, plus the anti-fraud spine that keeps a
double-entry ledger honest at platform scale — is the subject of
[./abundantia-economy-firewall-and-anti-fraud.md](./abundantia-economy-firewall-and-anti-fraud.md).

## Related

- [Moremi: Realm Server & Netcode](./moremi-realm-server-and-netcode.md) — the
  authority that validates every Nàná and Hera mutation, and the wire matrix the
  voice channel rides
- [Danu Meshing and Nephthys Persistence](./danu-meshing-and-nephthys-persistence.md)
  — where character, property, and group state become durable and seamless
- [Abundantia Economy Firewall and Anti-Fraud](./abundantia-economy-firewall-and-anti-fraud.md)
  — payout rails and the anti-fraud spine around the realm economy
- The orientation hub: [../V7_ARCHITECTURE.md](../V7_ARCHITECTURE.md)
