feat: wire HermesWorld MJ assets (#374)

Co-authored-by: Aurora release bot <release@outsourc-e.com>
This commit is contained in:
Eric
2026-05-06 22:45:29 -04:00
committed by GitHub
parent b7f518b36b
commit 8b12384b37
23 changed files with 582 additions and 21 deletions

View File

@@ -0,0 +1,475 @@
{
"schemaVersion": 1,
"generatedBy": "Hermes asset wiring inventory",
"sourceRoots": [
"public/assets/hermesworld/art",
"public/assets/hermesworld/characters",
"public/assets/hermesworld/zones",
"public/assets/hermesworld/v2",
"public/assets/hermesworld/video"
],
"assetCount": 38,
"notes": [
"Inventory covers existing files only; no image generation performed.",
"Video dimensions are read from the first video stream when ffprobe is available.",
"v2 contact sheets are source/reference assets; individual runtime usage should crop or object-fit as needed."
],
"assets": [
{
"path": "public/assets/hermesworld/art/hermesworld-app-icon.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-app-icon.png",
"extension": "png",
"bytes": 1257,
"sizeLabel": "1.2 KB",
"width": 256,
"height": 256,
"durationSeconds": null,
"suggestedUseCategory": "app_icon"
},
{
"path": "public/assets/hermesworld/art/hermesworld-app-icon.svg",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-app-icon.svg",
"extension": "svg",
"bytes": 259,
"sizeLabel": "0.3 KB",
"width": 256,
"height": 256,
"durationSeconds": null,
"suggestedUseCategory": "app_icon"
},
{
"path": "public/assets/hermesworld/art/hermesworld-app-icon@2x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-app-icon@2x.png",
"extension": "png",
"bytes": 26594,
"sizeLabel": "26.0 KB",
"width": 512,
"height": 512,
"durationSeconds": null,
"suggestedUseCategory": "app_icon"
},
{
"path": "public/assets/hermesworld/art/hermesworld-app-icon@3x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-app-icon@3x.png",
"extension": "png",
"bytes": 52299,
"sizeLabel": "51.1 KB",
"width": 768,
"height": 768,
"durationSeconds": null,
"suggestedUseCategory": "app_icon"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-caduceus-badge.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-caduceus-badge.png",
"extension": "png",
"bytes": 162956,
"sizeLabel": "159.1 KB",
"width": 467,
"height": 280,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-caduceus-favicon.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-caduceus-favicon.png",
"extension": "png",
"bytes": 15211,
"sizeLabel": "14.9 KB",
"width": 160,
"height": 160,
"durationSeconds": null,
"suggestedUseCategory": "app_icon"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-caduceus.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-caduceus.png",
"extension": "png",
"bytes": 124317,
"sizeLabel": "121.4 KB",
"width": 467,
"height": 280,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-h.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-h.png",
"extension": "png",
"bytes": 15211,
"sizeLabel": "14.9 KB",
"width": 160,
"height": 160,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-horizontal.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-horizontal.png",
"extension": "png",
"bytes": 37128,
"sizeLabel": "36.3 KB",
"width": 520,
"height": 156,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-horizontal.svg",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-horizontal.svg",
"extension": "svg",
"bytes": 273,
"sizeLabel": "0.3 KB",
"width": 520,
"height": 156,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-horizontal@2x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-horizontal@2x.png",
"extension": "png",
"bytes": 137541,
"sizeLabel": "134.3 KB",
"width": 1040,
"height": 312,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-horizontal@3x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-horizontal@3x.png",
"extension": "png",
"bytes": 258461,
"sizeLabel": "252.4 KB",
"width": 1560,
"height": 468,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-stacked.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-stacked.png",
"extension": "png",
"bytes": 99870,
"sizeLabel": "97.5 KB",
"width": 360,
"height": 300,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-stacked.svg",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-stacked.svg",
"extension": "svg",
"bytes": 267,
"sizeLabel": "0.3 KB",
"width": 360,
"height": 300,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-stacked@2x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-stacked@2x.png",
"extension": "png",
"bytes": 335190,
"sizeLabel": "327.3 KB",
"width": 720,
"height": 600,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-logo-stacked@3x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-logo-stacked@3x.png",
"extension": "png",
"bytes": 640821,
"sizeLabel": "625.8 KB",
"width": 1080,
"height": 900,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-sigil.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-sigil.png",
"extension": "png",
"bytes": 15211,
"sizeLabel": "14.9 KB",
"width": 160,
"height": 160,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-sigil.svg",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-sigil.svg",
"extension": "svg",
"bytes": 253,
"sizeLabel": "0.2 KB",
"width": 160,
"height": 160,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-sigil@2x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-sigil@2x.png",
"extension": "png",
"bytes": 51261,
"sizeLabel": "50.1 KB",
"width": 320,
"height": 320,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/hermesworld-sigil@3x.png",
"directory": "public/assets/hermesworld/art",
"fileName": "hermesworld-sigil@3x.png",
"extension": "png",
"bytes": 96409,
"sizeLabel": "94.1 KB",
"width": 480,
"height": 480,
"durationSeconds": null,
"suggestedUseCategory": "brand_logo"
},
{
"path": "public/assets/hermesworld/art/social-preview-hero.jpg",
"directory": "public/assets/hermesworld/art",
"fileName": "social-preview-hero.jpg",
"extension": "jpg",
"bytes": 82326,
"sizeLabel": "80.4 KB",
"width": 1200,
"height": 630,
"durationSeconds": null,
"suggestedUseCategory": "social_share_preview"
},
{
"path": "public/assets/hermesworld/characters/README.md",
"directory": "public/assets/hermesworld/characters",
"fileName": "README.md",
"extension": "md",
"bytes": 684,
"sizeLabel": "0.7 KB",
"width": null,
"height": null,
"durationSeconds": null,
"suggestedUseCategory": "character_asset_placeholder"
},
{
"path": "public/assets/hermesworld/zones/cityscape.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "cityscape.jpg",
"extension": "jpg",
"bytes": 63712,
"sizeLabel": "62.2 KB",
"width": 1920,
"height": 600,
"durationSeconds": null,
"suggestedUseCategory": "zone_background_hero"
},
{
"path": "public/assets/hermesworld/zones/hero.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "hero.jpg",
"extension": "jpg",
"bytes": 119035,
"sizeLabel": "116.2 KB",
"width": 1280,
"height": 780,
"durationSeconds": null,
"suggestedUseCategory": "zone_background_hero"
},
{
"path": "public/assets/hermesworld/zones/zone-1.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "zone-1.jpg",
"extension": "jpg",
"bytes": 28167,
"sizeLabel": "27.5 KB",
"width": 600,
"height": 520,
"durationSeconds": null,
"suggestedUseCategory": "zone_fallback_background"
},
{
"path": "public/assets/hermesworld/zones/zone-2.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "zone-2.jpg",
"extension": "jpg",
"bytes": 41268,
"sizeLabel": "40.3 KB",
"width": 600,
"height": 520,
"durationSeconds": null,
"suggestedUseCategory": "zone_fallback_background"
},
{
"path": "public/assets/hermesworld/zones/zone-3.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "zone-3.jpg",
"extension": "jpg",
"bytes": 48092,
"sizeLabel": "47.0 KB",
"width": 600,
"height": 520,
"durationSeconds": null,
"suggestedUseCategory": "zone_fallback_background"
},
{
"path": "public/assets/hermesworld/zones/zone-4.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "zone-4.jpg",
"extension": "jpg",
"bytes": 48371,
"sizeLabel": "47.2 KB",
"width": 600,
"height": 520,
"durationSeconds": null,
"suggestedUseCategory": "zone_fallback_background"
},
{
"path": "public/assets/hermesworld/zones/zone-5.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "zone-5.jpg",
"extension": "jpg",
"bytes": 40418,
"sizeLabel": "39.5 KB",
"width": 600,
"height": 520,
"durationSeconds": null,
"suggestedUseCategory": "zone_fallback_background"
},
{
"path": "public/assets/hermesworld/zones/zone-6.jpg",
"directory": "public/assets/hermesworld/zones",
"fileName": "zone-6.jpg",
"extension": "jpg",
"bytes": 59446,
"sizeLabel": "58.1 KB",
"width": 600,
"height": 520,
"durationSeconds": null,
"suggestedUseCategory": "zone_fallback_background"
},
{
"path": "public/assets/hermesworld/v2/IMAGEGEN-STATUS.md",
"directory": "public/assets/hermesworld/v2",
"fileName": "IMAGEGEN-STATUS.md",
"extension": "md",
"bytes": 796,
"sizeLabel": "0.8 KB",
"width": null,
"height": null,
"durationSeconds": null,
"suggestedUseCategory": "v2_pipeline_doc"
},
{
"path": "public/assets/hermesworld/v2/MANIFEST.md",
"directory": "public/assets/hermesworld/v2",
"fileName": "MANIFEST.md",
"extension": "md",
"bytes": 358,
"sizeLabel": "0.3 KB",
"width": null,
"height": null,
"durationSeconds": null,
"suggestedUseCategory": "v2_pipeline_doc"
},
{
"path": "public/assets/hermesworld/v2/wave-a-source/A03-A08-rerolls.png",
"directory": "public/assets/hermesworld/v2/wave-a-source",
"fileName": "A03-A08-rerolls.png",
"extension": "png",
"bytes": 1605926,
"sizeLabel": "1.53 MB",
"width": 1774,
"height": 887,
"durationSeconds": null,
"suggestedUseCategory": "source_contact_sheet_or_portrait_reference"
},
{
"path": "public/assets/hermesworld/v2/wave-a-source/all-images-contact-sheet.png",
"directory": "public/assets/hermesworld/v2/wave-a-source",
"fileName": "all-images-contact-sheet.png",
"extension": "png",
"bytes": 2131604,
"sizeLabel": "2.03 MB",
"width": 1536,
"height": 1024,
"durationSeconds": null,
"suggestedUseCategory": "source_contact_sheet_or_portrait_reference"
},
{
"path": "public/assets/hermesworld/video/hero-720p.mp4",
"directory": "public/assets/hermesworld/video",
"fileName": "hero-720p.mp4",
"extension": "mp4",
"bytes": 644082,
"sizeLabel": "629.0 KB",
"width": 1280,
"height": 874,
"durationSeconds": 9.683333,
"suggestedUseCategory": "video_demo"
},
{
"path": "public/assets/hermesworld/video/hero-poster.jpg",
"directory": "public/assets/hermesworld/video",
"fileName": "hero-poster.jpg",
"extension": "jpg",
"bytes": 64556,
"sizeLabel": "63.0 KB",
"width": 1280,
"height": 874,
"durationSeconds": null,
"suggestedUseCategory": "video_poster"
},
{
"path": "public/assets/hermesworld/video/world-demo-720p.mp4",
"directory": "public/assets/hermesworld/video",
"fileName": "world-demo-720p.mp4",
"extension": "mp4",
"bytes": 10353994,
"sizeLabel": "9.87 MB",
"width": 1076,
"height": 720,
"durationSeconds": 75.033333,
"suggestedUseCategory": "video_demo"
},
{
"path": "public/assets/hermesworld/video/world-demo-poster.jpg",
"directory": "public/assets/hermesworld/video",
"fileName": "world-demo-poster.jpg",
"extension": "jpg",
"bytes": 63007,
"sizeLabel": "61.5 KB",
"width": 1076,
"height": 720,
"durationSeconds": null,
"suggestedUseCategory": "video_poster"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

View File

@@ -0,0 +1,21 @@
# Imagegen Status — 2026-05-06 07:05 EDT
## Result: Imagegen blocked tonight
All 3 configured image models failed:
- `google/gemini-3.1-flash-image-preview` — no API key configured
- `minimax/image-01` — plan does not support this model
- `openai/gpt-image-1`**billing hard limit reached**
## What we have
- WAVE-A-PROMPTS.md is complete (PR #13) — 18 prompts ready to feed into any working imagegen
- Style lock + brand sheet in place (PR #18)
- Once Eric resolves billing or adds Google API key, all 18 prompts can be batched and saved to `wave-a-source/`
## Recommended next step for Eric
1. Top up OpenAI billing (gpt-image-1)
2. Or add Google AI Studio API key for gemini-3.1-flash-image-preview
3. Then re-dispatch swarm5 or run from orchestrator with WAVE-A-PROMPTS.md

View File

@@ -0,0 +1,9 @@
# HermesWorld v2 Asset Manifest
All Wave A/B/C/D/E generated assets land here. Each row tracks prompt, model, batch, vision-review status.
| File | Wave | Prompt | Model | Status | Vision Review |
|------|------|--------|-------|--------|---------------|
| (TBD) | A | TBD | gpt-5.5 | pending | pending |
Logged automatically by the swarm asset-gen lane.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -225,7 +225,7 @@ export function PlaygroundDialog({
{/* Speech body / chat history */}
{!showChat ? (
<div className="px-4 py-4">
<SpeechBubble variant="npc" tail="left" accent={npc.color} name={npc.name}>
<SpeechBubble variant="npc" tail="left" accent={npc.color} name={npc.name} portraitSrc={npc.portraitSrc} portraitAlt={npc.portraitAlt}>
{reply ?? npc.opening}
</SpeechBubble>
</div>
@@ -236,7 +236,7 @@ export function PlaygroundDialog({
>
{/* Show opening line as an initial assistant turn for context */}
<div className="mb-3">
<SpeechBubble variant="npc" tail="left" accent={npc.color} name={npc.name} compact>
<SpeechBubble variant="npc" tail="left" accent={npc.color} name={npc.name} portraitSrc={npc.portraitSrc} portraitAlt={npc.portraitAlt} compact>
{reply ?? npc.opening}
</SpeechBubble>
</div>
@@ -249,7 +249,7 @@ export function PlaygroundDialog({
</SpeechBubble>
</div>
) : (
<SpeechBubble variant={t.fallback ? 'system' : 'npc'} tail="left" accent={npc.color} name={npc.name} compact>
<SpeechBubble variant={t.fallback ? 'system' : 'npc'} tail="left" accent={npc.color} name={npc.name} portraitSrc={npc.portraitSrc} portraitAlt={npc.portraitAlt} compact>
{t.content}
{t.fallback && (
<span className="ml-2 rounded bg-amber-800/15 px-1.5 py-0.5 text-[9px] font-bold uppercase tracking-[0.18em] text-amber-900/75">

View File

@@ -162,6 +162,14 @@ const WORLDS_3D: Record<PlaygroundWorldId, WorldDef> = {
},
}
const ZONE_FALLBACK_BACKGROUNDS: Partial<Record<PlaygroundWorldId, string>> = {
training: '/assets/hermesworld/zones/zone-1.jpg',
forge: '/assets/hermesworld/zones/zone-2.jpg',
grove: '/assets/hermesworld/zones/zone-3.jpg',
oracle: '/assets/hermesworld/zones/zone-4.jpg',
arena: '/assets/hermesworld/zones/zone-5.jpg',
}
/* ── Ground ── */
/** Procedural stone-tile plaza overlay (canvas texture). Used as a circular */
/** floor under the central HermesStatue in Training Grounds + Agora. */
@@ -3102,6 +3110,7 @@ export function PlaygroundWorld3D({
const playerYaw = useRef(0)
const [settings] = useHermesWorldSettings()
const photosensitiveMode = settings.accessibility.photosensitiveMode
const fallbackBackground = ZONE_FALLBACK_BACKGROUNDS[worldId]
const positionForMp = useRef<{ x: number; y: number; z: number } | null>({ x: 0, y: 0, z: 6 })
// Sync simple position object for multiplayer hook (it doesn't use THREE).
// Also expose to window so the HUD objective arrow can compute heading.
@@ -3159,9 +3168,22 @@ export function PlaygroundWorld3D({
inset: 0,
width: '100%',
height: '100%',
background: '#0b1720',
background: fallbackBackground
? `linear-gradient(180deg, rgba(5,11,18,.22), rgba(5,11,18,.6)), url(${fallbackBackground}) center / cover no-repeat, #0b1720`
: '#0b1720',
}}
>
{fallbackBackground ? (
<div
aria-hidden="true"
style={{
position: 'absolute',
inset: 0,
background: 'radial-gradient(circle at 50% 45%, transparent 0%, rgba(5,11,18,.34) 68%, rgba(5,11,18,.78) 100%)',
pointerEvents: 'none',
}}
/>
) : null}
<style>{`
@keyframes hermes-target-arrow {
0%, 100% { transform: translateY(0); opacity: 0.78; }

View File

@@ -14,6 +14,8 @@ type SpeechBubbleProps = {
tail?: SpeechBubbleTail
accent?: string
name?: string
portraitSrc?: string
portraitAlt?: string
className?: string
style?: CSSProperties
compact?: boolean
@@ -82,6 +84,8 @@ export function SpeechBubble({
tail = 'bottom',
accent,
name,
portraitSrc,
portraitAlt,
className = '',
style,
compact = false,
@@ -113,21 +117,46 @@ export function SpeechBubble({
...style,
}}
>
{name ? (
<div
style={{
color: accent || tokens.label,
fontSize: compact ? 9 : 10,
fontWeight: 900,
letterSpacing: '.16em',
textTransform: 'uppercase',
marginBottom: compact ? 2 : 5,
}}
>
{name}
<div style={{ display: portraitSrc ? 'flex' : 'block', gap: compact ? 8 : 12, alignItems: 'flex-start' }}>
{portraitSrc ? (
<img
src={portraitSrc}
alt={portraitAlt || name || 'NPC portrait'}
loading="lazy"
style={{
width: compact ? 42 : 64,
height: compact ? 42 : 64,
flex: '0 0 auto',
borderRadius: compact ? 12 : 16,
border: `2px solid ${border}`,
objectFit: 'cover',
objectPosition: 'center',
background: 'rgba(0,0,0,.18)',
boxShadow: `0 0 14px ${tokens.glow}`,
}}
onError={(event) => {
;(event.currentTarget as HTMLImageElement).style.display = 'none'
}}
/>
) : null}
<div>
{name ? (
<div
style={{
color: accent || tokens.label,
fontSize: compact ? 9 : 10,
fontWeight: 900,
letterSpacing: '.16em',
textTransform: 'uppercase',
marginBottom: compact ? 2 : 5,
}}
>
{name}
</div>
) : null}
<div>{children}</div>
</div>
) : null}
<div>{children}</div>
</div>
</div>
</>
)

View File

@@ -34,6 +34,9 @@ export type NpcDialogTree = {
name: string
title: string
color: string
/** Optional runtime portrait used by the shared SpeechBubble component. */
portraitSrc?: string
portraitAlt?: string
/** Opening line shown when dialog starts */
opening: string
/** Lore line shown if player keeps talking */
@@ -321,10 +324,12 @@ export const NPC_DIALOG: Record<string, NpcDialogTree> = {
shopkeeper: {
id: 'shopkeeper',
name: 'Dorian',
title: 'Quartermaster of the Market',
color: '#38bdf8',
title: 'Quartermaster of the Starter Kit',
color: '#fbbf24',
portraitSrc: '/assets/hermesworld/v2/wave-a-source/A03-A08-rerolls.png',
portraitAlt: 'Midjourney quartermaster portrait reference for Dorian',
opening:
'Welcome to the Agora market. Every real MMO hub needs a place where players understand value. Your starter kit teaches inventory, gear, and progression in the same motion.',
'You look under-equipped. I am Dorian, quartermaster of the Training Grounds. Builders do better with a blade, a cloak, and a sigil.',
lore: [
'The market will eventually trade cosmetics, generated relics, guild banners, and agent-made artifacts. For now, your starter kit teaches inventory, gear, progression, and rewards.',
'A good product hub has economy, even before money. Reputation, tokens, badges, access, trust — those are currencies too.',