Mawu · Architecture

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

A focused page within the Mawu Architecture documentation. The full map and every sibling page live in the Architecture hub.

6sections12 minread1diagram

On this page

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.

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 daemonhera-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 MoremiNanaLedgerEntrys. 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.

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.

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 HeraGroupRoles (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, 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 — 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.