# Pheme Voice & Hera Social Graph

Two things turn a server full of strangers into a place. The first is being able
to **hear** the person standing next to you — to whisper a deal across a market
stall, to key a radio and have only the dispatcher's net carry it, to take a
phone call from across the map. The second is **belonging** — a crew with a
name, a treasury, officers and recruits, a reputation that a whitelisted realm
reads at its door, and an identity that survives when its members cross into
another world entirely. V7 ("Mawu") names those two planes for two Greek
goddesses: **Pheme**, the carrying, spreading voice, and **Hera**, goddess of
alliance and organized society. This page is the feature tour of both.

Pheme is the positional, proximity-attenuated voice that immersive roleplay
requires — the pma-voice model FiveM made the genre standard — layered with
radio nets, phone calls, and the accessibility and child-safety guarantees the
platform never delegates to a realm. Hera is the persistent player social graph:
crews, guilds, and organizations as first-class, cross-realm, cross-version
aggregates with role-gated treasuries, an audit ledger, a reputation whitelist
gate, and an Ori-passport bridge that carries a group into V2–V6. Both are
grounded in real, test-driven code, and this page follows the code where the
prose runs ahead of it.

The authority that validates every Hera treasury debit and that hosts the radio
nets lives in [./moremi-roleplay-framework.md](./moremi-roleplay-framework.md);
the way a crew travels into other Oshun games — and the streamer-mode redaction
that protects a broadcasting player — lives in
[./streaming-incarnation-and-live-service.md](./streaming-incarnation-and-live-service.md);
the engineering companion that owns the data-model and voice-DSP detail is
[../architecture/nana-data-model-and-pheme-voice.md](../architecture/nana-data-model-and-pheme-voice.md).
For the full V7 scope this slots into, start at the hub:
[../V7_features.md](../V7_features.md).

## What ships, honestly

These two planes are real, test-covered code, but they ship at three different
levels of assembly, and one of them — Pheme — is _more_ implemented than the
architecture companion's quick summary lets on. The honest picture:

- **Real and test-covered.** Pheme has **two** genuine evaluators. The
  client-side mix is `UMawuVoiceMixLibrary::EvaluateVoiceEmitter`
  (`V7/ue/Source/MawuVoice/Private/MawuVoiceMix.cpp`) — real UE C++ distance
  falloff, radio floor, and equal-power panning. The server-side audibility,
  admission, and captioning model is real Rust in `apps/v7/mawu-gateway`
  (`src/lib.rs`): proximity gain with **ray-vs-AABB occlusion**, scriptable
  whisper/normal/shout ranges, radio channel admission (PTT / squelch / role /
  on-duty), proximity-independent phone, a hearing-impaired caption projection,
  and a platform voice-safety tap that **ignores a realm's request to disable
  it** — four of the gateway's **16 `#[test]` cases** pin these. Hera is a real
  **1,085-line** social graph (`apps/v7/hera-social-service/src/lib.rs`) with
  role-gated audited treasury withdrawals, a cross-realm reputation whitelist
  gate, an Ori group-presence passport with a continuity hash, and a
  platform-ban + device-attestation ban-evasion eval (**7 tests**).
- **Library-grade, not a running daemon.** Both `mawu-gateway` (owner "Mawu",
  port `47204`) and `hera-social-service` (owner "Hera", port `47206`) expose
  their behaviour as libraries plus a **health-only daemon** — each `main()`
  calls `run_service()`, which binds the shared `service_contract` health server
  and answers one route. The audibility/admission model and the social graph are
  exercised by **evals and tests**, not by a live socket mixing Opus packets or
  serving group mutations.
- **Spec-level where it is genuinely reused or deferred.** The **codec and SFU**
  themselves — Opus over the WebRTC SFU — are the reused **V6 Egbe-Gateway**
  transport, not re-implemented in V7; the gateway's own comment says "the SFU
  transport itself (V6) is out of scope; this is the audibility/admission/
  captioning model the SFU is driven from." The Pheme scale budget ("**≤40
  audible streams per listener**" nearest-N AoI culling, "≤2 cores per 256
  speakers", "p95 ≤250 ms mouth-to-ear") is a written launch target — the
  audibility evaluator runs **per speaker-listener pair**, with no nearest-N
  selection yet. And Hera group kinds are `Crew | Guild | Organization` (the
  monolith's "gang"/"posse"/"org" are flavour, not enum variants).

## Pheme — proximity and radio voice

### Two evaluators, one falloff model

Pheme computes "what can this listener hear from that speaker" in two honest
places. On the **client**, the Mawu UE module ships
`UMawuVoiceMixLibrary::EvaluateVoiceEmitter`, a `UBlueprintFunctionLibrary`
(deps `Core` / `CoreUObject` / `Engine` only, per `MawuVoice.Build.cs`) that
takes an `FMawuVoiceListener` (location, forward, `HearingRadiusCm` default
**3200**, the `RadioChannels` it monitors) and an `FMawuVoiceEmitter` (location,
`VoiceRadiusCm` default **2400**, `SpokenLevelDb` default **−12**,
`bRadioTransmitting`, `RadioChannel`) and returns an `FMawuVoiceMixResult`. The
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)`. Panning
is equal-power-shaped — the listener's right vector is `cross(Up, Forward)`, the
pan is `dot(directionToEmitter, right)` clamped to [−1, 1] — and `bAudible`
trips only above a `0.001` linear-gain floor.

On the **server**, `pheme_evaluate_audibility` (`mawu-gateway/src/lib.rs`) does
the same falloff but adds two things the client evaluator does not: **scriptable
voice modes** and **occlusion**. `PhemeVoiceMode` gives whisper / normal / shout
distinct effective ranges of **300 / 1,800 / 5,000 cm**; the distance gain is
the same `(1 − alpha)²` quadratic; and a `PhemeOccluder` (an axis-aligned box
with an `attenuation_db`) is tested by `pheme_segment_hits_occluder`, a real
**ray-vs-AABB slab intersection** along the speaker→listener segment. Each
occluder the segment crosses adds its dB, converted back to a linear factor as
`10^(−occlusion_db/20)`, and the received level is
`source_level_db + 20·log10(gain)`, floored at
`PHEME_INAUDIBLE_FLOOR_DB = −120`. The eval `run_pheme_proximity_eval` asserts
the properties that matter: gain is **monotonically decreasing** with distance,
a voice is **silent beyond range**, an **occluded** path is quieter than an
unoccluded one at the same distance, and `whisper < normal < shout` range
ordering holds.

### Radio nets and phone calls

Radio is a parallel, distance-_independent_ path. On the client, when the
emitter transmits on a non-empty channel the listener monitors,
`EvaluateVoiceEmitter` floors the gain at **0.18** regardless of distance and
pulls the pan toward centre (`Pan *= 0.25`) — the familiar "in your head" radio
feel. On the server, `pheme_evaluate_radio_transmit` decides **admission** onto
a channel. `PhemeRadioChannel` carries an optional `required_role`, a
`require_on_duty` flag (for dispatch nets), and a `squelch_threshold`; the
result is a `PhemeRadioAdmission` that is one of `Admitted`, `PttNotKeyed`,
`NotChannelMember`, `NotRoleAuthorized`, `NotOnDuty`, or `SquelchedWeakSignal`.
The `run_pheme_radio_phone_eval` fixture is a police dispatch net: an **on-duty
dispatcher is admitted**, an off-duty dispatcher is `NotOnDuty`, a civilian is
`NotRoleAuthorized`, a released push-to-talk is `PttNotKeyed`, and a −110 signal
under a −90 squelch is `SquelchedWeakSignal`. Phone is
`pheme_connect_phone_call` — `proximity_independent: true`, point-to-point or
`is_conference` for more than two — and the eval connects two endpoints **50,000
cm apart** (far beyond shout range) to prove distance never gates a call.

That dispatch radio net is also where Pheme meets the Ixchel audio-mod model in
the realm server. A police radio is declared as a
`MoremiForgeAudioRoute("radio.police", "dispatch-radio", "voice")`
(`apps/v7/moremi-realm-server/src/lib.rs`), and the `forge-conflict` resolver
keeps it on an **independent mix bus** from a `music.club` route ("club-music",
`music`) — while flagging a genuine `dispatch-radio<->radio-hijack`
contradiction when a hostile pack tries to seize the police channel onto the
wrong bus. Radio channels are not just a voice routing concept; they are a named
audio resource the composition layer protects.

### Voice on the wire, and the safety tap the realm cannot turn off

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

Wrapped around the mix is a platform-owned safety tap the realm operator cannot
remove. `MawuVoiceSafetyPolicy::platform_default()` sets
`platform_tap_required: true`, `realm_delegation_allowed: false`, and a route
threshold of **7,000 basis points** into the `kuanyin-human-review` queue.
`screen_voice_segment` scores a segment on direct-audio-grooming, coercive-tone,
distress, and minor-context signals, and — critically — when a realm sets
`realm_requested_tap_disabled`, the decision records
`realm_disable_request_ignored: true` and screens anyway. This is the
trust-boundary posture in miniature, proven by
`platform_voice_safety_tap_routes_flagged_segments_and_ignores_realm_disable`.
Child-safety reaches further: Sekhmet makes `ProximityVoice` a
`MinorProtectionFeature` (`apps/v7/sekhmet-scanner/src/lib.rs`) and screens a
`VoiceGroomingSignal` endangerment kind on a tight **2,400 ms** SLA.

### Accessibility parity

No voice surface ships without a text/visual equivalent. On the server,
`pheme_build_accessibility_view` projects live `PhemeVoiceEvent`s into a
`PhemeAccessibilityView`: for a hearing-impaired profile with captions on, every
event becomes a caption line carrying **who** (speaker), **which channel**
(proximity / radio / phone + id), and **where** (distance + direction for
proximity, "off-proximity" for radio/phone), plus distinct **speaker and channel
indicators**. A `parental_mute` profile suppresses _all_ surfaced voice and
emits no captions at all. The contract layer enforces the same bar statically:
`lintVoiceRadioParity` (`libs/v7/contracts/src/accessibility-eval.ts`) requires
captions, transcript, and speaker / range / channel / mute-state visual
indicators for both proximity and radio voice, emitting
`voice_radio_parity_missing:<field>` against the
`voice-radio-text-visual-parity` rule when a surface omits one. The mix
evaluator decides _what you hear_; the accessibility view and the parity linter
decide _what the platform must guarantee around it_.

## Hera — crews, guilds, and the persistent social graph

### The group aggregate

A Hera group is a real aggregate, not a join table. `HeraGroup` carries a `kind`
(`Crew | Guild | Organization`), a `home_realm_id`, a map of `HeraGroupRole`s
(each a permission set drawn from `TreasuryDeposit`, `TreasuryWithdraw`,
`SharedAssetManage`, `ReputationRead`), members with their assigned `role_ids`,
a `HeraTreasuryAccount` (an `aje_account_id` plus a `balance_cents`), shared
assets, and a `cross_realm_reputation_score`. Everything a group can do is
mediated by `actor_has_permission`, which walks a member's roles and checks the
requested permission — there is no "owner can do anything" shortcut.

```mermaid
flowchart TD
    G["HeraGroup<br/>kind: Crew | Guild | Organization<br/>home_realm_id · cross_realm_reputation_score"]
    G --> R["Roles → permissions<br/>TreasuryWithdraw · SharedAssetManage<br/>TreasuryDeposit · ReputationRead"]
    R --> M["Members → role_ids"]
    M --> PERM{"actor_has_permission?"}
    PERM -->|TreasuryWithdraw| T["withdraw_from_treasury<br/>debit Aje treasury OR deny<br/>→ append HeraGroupAuditEntry"]
    PERM -->|SharedAssetManage| A["publish_shared_asset"]
    G --> WG["evaluate_cross_realm_whitelist_gate<br/>score ≥ required_minimum_score"]
    G --> P["mint_ori_group_presence_passport<br/>FNV-1a continuity_hash"]
    P --> V2["bridge_to_v2_match → HeraV2MatchGroupRoster<br/>recompute hash → tamper-detect"]
    BAN["PlatformBanLedger<br/>platform_account_id + device_attestation_id"] --> J{"evaluate_realm_join"}
    J -->|banned acct OR same device| BLK["Blocked…<br/>reputation → negative"]
```

### Role-gated, audited treasury

The treasury is the load-bearing example. `withdraw_from_treasury` checks
`actor_has_permission(TreasuryWithdraw)` and then 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, action, decision, reason, amount, and
the treasury balance _after_. `publish_shared_asset` is gated the same way on
`SharedAssetManage`. The gate `run_hera_groups_eval` only passes when a
permissionless member's 2,500-cent withdrawal is **denied** (the 25,000-cent
balance untouched), a treasurer's 7,500-cent withdrawal **succeeds** (balance →
**17,500**), a quartermaster's 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`. The
treasury balance is in `aje_account_id` terms, so a group's shared money is
settled through Aje, never minted inside the social service.

### Cross-realm reputation and the whitelist gate

Reputation is the thing a group earns in one realm that another realm reads at
its door. `evaluate_cross_realm_whitelist_gate` compares a group's
`cross_realm_reputation_score` against a target realm's `required_minimum_score`
and returns a `HeraGroupWhitelistDecision` — in the eval, a score of **82**
clears a required **50**, so an established crew is admitted to a whitelisted RP
realm **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` whose parent is `'server'`, with
reputation-weighted voting, 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 crew travels into V2–V6" promise is implemented, not asserted.
`mint_ori_group_presence_passport` 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`, formatted `fnv64:…`).
`bridge_group_presence_passport_to_v2_match` then projects that passport into a
`HeraV2MatchGroupRoster`, **recomputing the same hash** so any tamper is
detectable. `run_hera_cross_version_group_presence_eval` passes 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. A "Moonlit Couriers" crew formed in a market realm can therefore
field a verified two-runner roster in a V2 ranked match with its roles intact.

### Ban propagation and device-attestation evasion

Hera closes the smurf / ban-evasion loop FiveM left open, and it is the platform
— never the realm — that owns it. `PlatformBanLedger` records a `PlatformBan`
keyed on **both** a `platform_account_id` and a `device_attestation_id`, and
`evaluate_realm_join` returns a `RealmJoinDecision` that blocks not only the
banned account (`BlockedPlatformAccountBan`) but a **fresh account joining from
the same attested device** (`BlockedDeviceAttestation`). A ban also drives the
`cross_realm_reputation_score` negative. `run_ban_evasion_eval` passes 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** — the fixture
lands in 2,000), and the reputation score is negative — pinned by
`device_attestation_blocks_fresh_account_realm_hop` and
`ban_evasion_eval_blocks_platform_ban_and_device_hop`. This is the trust
boundary's "platform owns identity and bans, realms cannot launder them" posture
made concrete in the social graph: a banned player cannot simply re-register and
realm-hop their way back in.

## Where this connects

Pheme and Hera are the **payload** the realm backbone carries: a voice signal is
a reliable-ordered frame on the realm wire, and every treasury debit is a
server-authoritative mutation. The authority that validates those writes, hosts
the radio nets, and owns the Nàná characters those voices belong to is
[./moremi-roleplay-framework.md](./moremi-roleplay-framework.md). The way a Hera
crew incarnates into V2–V6 via the Ori passport — and the streamer-mode
redaction that protects a broadcasting voice — lives in
[./streaming-incarnation-and-live-service.md](./streaming-incarnation-and-live-service.md).
The data-model and voice-DSP engineering behind everything on this page,
including the character record and economy ledger these social and voice planes
sit beside, is in the architecture companion
[../architecture/nana-data-model-and-pheme-voice.md](../architecture/nana-data-model-and-pheme-voice.md).
For the full V7 feature scope, return to the hub:
[../V7_features.md](../V7_features.md).

## Related

- [Moremi: The Server-Authoritative Roleplay Framework](./moremi-roleplay-framework.md)
  — the authority that validates every Hera mutation and hosts the Pheme radio
  nets
- [Streaming, Incarnation & Live Service](./streaming-incarnation-and-live-service.md)
  — how a crew travels into V2–V6 and how streamer-mode protects a live player
- [Nàná Data Model, Pheme Voice & Hera Social Graph](../architecture/nana-data-model-and-pheme-voice.md)
  — the engineering companion: character/economy data model and the voice-DSP
  math
- The feature hub: [../V7_features.md](../V7_features.md)
