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.

Abenteuer-Historie nicht aus Remote-Sessions erreichbar

  • Die bisher gespielten Abenteuer (inkl. KI-Zusammenfassungen) liegen in Dropbox, sind aber weder im Sync-Manifest (scripts/sync-docs.ts / docs/CONTENT-MAP.md) noch aus einer Remote-Session zugänglich — dort fehlen DROPBOX_APP_KEY / DROPBOX_APP_SECRET / DROPBOX_REFRESH_TOKEN. Nötig für Kontinuität bei der Abenteuerplanung. Optionen: Ordner ins Sync-Manifest aufnehmen, oder ein kleines dropbox:ls-Skript zum lokalen Auflisten ergänzen.

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

Email/password login (2026-10-05)

The login icon now opens a panel with email + password and the Google button. Public sign-up is off; accounts are created by the admin with npm run user:add -- <email> <password> [name], which calls POST /api/admin/users (API key only) and also resets passwords or adds a password to an existing Google user. The GENESIS_ALLOWED_EMAILS allowlist still applies to both login methods.

Talentring: choose the talent group on purchase (2026-10-02)

Buying a ring with a generic "Talentgruppe" bonus (Einfacher/Meisterlicher/Mächtiger Talentring) now opens a picker for the talent group. The item is stored as e.g. "Talentring (Fingerspiel)" with the mod targeting the group name; the modifier engine fans a group-name target out to every talent in that group.

Ragged wiki tables render correctly (2026-10-02)

WikiTable now pads short rows (title lines, stat blocks, notes) by letting the last cell span the remaining columns, so rows are fully filled and long note text wraps instead of stretching the first column. Line breaks inside cells are preserved.

Machtgegenstand equipment slot (2026-09-25)

New machtgegenstand equipment slot (next to the weapons in the paper doll) so Machtgegenstände can be equipped with "Anlegen" and their bonuses apply like other gear. The shop now shows the Machtgegenstand category badge.

Zusatz editor in the Zauberbuch (2026-09-25)

Each learned spell card in "Meine Zauber" has a "+ Zusatz" link (or ✎ on an existing Zusatz) that opens a textarea to add, edit or clear the character-specific spell addition.

Per-character spell additions (2026-09-25)

Learned spells (CharSpell) take an optional zusatz text for GM-granted improvements; it shows as a "Zusatz" box on the Zauberbuch and Kampf spell cards. Torvin's Heilblitz: doubled healing below 25% LE.

Zauberbuch spell cards in two columns (2026-09-25)

"Meine Zauber" lays out spell cards in a two-column grid from lg up instead of stacking them full width; single column on smaller screens.

Character notes keep single line breaks (2026-09-25)

Notes are rendered as Markdown, which folded single newlines into one paragraph, so songs and poems lost their line structure. MarkdownNotes now parses with breaks: true, passed per call so the wiki renderers stay unchanged.

Adventure 01: the motive is a demonic pact (2026-09-12)

Falsified ledgers were too small a motive for the head of an order. Vey now follows the payments to what they bought: Marsonne had hired a demon. Vey copied the evidence and hid the copy before they came for him. Marsonne noticed and set the Orden der Wahrheit — the party priest's own order — on a heresy that did not exist, charging Vey with "Umgang mit Unreinem", which is Marsonne's own crime. After the burning he sealed the demon into the place along with the debt he owed it. That open pact is why the place still collects permanent LE from anyone who falls in, and why the accuser asks at all: he wants his debtor's name. New find in the crypt, behind a loosened stone: Veys Abschrift, the pact terms in Marsonne's hand — it answers everything and cannot leave the ward, which keeps the party short of court-usable proof. The second seal under the verdict is now an investigation order to the Orden der Wahrheit obtained by the Kustos.

Adventure 01: victim and antagonist named (2026-09-12)

The burned heretic is Anselm Vey, a Zeuge (third rank) of the Orden des Sehens who kept the order's ledgers and found a year that did not balance. The Kustos of that administration, Aurelian Marsonne, had him charged and burned, then sealed the demon he had used into the place and had Vey's name scratched out everywhere. Marsonne is today Hochseher and head of the order, styled Das Erste Auge. Assumption flagged to the GM: the party priest's Orden der Wahrheit sits under the Orden des Sehens, so Marsonne outranks him — that keeps the established follow-up thread intact and is a one-line change if the GM wants two separate orders instead. Both documents updated: the name is now the answer to the accuser's first question, the ledgers are the order's own, the second seal on the verdict is Marsonne's approbation as Kustos, and the song was commissioned about an Ordensbruder rather than a generic scribe.

Adventure 01 reduced to the adventure itself (2026-09-12)

The GM's standing instruction for this document: it describes the adventure, short and precise, and carries no GM-technique rules unless asked for. Everything else is out of both the markdown and the artifact — the "the GM never narrates" section and its do/don't table, the three-clues-per-conclusion rule, the stall-relief guidance, the minute-by-minute run sheet and its emergency brakes, the "GM knowledge" and "why the bard goes last" notes, the permanent-LE death-spiral discussion, plus the whole planning record (theme idea pool, rejected casting rules, first-round pitches, continuity checklist, open-questions sections). What remains: starting situation, the place, the repeating day, the two kinds of demon, what actually happened, where the evidence lies, the reconstruction by date, the four roles with trail and penance, the candles, the exit, and the follow-up thread. Markdown went from ~600 to 206 lines. The removed planning history stays retrievable from git at 6c306bc.

Adventure 01: four found objects reconstruct the crime (2026-09-11)

Three more GM corrections. (1) Remaining design commentary removed from both files — the house-rule box justifying the no-clock decision, "why this version is better" rationale, "the best moment of the adventure", and similar. What stays is what happens at the table. (2) The four commission documents no longer sit in the players' own equipment: they are hidden at four locations found by day — the notice board by the pillory, behind the bar in the tavern, on a music stand in the house with the open window, and the sacristy cupboard. Since each carries a name, the finder is usually not the owner and reads a fellow player's name aloud. (3) Each document carries a date, and laying the four in order is the reconstruction: the song was commissioned and paid for before any charge was laid, then the bounty went up with the instruction not to listen to him, then the escort was settled, then a confirmation signature closed it. It was never a trial, it was a procedure, and it was finished before it began. The GM never sorts them — he reads out a date when asked, and nothing else. Manual line breaks removed from both files.

Adventure 01: investigation redesign, rule references stripped (2026-09-11)

Two corrections from the GM. (1) The adventure was a lecture: the midnight demon narrated each player's role and the GM handed out findings, so nobody deduced anything. Rebuilt the discovery layer around a stated house rule that nothing about the past is ever spoken — not by the GM, not by an NPC, not by the demon. The accuser now asks exactly one question per midnight instead of accusing, and steps back from the door for each truth the party speaks aloud, which makes him a lock and a visible progress track rather than a narrator. Findings became physical, individually inconclusive objects with a three-independent-clues rule behind each of the four conclusions, and the four commission notes are no longer handed over: they sit in the players' own equipment from the first minute and are only found if someone searches their own gear. Stall relief opens a location, never supplies a fact. (2) All rulebook references and rules commentary removed from both the markdown and the artifact — no source-file citations, no stat guidance, no formulas. The mechanics that belong to this adventure stay; the system talk is gone.

Adventure 01 cut to one session, hard variant locked (2026-09-11)

Two GM decisions worked into abenteuer/abenteuer-01.md and the artifact. (1) Scope: one sitting of roughly four hours instead of two sessions, so the structure drops from 5–7 passes to four. Added a run sheet with a minute budget per pass, the compressions that buys (two accusations per midnight from pass 2, exactly one fight, overlapping penances, three of six findings handed out rather than searched for), and a table of safety valves for when the clock bites. The minute budget is explicitly table craft, not an in-fiction mechanic — the no-wall-clock rule still holds inside the game. (2) Casting hardness: hard, but the party is not culpable. All four really were the four hands of this execution, and each was recruited by the same corrupted prelate, who gave each of them the one lie that stopped him asking. This makes the accuser's charges true but incomplete (a Täuscher deceives by omission, and four charges sharing one blank space is how the group cracks the case), turns the follow-up thread into every player's personal score rather than only the priest's, and adds "ich wusste es nicht" alongside "es tut mir leid" as something the altar refuses. Prep item added: four written commission notes handed out as each role is named.

Adventure 01 published as an artifact (2026-09-11)

The GM-facing read of abenteuer/abenteuer-01.md is now a standalone page: https://claude.ai/code/artifact/8f4ff900-a3f8-45e1-8099-b0d7189a0b5f (link recorded in the markdown header). Carries the frame, the casting rule, the Bannraum, the location, the Durchgang cycle, the Ankläger, both remaining tasks, the resolution and the follow-up thread — the rejected casting rules and the theme-selection archive stay in the markdown only. Also fixed a contradiction found while building it: the milestone of speaking the heretic's name and the four penances were each lighting one of the same four altar candles. The name now unlocks the tracker (before it, every flame gutters out immediately) and the four candles belong to the four penances alone.

Adventure 01 fully structured (2026-09-11)

abenteuer/abenteuer-01.md now carries the whole adventure: the GM fixed the frame (wake in the church without memory, burnt pyre on the forecourt, demons at the edge, death costs 1 permanent LE, a greater demon kills everyone outside the church at midnight with forewarning, tasks are recognise-the-injustice → recognise-your-role → penance, everyone gets out, bonus thread is a corrupted high churchman). Added the world's in-fiction explanation anchored in 13-kultisten: the place is a Bannraum ("Bannen bedeutet, einen Dämon in einer bestimmten Artefakträumlichkeit gefangen zu setzen"), which also explains the permanent LE drain as pact currency ("Aufopferung permanenter Werte") and turns the malus into a clue. The midnight demon is designed as a Täuscher-type accuser who delivers the plot instead of fighting — the party learns its roles by being accused through the church door. Per-role investigation trails, penances, a four-candle progress tracker, the resolution, and the evidence chain for the follow-up thread are all in. Flagged: 17-monsterbuch has no demon entries, so demon values must be built (13-kultisten gives Stufe 13+ as the anchor), and the permanent LE loss needs a floor to avoid a death spiral.

Adventure 01 theme locked: „Der Ketzer war im Recht" (2026-09-11)

abenteuer/abenteuer-01.md restructured around the chosen theme — a trap-world born from the burning of an innocent heretic, centred on the church and the forecourt where the pyre stood. Main content is the answer to the design question of why the three non-priest players are in it: five candidate rules ranked, with the recommended one casting the party as the four links of an execution (Häscher / Richter / Vollstrecker / Erzähler), which puts the bard on the escape condition because the surviving story, not the fire, is what sustains the world. Two hardness variants (with/without biography retcon) left open for the GM. The idea pool and the first-round pitches moved to archive sections. Flagged in-document that the no-wall-clock-mechanics rule applies: any loop or progress counter is GM-set, never real time.

Adventure planning: focus rule + idea pool (2026-09-11)

abenteuer/abenteuer-01.md now leads with an idea pool instead of full pitches. The GM set the selection rule that the adventure must centre on one of the two players carrying the group — the Priester des Orden der Wahrheit or the Mechaniker/Myndralbarde — so the document lists 12 priest-led themes, 12 machinist/bard-led themes, and 5 that carry both, one line each, for sieving. The six worked-out pitches from the first round are kept below as a second section, annotated with which ones survive the new rule (E drops: it is carried by the Kasskarer). Recommendation updated accordingly.

Adventure planning workspace: abenteuer/ (2026-09-11)

Added abenteuer/abenteuer-01.md — a GM-facing planning document for the next campaign adventure (pure content, no app code). Holds the full party roster (Jäger/Waldläufer, Priester des Orden der Wahrheit, Kasskarer Barbar, Mechaniker/Myndralbarde), a working definition of the Orden der Wahrheit placed inside the existing canon (inquisitorial arm of the Kirche des leuchtenden Gottes), six themed adventure pitches grounded in the wiki setting (Aldgarth borderlands, Schattensümpfe, Frostmark/Baradun, Barbarensteppe/Drachenmark, Dugroschim zeppelin salvage), a comparison matrix scoring every pitch per character, and the open questions blocking the detailed write-up. Two rules-derived hooks are called out for the GM: the Maschinistische Lethargie (03-maschinisten) makes the machinist and the bard halves of char 4 mechanically compete, and the Kasskarer's kinship with Sherkan's Drachenreiter turns the Barbarensteppe pitch from scenery into a personal stake. German, per the content rule.

Removed redundant tooltips from the equipment slots (2026-09-04)

Each paper-doll slot tile carried title={SLOT_LABELS[slotId]} while already rendering that exact label as visible text above the item name, so hovering a slot popped a native tooltip that said nothing new. Dropped the title; the visible label also keeps the button's accessible name intact.

Inventory delete "✕" pinned to the card's top right (2026-09-04)

The remove button was the last child of the item card's bottom action row, which is flex-wrap — with "Verwenden"/"Anlegen"/"✎" ahead of it in a narrow column it wrapped onto its own line, so the ✕ drifted around the lower right. It now sits in the header row as a shrink-0 sibling of the name/badges column, so it is always flush top-right regardless of how the badges or action buttons wrap. The action row is now only rendered when it actually has buttons (hasActions), avoiding an empty 8px strip on plain items.

"+ Fundgegenstand" no longer requires edit mode (2026-09-04)

The button that opens CustomItemForm in the "Ausrüstung & Inventar" card was gated on editMode, so looting an item mid-session meant toggling the nav padlock first. EquipmentInventoryPanel now takes a separate canEdit prop (from useCanEdit(), i.e. any logged-in user) and gates the button on that. editMode still gates the per-item "Bearbeiten" action on custom items.

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 rollTpWurf→rollTpDamage, gwAusweichen→gwDodge, GwAttack.anzahl→count.

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 (CharFaehigkeit→CharAbility, StufenanstiegEintrag→LevelUpEntry, GebundenesWesen→BoundCreature, Zauber→Spell, Faehigkeit→Ability, Klasse→ItemTier, …); module files (lib/characters/steigerung.ts→level-up.ts, faehigkeit-kontingent.ts→ability-contingent.ts, lib/wiki/faehigkeiten.ts→abilities.ts, waffenkammer.ts→armory.ts, zauber.ts→spells.ts, …); components (WuerfelPanel→DicePanel, TalentVerteilung→TalentDistribution, FaehigkeitenIndex→AbilitiesIndex, …); 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.ts — neueStufe = (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: none → database: 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 rename — fart field renamed to faehigkeitArt across types, parser, API route, components.
  • RS/BE als first-class Stats — applyModifiers() 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/Nebengebiet — lernen-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 Waffendaten — parseKampfKlassen() leitet TP- und GW-Klasse aus Waffentraits ab; Auto-Klassen-Badge statt Dropdown.
  • Desktop-Layout: Auto-fill-Grid + volle Breite — repeat(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 Engine — applyModifiers() 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-Boni — faehigkeiten-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-Tabellen — npm 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" substituieren — substituteWertigkeit() ersetzt Platzhalter durch ZW-Wert im Prosatext.
  • Zauber: Wirkungsformel + Ansage-Boost — resolveEffektFormel() 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 ZRW — resolveWirkungsdauer() 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 Layout — DiceCard-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-Fallback — effektiveKlasse() 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.