diff --git a/.voidbot/state/README.md b/.voidbot/state/README.md index d442914..1ffe1c3 100644 --- a/.voidbot/state/README.md +++ b/.voidbot/state/README.md @@ -1,8 +1,8 @@ -# Repo Face State +# Repo Persona State This folder stores birth and continuity state for `Eve`. Do not treat these files as arbitrary editable projections. Persistent memory should cross typed operation boundaries. `docs/eve-face.md` is the human-readable mission/memory surface for Eve's repo -Face. Keep `.voidbot/state/eve.cc` as the typed authority, and keep the docs +Persona. Keep `.voidbot/state/eve.cc` as the typed authority, and keep the docs surface aligned when Eve's durable mission changes. diff --git a/.voidbot/state/eve.cc b/.voidbot/state/eve.cc index b59d175..9a82389 100644 Binary files a/.voidbot/state/eve.cc and b/.voidbot/state/eve.cc differ diff --git a/.voidbot/status/void-memory-maintenance.json b/.voidbot/status/void-memory-maintenance.json new file mode 100644 index 0000000..dee855c --- /dev/null +++ b/.voidbot/status/void-memory-maintenance.json @@ -0,0 +1,7 @@ +{ + "status": "starting", + "startedAt": "2026-06-14T19:46:52.908Z", + "stateFile": "E:\\Projects\\Eve\\.voidbot\\state\\eve.cc", + "launcher": "repo_face_heartbeat", + "reason": "repo_face_stale_short_term_memory" +} diff --git a/.voidbot/voice/README.md b/.voidbot/voice/README.md index 5986751..2fd8a71 100644 --- a/.voidbot/voice/README.md +++ b/.voidbot/voice/README.md @@ -1,4 +1,4 @@ -# Repo Face Voice +# Repo Persona Voice -This folder belongs to the Discord-facing Face for `Eve`. -Discord roles address the Face; VoidBot's webhook persona pipe is the mouth. +This folder belongs to the Discord-facing Persona for `Eve`. +Discord roles address the Persona; VoidBot's webhook persona pipe is the mouth. diff --git a/.voidbot/voice/identity.json b/.voidbot/voice/identity.json index e8a34b9..2a76758 100644 --- a/.voidbot/voice/identity.json +++ b/.voidbot/voice/identity.json @@ -6,5 +6,5 @@ "roleId": "1510848465243082874", "avatarUrl": "https://raw.githubusercontent.com/GameCult/VoidBot/codex/voidbot-cultcache-replace-retry/assets/repo-faces/eve.png", "repoPath": "E:\\Projects\\Eve", - "createdOrRefreshedAt": "2026-06-01T18:13:24.841Z" + "createdOrRefreshedAt": "2026-06-15T20:45:04.746Z" } diff --git a/README.md b/README.md index 84bebff..e2700ef 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ CultMesh as typed distributed state; the [Week 07 damage report](https://gamecult.org/Blog/daily-damage-report/week-07) records Eve, Fensalir, CultLib, Sai, and Odin turning operator surfaces into first-class state; [The Free Mouth And The Native Body](https://gamecult.org/Blog/the-free-mouth-and-the-native-body) -describes the Face/native-body split Eve must serve; and +describes the Persona/native-body split Eve must serve; and [The Sleeping Colossus Learns To Refuse The Throne](https://gamecult.org/Blog/the-sleeping-colossus-refuses-the-throne) is the praxis warning: means and ends must match. Eve's means are local projection, consentful command boundaries, inspectable stale state, and clear @@ -241,7 +241,7 @@ state and command handling. Eve is the operator's hand on the scene graph, not a second compositor and not a remote WebKit runtime. The broker includes a native VoidBot tab/provider. Eve renders its CTB rail with -avatar images, selected Face status panel, state tree, and detail pane from the +avatar images, selected Persona status panel, state tree, and detail pane from the same VoidBot `swarm-state.json` projection used by the web dashboard. ## Build Shape diff --git a/docs/cultmesh-streaming-ui-framework.md b/docs/cultmesh-streaming-ui-framework.md index 52c0245..9bf0b1e 100644 --- a/docs/cultmesh-streaming-ui-framework.md +++ b/docs/cultmesh-streaming-ui-framework.md @@ -136,7 +136,7 @@ The current iOS app consumes Mimir's `/eve/deck` WebSocket broker. That broker i already shaped like the future API: provider manifests, retained dashboard state, provider switching, commands, and typed node metadata. The VoidBot provider proves that Eve can render an app-specific native cockpit from a -structured mesh snapshot: CTB rail, avatar images, selected Face/status pane, +structured mesh snapshot: CTB rail, avatar images, selected Persona/status pane, state tree, and detail panel. The Android proof under `android/` builds directly against the installed Android diff --git a/docs/cultui-style-system.md b/docs/cultui-style-system.md index f9b5f3c..83c3d7b 100644 --- a/docs/cultui-style-system.md +++ b/docs/cultui-style-system.md @@ -293,8 +293,9 @@ The portable style document should look like data: "colorBackgroundTop": "#01040b", "colorPanel": "#0a2239", "colorText": "#efe5c9", - "fontTitle": "Press Start 2P", - "fontBody": "VT323", + "fontDisplay": "\"Montserrat\", \"Zen Kaku Gothic New\", \"M PLUS 1\", sans-serif", + "fontTitle": "\"Montserrat\", \"Zen Kaku Gothic New\", \"M PLUS 1\", sans-serif", + "fontBody": "\"M PLUS 1\", \"Ubuntu Sans\", Ubuntu, sans-serif", "borderWidthPx": 4, "pixelArt": true, "scanlineOverlay": true @@ -315,6 +316,17 @@ Rules should target component roles, variants, and state, not arbitrary global text selectors. If a style cannot explain which component role or variant it serves, it probably belongs in a runtime lowerer or nowhere. +Typography roles carry script-coverage obligations. GUI body text should use +`M PLUS 1` and `Ubuntu Sans`/Ubuntu for the clean contemporary reading voice. +GUI display text may use Montserrat for Latin, but Japanese display needs a +matching contemporary fallback such as `Zen Kaku Gothic New` before falling +back to a body face. + +Pixel text is a TUI raster-font concern, not a GUI webfont substitution. A TUI +lowerer that claims Japanese pixel typography must expose fixed-cell raster +atlases for display, body, and small scales, with Latin and hiragana supported +and katakana supported inside each advertised cell size. + ## What CultUI Takes From CSS Keep: @@ -374,6 +386,9 @@ Each runtime needs one style lowerer: Runtime lowerers must report capability gaps. If Android cannot yet render Repixelizer's pixel-art image preview or cleanup canvas, the surface should say that plainly instead of faking a control that cannot carry the command. +If a lowering target cannot load the declared Japanese-capable display fonts or +TUI raster atlases, it must report that typography capability gap rather than +silently rendering hiragana with tofu or an unrelated default face. ### Parity Unit Boundary diff --git a/docs/eve-face.md b/docs/eve-persona.md similarity index 94% rename from docs/eve-face.md rename to docs/eve-persona.md index 42cbe19..d59b211 100644 --- a/docs/eve-face.md +++ b/docs/eve-persona.md @@ -1,7 +1,7 @@ -# Eve Face Memory +# Eve Persona Memory -This is the human-readable persistent memory surface for Eve's repo Face. The -typed `.voidbot/state/eve.cc` file remains the live Face-state authority; this +This is the human-readable persistent memory surface for Eve's repo Persona. The +typed `.voidbot/state/eve.cc` file remains the live Persona-state authority; this document is the stable re-entry map that belongs in the repo body. ## Mission @@ -56,8 +56,8 @@ CultLib/CultMesh is Eve's substrate. If the substrate cannot expose field-level reactivity, authority, prediction, and reconciliation, Eve's magic becomes a gesture instead of a machine. -VoidBot and repo Faces are Eve's social field. Eve surfaces should let their -state become legible without flattening each Face into one central dashboard +VoidBot and repo Personas are Eve's social field. Eve surfaces should let their +state become legible without flattening each Persona into one central dashboard voice. ## Current Doctrine diff --git a/docs/surface-contract-v1.md b/docs/surface-contract-v1.md index b8fd0c2..77a6f40 100644 --- a/docs/surface-contract-v1.md +++ b/docs/surface-contract-v1.md @@ -247,9 +247,29 @@ Minimum common tokens: - `radiusPanel` - `fontBody` - `fontTitle` +- `fontDisplay` - `pixelArt` - `motion` +Typography tokens describe roles, not one runtime's installed fonts. `fontBody` +should favor `M PLUS 1` and `Ubuntu Sans`/Ubuntu for GUI lowerers. `fontDisplay` +and `fontTitle` may use Montserrat for Latin text, but must include a +Japanese-capable contemporary display fallback such as `Zen Kaku Gothic New`; +Ubuntu remains the body voice, not the flashy title answer. + +TUI pixel typography is a separate lowering capability, not a GUI webfont +fallback. A TUI lowerer that claims Japanese pixel support must expose fixed +raster-cell fonts for each advertised scale, with Latin, hiragana, and katakana +in the same cell grid. The expected capability roles are: + +- `tuiPixelDisplay`: large raster display labels, roughly 16px or larger. +- `tuiPixelBody`: readable dense raster text, roughly 12px. +- `tuiPixelSmall`: the smallest readable kana lane, roughly 10px. + +If a TUI target lacks a Unicode raster atlas for one of those scales, it should +publish a capability gap instead of silently falling back to tofu, proportional +Japanese text, or a Latin-only console font. + Product surfaces may add scoped token groups such as: - `streamPixels.characterPreview` diff --git a/web/fixtures/repixelizer.eve-surface.json b/web/fixtures/repixelizer.eve-surface.json index 2326191..e5b304b 100644 --- a/web/fixtures/repixelizer.eve-surface.json +++ b/web/fixtures/repixelizer.eve-surface.json @@ -35,7 +35,7 @@ "node-bound command taps" ], "losesUntilRendererParity": [ - "Press Start 2P and VT323 font selection", + "GUI display/body font selection plus TUI Latin/kana raster font scale selection", "Repixelizer shell frame, scanlines, pixel grid, corner accents, and chunky border widths", "landing/app image assets; current Android lowerer prints asset refs when images are not explicitly implemented", "upload picker, comparison canvas, pan/zoom inspection, eyedropper, and paint controls", @@ -43,7 +43,7 @@ ], "minimumParityCuts": [ "map surface.styles.tokens before toneColor/textColorFor/textSizeFor", - "add packaged pixel fonts or font download cache", + "add packaged GUI fonts plus TUI raster atlas cache", "render image assetUri/assetRef as ImageView with pixelated sampling", "lower command controls to Android upload/document picker and job route calls" ], @@ -1200,14 +1200,14 @@ "styles": { "assets": { "appUrl": "https://repixelizer.gamecult.org/app/", - "fontCss": "https://fonts.googleapis.com/css2?family=Press+Start+2P&family=VT323&display=swap", + "fontCss": "https://fonts.googleapis.com/css2?family=M+PLUS+1:wght@300;400;500;700&family=Montserrat:wght@100;300;400;500;700&family=Ubuntu+Sans:wght@300;400;500;700&family=Zen+Kaku+Gothic+New:wght@300;400;500;700&display=swap", "landingInput": "https://repixelizer.gamecult.org/app/landing-assets/character-input.png", "landingOutput": "https://repixelizer.gamecult.org/app/landing-assets/character-repixelized.png" }, "loweringHints": { - "android-native": "Load packaged or downloadable Press Start 2P and VT323 fonts, map style tokens before hard-coded tones, and render asset images instead of asset labels.", + "android-native": "Load packaged or downloadable Montserrat, Zen Kaku Gothic New, M PLUS 1, and Ubuntu Sans fonts, map style tokens before hard-coded tones, and render asset images instead of asset labels.", "css": "Use the tokens as CSS custom properties and retain scanline/pixel-grid overlays.", - "tui": "Preserve title/body font distinction as role metadata; canvas editing lowers to artifact refs and command intents only." + "tui": "Use fixed-cell raster atlases for tuiPixelDisplay/body/small; each advertised size must support Latin, hiragana, and katakana in the same cell grid." }, "profile": "repixelizer.retro.pixel", "tokens": { @@ -1233,17 +1233,21 @@ "colorTextBright": "#f7efd8", "cornerAccentPx": 12, "dashedInnerFrame": true, - "fontBody": "\"VT323\", \"Courier New\", monospace", - "fontMono": "\"VT323\", \"Courier New\", monospace", - "fontTitle": "\"Press Start 2P\", \"VT323\", monospace", + "fontBody": "\"M PLUS 1\", \"Ubuntu Sans\", Ubuntu, sans-serif", + "fontDisplay": "\"Montserrat\", \"Zen Kaku Gothic New\", \"M PLUS 1\", sans-serif", + "fontMono": "\"Ubuntu Sans Mono\", \"M PLUS 1 Code\", \"Cascadia Mono\", monospace", + "fontTitle": "\"Montserrat\", \"Zen Kaku Gothic New\", \"M PLUS 1\", sans-serif", "imageRendering": "pixelated", - "scanlineOverlay": true + "scanlineOverlay": true, + "tuiPixelBody": "fixed-cell-raster-latin-kana-12", + "tuiPixelDisplay": "fixed-cell-raster-latin-kana-16", + "tuiPixelSmall": "fixed-cell-raster-latin-kana-10" } }, "title": "Repixelizer" }, "title": "Repixelizer", "type": "surface-state", - "updatedAt": "2026-06-05T00:00:00Z", + "updatedAt": "2026-06-04T00:00:00Z", "version": 1 } diff --git a/web/styles.css b/web/styles.css index 79f8b6c..44699e9 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,3 +1,5 @@ +@import url("https://fonts.googleapis.com/css2?family=M+PLUS+1:wght@100;300;400;500;700&family=Montserrat:wght@100;300;400;500;700&family=Ubuntu+Sans:wght@300;400;500;700&family=Zen+Kaku+Gothic+New:wght@300;400;500;700&display=swap"); + :root { color-scheme: dark; --bg: #020909; @@ -23,7 +25,9 @@ --border-width: 1px; --border-bottom-width: var(--border-width); --corner-accent-size: 0; - --font-mono: "Cascadia Mono", Consolas, monospace; + --font-body: "M PLUS 1", "Ubuntu Sans", Ubuntu, "Noto Sans JP", system-ui, sans-serif; + --font-title: "Montserrat", "Zen Kaku Gothic New", "M PLUS 1", "Ubuntu Sans", system-ui, sans-serif; + --font-mono: "Ubuntu Sans Mono", "M PLUS 1 Code", "Cascadia Mono", Consolas, monospace; } * { @@ -35,7 +39,7 @@ body { min-height: 100vh; background: linear-gradient(180deg, var(--bg-top), var(--bg-mid), var(--bg-bottom)); color: var(--text); - font-family: var(--font-body, Inter), "Segoe UI", system-ui, sans-serif; + font-family: var(--font-body); } .topbar { @@ -432,7 +436,7 @@ body[data-pixel-art="true"] .cultui-background { .cultui-title { color: var(--accent); - font: 700 16px "Cascadia Mono", Consolas, monospace; + font: 100 18px var(--font-title); line-height: 1.35; } diff --git a/web/surface.js b/web/surface.js index c5ca751..84a76ad 100644 --- a/web/surface.js +++ b/web/surface.js @@ -11,6 +11,7 @@ let socket; let providers = []; let currentProvider; let currentSurfaceStyles = {}; +let activeFontStylesheet; const defaultStyleTokens = { "--bg": "#020909", @@ -35,9 +36,9 @@ const defaultStyleTokens = { "--border-width": "1px", "--border-bottom-width": "1px", "--corner-accent-size": "0", - "--font-body": "Inter, Segoe UI, system-ui, sans-serif", - "--font-title": "Cascadia Mono, Consolas, monospace", - "--font-mono": "Cascadia Mono, Consolas, monospace", + "--font-body": "\"M PLUS 1\", \"Ubuntu Sans\", Ubuntu, \"Noto Sans JP\", system-ui, sans-serif", + "--font-title": "\"Montserrat\", \"Zen Kaku Gothic New\", \"M PLUS 1\", \"Ubuntu Sans\", system-ui, sans-serif", + "--font-mono": "\"Ubuntu Sans Mono\", \"M PLUS 1 Code\", \"Cascadia Mono\", Consolas, monospace", }; const localProviders = [ @@ -212,6 +213,7 @@ function renderSurface(state, source) { function applySurfaceStyles(styles) { const tokens = styles?.tokens || {}; const root = document.documentElement.style; + loadFontStylesheet(styles?.assets?.fontCss); for (const [variable, value] of Object.entries(defaultStyleTokens)) { root.setProperty(variable, value); } @@ -239,9 +241,10 @@ function applySurfaceStyles(styles) { for (const [token, variable] of Object.entries(map)) { if (tokens[token]) root.setProperty(variable, tokens[token]); } - if (tokens.fontBody) root.setProperty("--font-body", tokens.fontBody); - if (tokens.fontTitle) root.setProperty("--font-title", tokens.fontTitle); - if (tokens.fontMono) root.setProperty("--font-mono", tokens.fontMono); + if (tokens.fontBody) root.setProperty("--font-body", withJapaneseGuiFallback(tokens.fontBody)); + if (tokens.fontDisplay) root.setProperty("--font-title", withJapaneseGuiFallback(tokens.fontDisplay)); + if (tokens.fontTitle) root.setProperty("--font-title", withJapaneseGuiFallback(tokens.fontTitle)); + if (tokens.fontMono) root.setProperty("--font-mono", withJapaneseGuiFallback(tokens.fontMono)); if (tokens.borderWidthPx) root.setProperty("--border-width", `${tokens.borderWidthPx}px`); if (tokens.borderBottomWidthPx) root.setProperty("--border-bottom-width", `${tokens.borderBottomWidthPx}px`); if (tokens.cornerAccentPx) root.setProperty("--corner-accent-size", `${tokens.cornerAccentPx}px`); @@ -249,6 +252,31 @@ function applySurfaceStyles(styles) { document.body.dataset.scanlines = tokens.scanlineOverlay ? "true" : "false"; } +function loadFontStylesheet(href) { + const normalized = typeof href === "string" ? href.trim() : ""; + if (activeFontStylesheet?.dataset.href === normalized) return; + activeFontStylesheet?.remove(); + activeFontStylesheet = undefined; + if (!normalized) return; + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = normalized; + link.dataset.href = normalized; + document.head.append(link); + activeFontStylesheet = link; +} + +function withJapaneseGuiFallback(stack) { + if (typeof stack !== "string" || stack.includes("Zen Kaku Gothic New") || stack.includes("M PLUS 1")) return stack; + if (stack.includes("Montserrat")) { + return `${stack}, "Zen Kaku Gothic New", "M PLUS 1", "Noto Sans JP", "Hiragino Sans", "Yu Gothic", sans-serif`; + } + if (stack.includes("Ubuntu")) { + return `${stack}, "M PLUS 1", "Noto Sans JP", "Hiragino Sans", "Yu Gothic", sans-serif`; + } + return `${stack}, "Noto Sans JP", "Hiragino Sans", "Yu Gothic", sans-serif`; +} + function renderCultComponent(node) { const kind = node.kind || "panel"; const props = node.props || {};