Dev Progress

A living log of development progress and tracked issues for GENESIS Helper. Edit content/dev/PROGRESS.md to update — no code changes needed.


Open Issues

Volltextsuche — scope notes

  • No deep ###/paragraph anchor jumps for prose — a result lands on the section page and highlights/scrolls to the first hit, but the URL has no #anchor.
  • Note: the wiki content route /regelwerk/[...slug] is now server-rendered on demand (reads ?q= to pre-filter the interactive browsers) instead of fully static. Content loaders are module-cached, so this is cheap.

Custom card arrangement

  • Cards on the character sheet cannot be reordered or repositioned by the user — layout is fixed in code.
  • Desired: drag-and-drop (or up/down buttons) to rearrange cards within each column, with order persisted per character.

Alt-.doc-Quellen nicht maschinenlesbar

  • Mehrere autoritative Alt-Quellen in Dropbox sind Composite-Binär-.doc (z. B. 01 - Charaktererschaffung/04 Stand der Eltern.doc), die LibreOffice headless hier nicht öffnet ("source file could not be loaded") und die mammoth (nur .docx) nicht liest. Dadurch fehlen u. a. die A–G→Stand-Zuordnung und die Stands-Vorteile für die echte Stand-Mechanik. Workaround: Datei vorab in .docx/.ods konvertieren und committen, oder Daten manuell liefern.

Character sheet gaps vs. Heldenbrief reference

  • Boni-Sheet incomplete — 268 of 659 Fähigkeiten are absent from the Boni sheet in genesis_charaktergenerator-combined_TEST.xlsx and therefore get no auto-applied stat modifiers. Notable missing entries: Zusatz-AT, Zusatz-PA, Verbesserte Grundparade, Verbesserte Reflexe, Gesteigertes Ausweichen, Mächtige Parade, Physische Aura, ZW/ZS/ZF-Boost/Verstärkung family, and most Klasse-II/III abilities. Fix: add them to the Boni sheet, then npm run import:boni + commit.

Kindheit/Jugend — open data gaps

  • Liebesaffäre (and other youth events from the planning sheets) are listed in Übersicht/Aufzählung Ereignisse but have no row in the Auslöser table, so they are not in ereignisse. Add them to the workbook's Auslöser sheet (and to JUGEND_ONLY in the importer) when the designer fills them in.
  • Personengruppen Stufe-anchor alignment is loose (e.g. Förderer Söldner picks up Stufe 6-8 from col AX, likely an off-by-rows merged-cell artefact). The importer keeps raw rows with their source row; a hard Personengruppe×Stufe lookup must re-validate against the sheet. See docs/impl-kindheit-jugend.md §7.
  • Duplicate Förderer/Magier blocks (variante) — unclear if alternate roster or stale copy; both retained.

Deferred

  • Stand der Eltern — echte klassenbasierte Tier-Mechanik — Laut Quelle (07 - Bearbeitung/Stand_der_Eltern_Geld_Vorteile.xlsx) wird der Stand über drei klassenbasierte W1000-Tabellen (Butterseite/Vorzugs/Abenteurer) ermittelt → Kategorien X/A–G (X = W12-Knechtschafts-Sondertabelle). Blockiert: der Wizard hat kein Klassen-Konzept, und die A–G→Stand-Zuordnung + Vorteile liegen in 01 - Charaktererschaffung/04 Stand der Eltern.doc (Alt-Binär-.doc, hier nicht lesbar). Aktuell bleibt der vereinfachte W20-Tier-Wurf; nur die Vermögen-Werte wurden korrigiert.
  • Erziehung (neues Regelwerk)01 - Charaktererschaffung/05 Erziehung.ods: mehrachsiger Erziehungs-Generator (wer erzieht × Maß × Eltern-Eigenschaft × Erziehungsart → Effekte). Als eigener Wizard-Schritt umzusetzen.
  • Reward editor — generic GM-granted modifiers.
  • Temporary-effect add/expire UI — model carries kind:"temporary" + expiresAt; engine honours expiry.
  • Printable character sheet (A4)@media print layout; no PDF generation needed.

Done

Fix: selected text was unreadable (2026-08-07)

app/globals.css never defined ::selection, so the browser default applied: it repaints the background but keeps each element's own text colour. Against muted grey (--muted) and accent-coloured text the selection highlight left almost no contrast. Added explicit ::selection / ::-moz-selection rules using --accent background with --background as the text colour, which resolves per theme. mark.search-hit gets its own override (--foreground background) since accent-on-accent would otherwise be invisible when a search hit is selected.

Fix: "Einkommen − NaN Gulden" on characters with a legacy income value (2026-08-07)

Pre-existing bug, not from the English rename (the code is byte-identical to before it). GenesisCharacter.einkommen became a structured dice formula { formel, anzahl, wuerfel, multiplikator, flat } at some point, but characters created earlier still hold free text — Torvin's is the string "200 Gulden". buildFormel then read e.flat as undefined, and undefined !== 0 is true, so it rendered − ${Math.abs(undefined)}− NaN Gulden.

Added normalizeEinkommen in app/characters/[id]/page.tsx: a string is parsed for its leading number into flat (so "200 Gulden"+ 200) and keeps the original text as formel; partial objects get every missing field defaulted. EinkommenVermoegenCard normalizes once on entry, so all downstream code sees a complete object. The stored type in lib/characters/types.ts is now … | string with a comment, so the legacy shape is visible to anyone touching it rather than silently assumed away. Saving the card writes the structured form, migrating the character on first edit.

Fix: rename broke the JSON data bindings on the Kampfbogen (2026-08-07)

The English rename renamed the interface fields of types that describe on-disk JSON, so the runtime objects no longer matched and every read returned undefined — the Kampfbogen rendered undefinedWundefined+5 undefined for weapon damage.

Root cause: CombatTables mirrors content/wiki/derived/kampf-tabellen.json, whose entries are { wuerfel, seiten, plus } / { wuerfel, halb }. Renaming those to diceCount/sides/half typechecked fine (the JSON is cast via as unknown as CombatTables) but silently broke every lookup. Same class of bug in BonusEntry (faehigkeiten-boni.json: wirkdauerGruppe, quelleText), which had disabled the ability-bonus modifier sources, and in scripts/import-kindheit-jugend.ts, which had started writing personGroups/effects instead of personengruppen/auswirkungen.

Fix: any interface that describes a data file keeps that file's key names; the mapping to English happens explicitly (const { wuerfel: diceCount, seiten: sides, plus } = e). Derived shapes (TpDamage, GwAttack) stay English. Also renamed the leftovers rollTpWurfrollTpDamage, gwAusweichengwDodge, GwAttack.anzahlcount.

Guard for next time: tsc cannot catch this. Verify a rename by comparing, for every JSON under content/wiki/, which keys were referenced in the source before vs. after — a key that was referenced before and no longer is means a broken binding. That check is now clean for both derived/ and tabellen/.

Source code is English throughout (2026-08-07)

Full rename pass: every identifier, component, module filename and comment in app/, lib/, components/ and scripts/ is now English. UI strings stay German — the German-only rule covers content and copy, not code.

What changed: type names (CharFaehigkeitCharAbility, StufenanstiegEintragLevelUpEntry, GebundenesWesenBoundCreature, ZauberSpell, FaehigkeitAbility, KlasseItemTier, …); module files (lib/characters/steigerung.tslevel-up.ts, faehigkeit-kontingent.tsability-contingent.ts, lib/wiki/faehigkeiten.tsabilities.ts, waffenkammer.tsarmory.ts, zauber.tsspells.ts, …); components (WuerfelPanelDicePanel, TalentVerteilungTalentDistribution, FaehigkeitenIndexAbilitiesIndex, …); and all locals/params.

Deliberately unchanged — these are data, not code:

  • Persisted JSON keys on GenesisCharacter and everything it contains (faehigkeiten, talente, stufe, hintergrund, steigerungshistorie, planung, gebundeneWesen, beschreibung, kosten, klasse, eintraege, notizen, …) and persisted string values (kind: "faehigkeit", "hauptwaffe", art: "stat"). Renaming them would need a migration of data/characters.json on the live server; the mapping now happens explicitly at the boundary (see handleBuyAbility, handleAddAbility) with a comment.
  • Wiki/derived data keys read from the synced spreadsheets ("Fähigkeiten gesamt", byTyp, faehigkeiten-boni.json column names).
  • Route segments (/characters/[id]/stufenanstieg, /regelwerk/06-zauberbuecher) — user-visible URLs.

Internal-only discriminators were renamed (PageContent.kind: "faehigkeiten-index""abilities-index" etc.; SpellResult: "erfolg""success", "patzer""fumble", …) since they are computed per request and never stored.

Every game-rule limit is now display-only, app-wide (2026-08-07)

Follow-up to the SP-budget change below, applying the same policy everywhere: no game rule may block an action, because the Meister can grant extra points, gold, languages or an ability as a reward. Limits are still shown, exceeded values render amber, nothing is disabled or hidden.

  • Character creation (app/characters/new/page.tsx): the 3-reroll attribute cap and the 1-reroll Geburt cap no longer disable the dice button (counters turn amber past the limit); the 2 Freipunkte can go negative or over budget (adjustBonus no longer clamps, and a negative bonus is allowed as a Meister-imposed penalty); the 3/10/3 talent quota no longer gates the "Weiter" button, it renders a hint instead; the language limit no longer hides the "Sprache hinzufügen" control.
  • components/characters/TalentVerteilung.tsx: quota buttons stay clickable past MOD_QUOTA; over-quota counters go amber.
  • Stufenanstieg (app/characters/[id]/stufenanstieg/page.tsx): an ability at its highest documented Steigerung keeps a buy button (the top step's data is reused); the Kontingent is a warning, not a block; abilities with a non-numeric cost ("16 oder 30", "keine") are purchasable and booked at 0 SP; racial and Entwicklungsfähigkeiten are no longer filtered out of the list.
  • Planung (app/characters/[id]/planung/page.tsx): same ability filter removed; MAX_PLAN_LEVEL = 30 dropped entirely, so planning runs as far as the campaign does.
  • Shop (app/characters/[id]/shop/page.tsx): a second copy of an owned item can be bought (the button is amber-outlined instead of disabled).
  • Healing is no longer capped at maximum: consumables on the character sheet and both applyHeilung / manual ME recharge in Kampf can now overheal past LE/ME max. The bars already clamp their own width, so this is display-safe.

Deliberately left in place: the duplicate-ability guard in handleAddFaehigkeit (app/characters/[id]/page.tsx:1310) — a second entry with the same name would break the name-keyed ownedAbilities lookups and syncFaehigkeitSources, so it is a data-integrity guard rather than a rule. Further Steigerungen of an owned ability go through the Stufenanstieg wizard as before.

SP budget in Stufenanstieg is advisory, not enforced (2026-08-07)

The Stufenanstieg wizard (app/characters/[id]/stufenanstieg/page.tsx) no longer blocks anything on the 50 + 2×Stufe SP budget: stat/talent/ability buy buttons stay enabled past the limit, and saving no longer requires spVerbleibend === 0. The Meister can grant extra points, and a player may also want to bank leftovers, so the budget is purely informational — same behaviour the Planung page already had. SpAnzeige now clamps the progress bar at 100%, turns yellow on overspend and reports "X SP über Budget" instead of a negative "verbleibend"; the save bar explains the delta in either direction. The now-unused verbleibend props were dropped from StatZeile and FaehigkeitKarte. Kontingent- and Stufen-checks on abilities are untouched — those are still hard rules.

New characters start at Stufe 0 (2026-07-03)

Character creation (app/characters/new/page.tsx) now explicitly sets stufe: 0 on the created character instead of leaving the field unset (which every display fell back to treating as Stufe 1 via ?? 1). A fresh character must go through the existing Stufenanstieg wizard once to reach Stufe 1 before play, matching the rule that characters generate at Stufe 0 and level up into Stufe 1 as their first regular level-up. No changes needed to lib/characters/steigerung.tsneueStufe = (char.stufe ?? 1) + 1 already computes 1 correctly for a Stufe-0 character, and all existing char.stufe ?? 1 display fallbacks use nullish coalescing so they still render "Stufe 0" correctly (only undefined/null is coalesced, not 0). Added a banner on the character sheet (app/characters/[id]/page.tsx) prompting the player to the Stufenanstieg page while stufe === 0.

Gebundene Wesen card — custom summoned/bound creatures (2026-07-03)

New "Gebundene Wesen" card for ritual-bound or summoned creatures (e.g. a Windgeist from Ritualmagie I). In edit mode the player creates a fully custom entity: free-text name/Quelle/Beschreibung, freeform attribute label/value pairs (LE, RS, etc. — not tied to the stat-modifier engine), and any number of named dice presets (e.g. "Blitzschlag" 1×W20). Dice presets persist on the character record and are rollable via the existing DiceButton; roll results themselves stay ephemeral (reset on reload), matching the existing Würfel-Panel behaviour. New GebundenesWesen type + gebundeneWesen field on GenesisCharacter; new components/characters/GebundenesWesenForm.tsx.

Fähigkeiten card editable in edit mode (2026-07-03)

The Fähigkeiten card on the character sheet now supports the global edit mode (nav padlock): a search picker adds any ability from the wiki catalog (needed e.g. for abilities granted outside a level-up, like a free Kindheit/Jugend Fähigkeit), and each ability card gets a remove button. Adding an ability also creates its stat-modifier source from the boni catalog; removing deletes both the entry and its source. The card is shown in edit mode even when the character has no abilities yet.

Serve portraits via binary endpoint — fixes slow navigation (2026-07-03)

Portraits (since video support, up to ~5 MB) were stored as base64 data-URLs inside the character JSON, so /api/characters grew to ~6.9 MB and every character sub-page navigation refetched the full character including the embedded video. Character API responses now replace data-URL portraits with a pointer URL (/api/characters/[id]/portrait?v=<updatedAt>&mime=…) served by a new binary route with immutable caching (lib/characters/portrait.ts). PUT ignores the pointer URL so it never overwrites the stored data-URL; PortraitCard detects videos via the mime=video query param. Storage format is unchanged — only responses are slimmed.

Fix value overflow in Modifikationen card (2026-07-03)

Long modifier target names (e.g. "Schiffe-/Großfahrzeuge steuern") pushed under the value column in the two-column grid on the character sheet. Labels now truncate with an ellipsis (full name via title tooltip) and the value column no longer shrinks.

Global edit-mode padlock, full-sheet field editing (2026-07-02)

Replaced the two separate in-page "Werte bearbeiten"/"Verteilung bearbeiten" toggle buttons with a single padlock icon in the top nav bar, shared via a new EditModeProvider (lib/contexts/edit-mode-context.tsx, mounted in Providers.tsx). The padlock only renders when a logged-in user (useCanEdit) is on the main character sheet route (app/characters/[id]/page.tsx calls setAvailable(canEdit)); toggling it off calls a save handler the page registers via registerSaveHandler, which persists every editable field in one PUT. Switching routes implicitly cancels edit mode (the page's local edits are discarded on unmount either way).

Extended edit-mode coverage beyond attributes/derived stats to close the "every value must be editable" gap: character name, Volk/Geschlecht/Größe/Gewicht/Statur, the free-form aussehen dict, Stand/StandTier/StandVermögenText, Sprachen (Stufe per language), and Hintergrund-Ereignisse (Titel/Beschreibung/Sondertext — attrMod/startkapitalMod stay read-only since those are computed game-math, not free values). Talent-Verteilung (TalentVerteilung) now also keys off the shared editMode instead of its own local toggle.

Video portrait support (2026-07-02)

PortraitCard.tsx now also accepts video/mp4, video/webm, and video/quicktime uploads (input accept attribute widened). Videos are read as a raw base64 data-URL without re-encoding (canvas downscaling only applies to images), so uploads are capped at 5 MB (MAX_VIDEO_BYTES) to keep data/characters.json from bloating. Rendering now branches on isVideoSrc() (checks data:video/ prefix or .mp4/.webm/.mov extension for URL-mode portraits) between next/image and a looping, muted, autoplaying <video> element. No changes to GenesisCharacter.portraitUrl (still string) or the API route — it stays an opaque string field.

Editable Stufenanstieg history (2026-07-01)

Past Stufenanstieg (level-up) entries were append-only — the wizard at app/characters/[id]/stufenanstieg/page.tsx always targeted stufe + 1, and purchases were merged into char.sources/char.faehigkeiten with no back-reference to which history entry created them, so a saved level-up could never be reopened or corrected.

Added id to StufenanstiegEintrag and steigerungEintragId to ModifierSource/CharFaehigkeit (lib/characters/types.ts) to tag exactly which entries a level-up produced. StufenanstiegAusgabe now also stores structured purchase data (stat/gewinn/talent/faehigkeit/istSteigerung/neuerSteigerungStufe), not just a display label, so a past entry's effects can be rebuilt. lib/characters/steigerung.ts gained recomputeSteigerungEffects(), which strips all tagged sources/faehigkeiten back to baseline (equipment + any untagged legacy data) and replays every history entry with an id, in level order — this is the "full recompute" model so editing an earlier level stays consistent with later ones. applyStufenanstieg (new level-up) and the new updateStufenanstiegEintrag (edit an existing one) both funnel through it.

The wizard page now reads an ?edit=<eintragId> query param: it prefills selections from the stored entry, computes costs/ownership against a "baseline" character with that entry's own effects subtracted out (so re-purchasing the same things doesn't double-count), and saves via updateStufenanstiegEintrag instead of applyStufenanstieg. HistoriePanel on the character sheet (app/characters/[id]/page.tsx) shows a "Bearbeiten" link per entry when logged in. Legacy entries saved before this change have no id and stay display-only — their effects are already baked into the character's baseline sources and are never touched by recompute.

Editable talent point distribution on character sheet (2026-07-01)

The character-creation talent point distribution (per-talent mod/endwert on char.talente, set at creation by buildTalentliste in lib/characters/talente.ts) was previously only assignable during creation and fixed afterward. Added a "Verteilung bearbeiten" toggle to the Talente panel in app/characters/[id]/page.tsx (visible only when logged in). Extracted the creation wizard's +30/+15/−20 toggle-button + quota-bar UI into a shared components/characters/TalentVerteilung.tsx, now used by both app/characters/new/page.tsx (Step 10) and the sheet's edit mode, so both look and behave identically. Edits update char.talente[].mod/endwert locally and persist via PUT /api/characters/[id] on save.

Iteration notes: first attempt used a free-form number input to dodge the quota buttons disabling on legacy non-bucket mod values (existing characters can have values like 5/10/20/35 from background-event bonuses baked directly into mod, predating the clean-bucket scheme). Fixed properly by loosening the shared component's disabled logic to key off quota counts only (not "current value must be 0 first"), so any button can directly overwrite an arbitrary existing value into a bucket — this also slightly simplifies the creation wizard's own UX.

Fix Notizen text overflow on character sheet (2026-07-01)

Long unbroken tokens (URLs, inline code, compound words) in rendered Markdown notes could overflow their container because .wiki-prose had no overflow-wrap, and the panel's grid item had no min-width: 0 to allow shrinking. Added overflow-wrap: anywhere + min-width: 0 to .wiki-prose (and its code rule) in app/globals.css, plus min-w-0 on the rendered notes wrapper (components/characters/MarkdownNotes.tsx) and the HideablePanel root (components/characters/HideablePanel.tsx).

Follow-up: the actual overlap the user saw was the absolutely-positioned edit (✎) button in MarkdownNotes.tsx sitting on top of the note text with no reserved space, clipping through words near the top-right corner. Added pr-12 to the rendered text wrapper so text no longer runs under the button.

Show logged-in user in footer status bar (2026-07-01)

Added components/layout/FooterAuthStatus.tsx, rendered next to the commit/build-time footer in app/layout.tsx. Shows the signed-in user's email; renders nothing when logged out.

Login fix: dedicated Google OAuth client + error visibility (2026-07-01)

Login button was silently failing live (POST /api/auth/sign-in/social returned 500) because genesis-helper's Dockerfile declared the generic GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET names, which collided with another app's existing OAuth client already occupying those keys in the shared hosting .env. Renamed to genesis-specific env vars (GENESIS_GOOGLE_CLIENT_ID, GENESIS_GOOGLE_CLIENT_SECRET) backed by a dedicated Google OAuth client, so the two apps no longer share credentials. Also added error handling to the sign-in flow (lib/auth-client.ts, components/layout/AuthButton.tsx) so a failed login now shows "Anmeldung fehlgeschlagen" instead of doing nothing.

Manual hosting steps required:

  1. Add GENESIS_GOOGLE_CLIENT_ID and GENESIS_GOOGLE_CLIENT_SECRET (dedicated client) to hosting .env on dark
  2. Remove the now-unused generic GOOGLE_CLIENT_ID/GOOGLE_CLIENT_SECRET entries from genesis-helper's env if they were only there for this app (leave untouched if another app still uses them)

Authentication + inline wiki editing (2026-07-01)

Added better-auth v1 with Google OAuth (same stack as hoplt). All character write endpoints (POST/PUT/DELETE) now require a valid session — returns 401 otherwise. New wiki content API (PUT /api/wiki/content) lets logged-in users overwrite any regelwerk markdown file in-browser. A "Bearbeiten" button appears on all prose wiki pages (prose-index, prose-section, prose-full) when signed in; clicking it opens the full document in a textarea for editing and saving. Sign in/out button added to the top navigation bar. Auth database uses the platform's shared Postgres (database: postgres in hosting manifest). New Dockerfile env vars: GENESIS_BETTER_AUTH_SECRET, GENESIS_BETTER_AUTH_URL, GENESIS_GOOGLE_CLIENT_ID, GENESIS_GOOGLE_CLIENT_SECRET.

Manual hosting steps required: (completed 2026-07-01 — see below)

  1. In darkguhl/hosting/apps/genesis-helper.yml: change database: nonedatabase: postgres
  2. Add GENESIS_BETTER_AUTH_SECRET and GENESIS_BETTER_AUTH_URL to hosting .env on dark

Hosting fix: postgres wiring + stale .env bind mount (2026-07-01)

Login was broken live because the two steps above were never actually applied: genesis-helper.yml still had database: none and hosting .env lacked the auth secrets, so the container booted with all GENESIS_*/DATABASE_URL env vars empty. Fixed by pushing database: postgres to the hosting manifest and redeploying. Also discovered the webhook container's single-file bind mount of hosting/.env had gone stale (different inode than the host file, from an atomic rename-on-save) and was serving old content — restarting the webhook container refreshed it before the redeploy picked up the real secrets. 3. In Google Cloud Console: add https://genesis.bpcloudtech.com/api/auth/callback/google as authorized redirect URI

Kindheit/Jugend: neuer kompositioneller Ereignis-Generator als Wizard-Schritte (2026-06-19)

Replaced the old W1000 Kindheit/Jugend tables with the new ruleset. lib/characters/kindheit-jugend.ts rolls each dimension individually: Anzahl Ereignisse (W6/2, aufgerundet, min 1), Ereignis (phase-gefiltert), Folge (W6 neg/neutral/pos), Schweregrad (W6 leicht/mittel/schwer), Personenkreis, Örtlichkeit (Typ-gemappt auf die Orte-Blöcke), Bezug, Auswirkung (positiv/negativ je Folge). One wizard step each for Kindheit and Jugend (app/characters/new/page.tsx), each page with multiple events; every field is an editable input with its own Würfeln-Button plus „Alles würfeln" je Ereignis (components/characters/KJEventCard.tsx). Events are stored as HintergrundEreignis (descriptive text, no attrMod). Data source: content/wiki/derived/kindheit-jugend.json (npm run kj:import).

Kindheit/Jugend ruleset imported (data + spec only) (2026-06-19)

Reverse-engineered the new Tabellen-Kindheit und Jugend.ods workbook (sheets Übersicht, Personengruppen, Aufzählung Ereignisse, Hauptblatt, Auslöser, Orte, Auswirkungen). Added scripts/import-kindheit-jugend.ts (npm run kj:import) which downloads the ODS and writes content/wiki/derived/kindheit-jugend.json (full structured dataset, German verbatim): Folge/Schweregrad W6 rules, 31 Ereignisse (with phase kindheit/jugend/beide, WS tags, +/- Auswirkungen), the Personengruppen tree (Bösewichte→Humanoide/Tier.Monster/Monster/Dämonisch, Förderer, Magier — 222 rows), 188 Orte across 8 Typ-blocks, and the Auswirkungen category lists. Schema + mechanic + quirks documented in docs/impl-kindheit-jugend.md. Wizard wiring deferred. NOT YET committed/pushed.

Charaktererschaffung: Attributblock ans Ende + editierbare Würfelwerte (2026-06-19)

Reordered the creation wizard so narrative steps (Aussehen, Geburt, Stand, Kindheit, Jugend, Fähigkeiten) come first, then the attribute block (würfeln + zuteilen), then attribute-dependent steps (Größe, Talente, Sprachen), then Zusammenfassung. All dice-thrown values are now editable input fields: the 8 attribute rolls, Körpergröße & Gewicht (BMI/Statur recompute live), and every Aussehen trait.

Aussehen: fehlende Würfe + Bartwuchs ergänzt (2026-06-19)

Added the missing appearance throws per char-aussehen.md: Strähnen, Augensprenkel/Asymmetrie/Umrandung, Bartwuchs (male-only, per-race threshold) and per-race extras (Kropf/Zähne, Chitinauswüchse, Hautfarbe des Menschenkörpers, Mähne usw.) across all races.

Geburt: volksspezifische Geburtsort-Tabellen (2026-06-19)

rollGeburtsort(volk) now uses each Volk's W20 Geburtsort table (fallback to the generic W6 logic); race categories map to the generic Auswirkungen, descriptive-only locations (Hazienda, Landvilla, Amtsgebäude, Vulkan) carry no invented boni. Generic Urban sub-table ranges normalised.

Stand: Vermögen-Werte exakt aus Regelwerk (2026-06-19)

Replaced the estimated/placeholder Vermögen with the exact formulas + equipment/Erbstücke from char-charaktererschaffung.md; W100 sub-table ranges verified against the rulebook. (Tier-Wurf-Mechanik selbst: siehe Deferred.)

Geburt: Geburtsumstände-Würfe ergänzt (2026-06-19)

The "Geburt" generator was missing the entire Geburtsumstände section from the Regelwerk. Added rollGeburtsumstaende(volk) to lib/characters/hintergrund.ts, surfaced via rollGeburt(volk): Zwillinge/Mehrlinge (W6, exploding), körperliche Behinderung (W20 trigger → W6 body region with full Kopf/Rumpf/Arm/Bein W20/W6 sub-tables), Tod der Mutter (W20 trigger → W20 cause), Vater unbekannt (W20), Vater verstorben (W20 trigger → W20 cause), Aussetzung (W20), Nichtanerkennung durch Vater (W20), Bastard (W20) and Geschwisteranzahl (per-Volk open-ended rolls; Zwillinge subtracted). rollGeburt now takes an optional volk arg (passed from the chargen wizard).

BMI-Talentmodifikatoren für Athletik- und WS-Talente (2026-06-19)

BMI-based talent modifiers now applied to character sheet at render time. Athletik talents (Fliegen, Klettern, Körperbeherrschung, Akrobatik, Tanzen) receive ±10/20/30 bonus; WS-Talente (Selbstbeherrschung, Zechen, Gifte Widerstehen) receive the mirror penalty, per Regelwerk rules. KO/GE attribute modifiers from BMI were already implemented (applyBMIModifier in chargen.ts); this adds the missing talent-level modifiers via a synthetic ModifierSource injected at render time.

Mobile: Pinned bottom nav für Charakter- und Regelwerk-Unterseiten (2026-06-19)

Fixed bottom navigation bar on mobile (< sm) for all character and Regelwerk sub-pages; uses inline position: fixed + bottom: 0 to prevent layout conflicts with the sticky header.

Kampf: LE negativ (Tot-Zustand), RS/BE kompakt, Kampfzustand-Karte vereint (2026-06-19)

LE bar can now go negative to represent death state. RS/BE stats collapsed into a single compact line. RS/BE and Kampfzustand merged into one card.

Charakterkopf: Titel editierbar, Körperdaten in Aussehen, Breadcrumb weg (2026-06-19)

Titel field is now inline-editable on the character header. Körperdaten moved into the Aussehen card. Character breadcrumb removed; Stufe badge repositioned to top-right.

Proben: Differenz inline, Vorzeichen korrekt (2026-06-19)

Dice result cards now show the difference (target − roll) inline next to the roll number; positive = success. Sign convention corrected across AT/PA/AW/Attributprobe.

Stufenanstieg: SP-Budget-Verteiler (2026-06-19)

Full level-up workflow: SP budget tracker with running "verbleibend" display, stat-steigerung (Abgeleitete Werte), Fähigkeiten-Kauf with ability browser, and Talente-Steigerung. History of past increases persisted per character.

Globale Entfernung der Number-Input Spin Buttons (2026-06-19)

Removed browser-default up/down spin arrows from all <input type="number"> elements site-wide.

Kampf: WS-Doppelschaden-Formel korrigiert + Wunden/Bewusstlos-Tracking (2026-06-18)

The SP-after-armor formula was previously 2×SP on Doppelschaden, which is wrong. The correct rule (Regelwerk §576): when SP > WS, the excess is deducted again — so total LE loss = SP + (SP − WS). Display now shows e.g. "15 +5 = 20 SP anwenden". A status sub-card in the Lebensenergie panel tracks accumulated Wunden (SP > 2×WS) and Bewusstlos (SP > 3×WS or LE < 15 %) during a combat session; sub-card is hidden when clean, values are manually correctable, and the whole thing resets on Kampf-Reset.

Porträt zurück in die linke Spalte, festes 3:4 (2026-06-18)

Moved the character portrait out of the right-column flex row (where it sat next to Ausrüstung with items-stretch, so its height — and thus aspect ratio — followed the Ausrüstung card) and back into the left sidebar as the topmost card, above the Attribute panel. PortraitCard now uses a fixed aspectRatio: 3/4 wrapper (no h-full/flex-1 fill), so the frame keeps a constant 3:4 at every viewport width. Ausrüstung/Inventar is full-width again.

Inventar: Boni-Zusammenfassung (2026-06-18)

Bottom-right of the inventory panel now shows a compact summary of all stat modifiers from currently equipped (and active) items. Mods are grouped by target, add and percent ops summed separately, color-coded green/red.

Inventar "Anlegen" — no slot-picker dialog (2026-06-18)

Clicking "Anlegen" on an inventory item now equips it directly to the first empty eligible slot (or the first eligible slot if all are occupied), without opening the slot-picker dialog. The picker dialog is still shown when clicking a slot tile in the paper doll.

2026-06-18 — Dice card redesign

  • Roll result number now same scale (text-2xl) as the target value in the top-right corner
  • Result layout: single line, verdict (Treffer/Fehlschlag) left, large number right
  • Removed ↺ recast arrow from all DiceButton instances
  • Removed "Grund (20 %)" row from KampfwertKarte and all MiniStat combat style blocks

Würfel-Karte auf der Hauptcharakterseite (2026-06-18)

New collapsible "Würfel" panel on the main character sheet. Defaults to one roll button each for W4, W6, W8, W10, W12, W20. Each card shows the die type, a count stepper (1–20), and the last result. Players can add further custom throws (any die type incl. W100, any count) and remove any throw individually.

Waffenloser Kampf panel on Kampf page (2026-06-17)

New collapsible panel "Waffenloser Kampf" covering all three unarmed/natural combat systems:

  • Raufen (always visible): 4 enhancement tiers (ohne Verstärkung / Schlagring / Eisenhand / Spezialeisenhand). Shows derived AT/PA/GW/TP with modifier badges. Rollable W100 AT and TP dice. −3 SP malus on bare-fist damage applied via ansageTp.
  • Dai Setzu (visible when character has Dai Setzu Fähigkeit): 4 styles (Basis / Madashinstil / Tigerkrallenstil / Drachenstil). Displays enhanced Super-AT/PA pool and Schockschaden info badge.
  • Aztraidenkrallen (visible when char.volk contains "aztraiden" or relevant Fähigkeit): 5 tiers (Basis / I–IV). Schlachtmeister bonus auto-applied to damage display.
  • Festhalten & Würgegriff sub-panel (shared): gegrappelt counter (0–3×) with penalty display, Festhalten-AT / Befreiungsversuch / Würgegriff / Spezielles Festhalten roll cards, Würgegriff damage tracker (+1/Runde manual button), and collapsible Regelhinweise.

State persisted to character document: raufenVerstaerkung, daiSetzuStil, aztraidenStufe, gegrappelt, wuergeDamage.

  • faehigkeitArt renamefart field renamed to faehigkeitArt across types, parser, API route, components.
  • RS/BE als first-class StatsapplyModifiers() gibt ruestung: { RS, BE } zurück; Rüstungs-Panel in der Sidebar zeigt RS und BE wenn Rüstung getragen wird.
  • Zauberbuch: MV-Budget, Macht-Maximum und Haupt/Nebengebietlernen-Seite zeigt MV-Verbrauch (used/total), Klasse-III-Zähler (used/Macht-Maximum = MV/8), Haupt-/Nebengebiet-Badge je Schule und Klassenverteilung pro Schule als reine Info-Anzeige; Hauptzauberbuch-Seite zeigt MV und Klasse-III im Zauberwerte-Panel sowie Haupt/Neben-Label bei jedem Schulabschnitt.
  • Karten ausblenden — Auge-Button zum Ausblenden von Panels; Zustand in hiddenCards[] persistiert.
  • Kampf: TP/GW-Klasse automatisch aus WaffendatenparseKampfKlassen() leitet TP- und GW-Klasse aus Waffentraits ab; Auto-Klassen-Badge statt Dropdown.
  • Desktop-Layout: Auto-fill-Grid + volle Breiterepeat(auto-fill, minmax(320px, 1fr)) für rechte Spalte; volle Viewport-Breite für Kampf, Stufenanstieg, Planung und Zauberbuch.
  • Charakter-Backup via GitHub API — Charakter-Backup via GitHub Contents API auf data-Branch nach jedem Speichern (debounced 10s).
  • Regelwerk Wiki — 56 Dokumente importiert mit Prosa-/Tabellen-/Browser-Renderern (Zauberbücher, Fähigkeiten, Waffenkammer).
  • Kampf: Steineffekte-Panel — Kampfeffekte als Badges, Proc-Würfelbuttons und Ladungs-Auslösen mit Persistierung via PUT.
  • Inventar: Verwenden-Button — Tränke/Pasten heilen LE/ME direkt; Kraftmittel erstellen temporäre ModifierSource.
  • Shop: alle 4 Ausrüstung-Blätter — Shop liest Alchemieladen, Waffenverbesserungen, Machtgegenstände und Magische Rüstgegenstände mit Typ-Badges und Blatt-Filter.
  • Modifier-Katalog: alle Einträge kodiert — Alle manuellen Einträge in applyManualPatches() kodiert mit neuen Effekttypen (Consumable, KampfEffekt, Proc, Ladung, ChoicePool, KampfAktion, Schild).
  • Modifier-Katalog: Qualitätsprüfung — False-positive Targets entfernt, Talent-Namen normalisiert, 35 manuelle Einträge transkribiert.
  • Stat-Modifier EngineapplyModifiers() recomputed alle Stats aus ModifierSource[] mit Cascading, Adds-vor-Percents und Expiry.
  • Aussehen + Stand/Vermögen — Wizard-Schritte für Aussehen und Stand mit Rassentabellen und W20-Standsrollen.
  • Sprachen — Sprachen-Wizard-Schritt mit Sprachgefühl-Regel und Sprachen-Panel auf Charakterbogen.
  • Stufenanstieg — SP-Budget mit Stat-Steigerung, Fähigkeiten-Kauf, Talent-Steigerung und Steigerungshistorie.
  • Charakterplanung — Multi-Level-Planungsseite mit Wert-/Talent-Steigerungen, Fähigkeiten-/Zauber-Picker und Auto-Save.
  • Stufenanstieg: Klassen-Stufengating — KL-II/III-Fähigkeiten erst ab Stufe 9/15 freigeschaltet; stufeErworben auf CharFaehigkeit gesetzt.
  • Talent-Probe W100 — Talent-Einträge klickbar mit inline W100-Würfelprobe (Erfolg/Fehlschlag farbkodiert).
  • Shop + Inventar — Waffenkammer-Shop, 18-Slot-Ausrüstung, Inventar und Custom-Fundgegenstände mit Stat-Mods und BE-Fächerung.
  • Zauberbuch — Per-Charakter Zauberliste mit SpellBrowser, Lernen-Button und Castbar (3W6 explodierend, Ansage-Stepper).
  • Fähigkeiten: Live-Bonifaehigkeiten-boni.json mit 397 Fähigkeiten/1212 Mods; Active-Toggle und Modifier-Chips je Fähigkeit.
  • Fähigkeiten: Slot-Kontingent + Stacking-Caps — Slot-Kontingent (max 8/6/3 je KL) und stärkste-Mod-pro-Bucket-Cap implementiert.
  • Kampf-Lookup-Tabellennpm run kampf:import erzeugt GW-/TP-Tabellen I–V; Engine-Funktionen in lib/characters/kampf.ts.
  • Kampfbogen → Live-Combat-Tracker — AT/AW/INI-Würfelbuttons, LE/ME-Balken mit Schaden/Heilen/Reset und Kampfzauber-Panel.
  • Kampf: Würfel-Rework — W100-Proben auf AT/PA/AW/F-AT mit Patzen-Automatik (2W6) und per-Waffe TP-Würfel via TP-Tabelle.
  • Kampf: Super-Treffer TP×2 + AT-Ansage — AT-Punkte gegen Bonus-TP tauschen; Super-Treffer TP×2-Button.
  • Kampf: Waffen-Besonderheiten mechanisiert — Schlachtmeister, Durchschlagskraft, Wucht, Phalanxwaffe u.a. via parseWaffenBesonderheiten().
  • Volltextsuche — Header-Suchfeld mit Umlaut-Folding, AND-Semantik und Titel-Boost über alle Regelwerk-Inhalte.
  • Zauber: Sonderresultate — Außergewöhnlicher Erfolg, Patzer und Schwerer Patzer via classifyErgebnis().
  • Zauber: „Wertigkeit" substituierensubstituteWertigkeit() ersetzt Platzhalter durch ZW-Wert im Prosatext.
  • Zauber: Wirkungsformel + Ansage-BoostresolveEffektFormel() löst Formel auf und zeigt ZW-Boost-Differenz.
  • Zauber: Ausführungszeit in Sekunden — Ausführungszeit via ZS in Sekunden berechnet und angezeigt.
  • Zauber: Wirkungsdauer mit ZRWresolveWirkungsdauer() löst ZRW-Platzhalter in Wirkungsdauer auf.
  • Zauber: a)/b)-Varianten-Toggle — 30 Zauber mit Kurz-/Langzeitvariante per a|b-Toggle in der Chip-Leiste.
  • Kampf-Karten: einheitliches LayoutDiceCard-Komponente mit festen Quadranten und gemeinsamer Typo-Skala für alle Würfelkarten.
  • Abenteuer-Log — Neue Seite /characters/[id]/abenteuer mit Sitzungsprotokoll, Kampfprotokollen und Auto-Save.
  • Charakter-Porträt — Porträtbild als URL oder Upload (base64, 3:4-Crop) in der linken Sidebar mit Hover-Löschen.
  • Ausrüstung & Inventar zusammengelegt (Diablo-Style) — Paper-Doll mit 18 Equip-Slots und Masonry-Inventar in einem Panel zusammengelegt.
  • Item-Qualitätsfarbe: Preis-FallbackeffektiveKlasse() leitet Qualitätsfarbe aus Preis ab wenn keine explizite Klasse gesetzt ist.
  • Login-Gate für alle Charakterbogen-Werte — Alle Unterseiten unter /characters/[id]/* sind für ausgeloggte Besucher schreibgeschützt (EditGuard/useCanEdit); Server-Endpunkte prüften die Session bereits. Neu: Sheet-weiter "Werte bearbeiten"-Toggle macht Attribute + alle abgeleiteten Werte (physical/magic/mech) direkt überschreibbar, statt nur über Boni-Quellen.