Jump to content

Thousand Year Old Vampire: Difference between revisions

From Yusupov's House
No edit summary
Line 534: Line 534:


<blockquote>
<blockquote>
He has not died. Let me say that plainly. There will be no grave. He is alive, but quietly, deliberately, and utterly outside your reach.
He has not died. Let me say that plainly. There will be no grave, no crumbling tomb, no name worn smooth by time. He is alive—quietly, deliberately, and utterly outside your reach.


When I first met him, I did not understand what he was. He wore his years like dust on old vellum—thin, barely visible, but ever present. [...] Over time, I became his mirror, then his partner, and finally his historian.
This book is not a memorial. It is a reckoning.
 
When I first met him, I did not understand what he was. He wore his years like dust on old vellum—thin, barely visible, but ever present. [...] Over time, I became his mirror, then his partner, and—finally—his historian.


But someone has to begin the telling. Someone has to place the first stone. So this book begins in a house of quiet gardens and low ceilings, where he writes in the mornings and feeds only when he must. [...]
But someone has to begin the telling. Someone has to place the first stone. So this book begins in a house of quiet gardens and low ceilings, where he writes in the mornings and feeds only when he must. [...]
Line 545: Line 547:
</blockquote>
</blockquote>


==== Statistics ====
==== A Life in Summary ====
 
{| class="wikitable"
|-
! Metric !! Count
|-
| Total memories || 16
|-
| Active memories || 5
|-
| Diary memories || 3
|-
| Lost (forgotten) memories || 8
|-
| Total experiences || 37
|-
| Skills || 11 (3 lost during play)
|-
| Resources || 10 (6 lost during play)
|-
| Marks || 2
|-
| Known characters || 11 (6 lost during play)
|}
 
==== Active Memories (at end of game) ====


{| class="wikitable"
Karmiš's story spans roughly three and a half thousand years. It begins in Bronze Age Mesopotamia, where the mortal scribe Naram serves in the temple of Ishtar at Mari—writing omens, navigating palace intrigue, and falling in love with a woman named Hessa who moves through the court "like smoke." His mentor, the High Priest Ibbi-Zamri, is the first person he kills after being turned by the ancient vampire Ashurban the Veiled.
|-
! Memory Title !! Location !! Experiences
|-
| Memory 1 || Active || "Mortal life" (Initial), "The Name Given in Ash" (24a)
|-
| The Thorn and the Quill || Active || 1 experience (35a)
|-
| The Light That Does Not Burn || Active || "The Dawn at Mirelde's Fire" (52a), "The Dust in the Foundation" (57a)
|-
| We Were Not Counted Among the Cargo || Active || "The Bellies of Ships and Men" (64a), "Ink Without Meaning" (66a)
|-
| Echoes Before the Self Was Named || Active || "The Weight in the Smoke" (71a), "The Clay That Knew My Name" (68a)
|}


==== Diary Memories ====
The early centuries are defined by guilt and exile. Fleeing with his mortal companion Ennatum, the newly-made vampire—now calling himself Ekurzu—struggles to control his hunger. He builds walls and plays flutes in the wilderness, but it is "never penance, only a cage with softer bars." Eventually the hunger wins: he devours Ennatum during a sandstorm and spends the following decades alone.


{| class="wikitable"
The middle game carries the character through the great civilisations of antiquity—Amarna under Akhenaten, the Achaemenid Royal Road, the libraries of Alexandria—always in the role of scribe, forger, or quiet observer. He accumulates skills like ''Silent Cartography'' ("I trace the unseen paths between people, places, and power") and ''Ash-Tongue'' ("I speak in soot and suggestion, in the cracks between laws"), but loses others as the centuries erode his identity: the ability to ''Decipher Ancient Texts'' and even ''I Control the Beast'' both slip away.
|-
! Memory Title !! Experiences
|-
| The Hill That Waits || "The Hollow Beneath the Cairn" (40a), "The Ring Beneath the Clay" (43c), "The Face Returned Through Time" (44a)
|-
| Ash of the Ledger || "The Last Ledger Vanishes" (38a), "The Fog Beneath Names" (39b), "The Weight of Unspoken Words" (34a)
|-
| The Fever-Ledger || "The Illness in Karkheda" (36a), "The Name That Returns in Ash" (41c), "The Echo That Cannot Translate" (47a)
|}


==== Lost Memories ====
A pivotal relationship develops with the scholar Thoöni, who recognises the name ''Ekurzu'' not as myth but as continuity. Later—after Thoöni's mortal death—her spectral echo begins appearing in the margins of his diary, scratching fragments in languages he can no longer read.


Eight memories were forgotten over the course of the game, including "The Forged Omen" (Memory 2), "Hessa's Last Message" (Memory 3), "Duel of Stars and Signs" (Memory 4), "The Turning", "The Name That Cannot Burn", "The Blood Between Accusations", "Trade Beneath Empire", and "What Was Meant to Last". These losses illustrate the game's core mechanic: as new experiences accumulate, the vampire is forced to sacrifice older memories, creating a poignant sense of identity erosion across the centuries.
By the late Roman period Karmiš has lost so many memories that he can barely hold a conversation. The game's memory mechanics enforce this viscerally: eight of his sixteen memories were permanently forgotten, including ''The Turning'' itself—the vampire no longer remembers how he was made. He compensates by keeping diaries, but even those are lost: ''The Walls of Forgetting'' (memory carvings in five scripts beneath a shrine near Carchemish) and ''The Monastery Diary'' both vanish, taking their stored memories with them.


==== Skills ====
The final act takes place in early modern Europe. Karmiš witnesses the Atlantic slave trade first-hand and begins compiling ''The Margins of Manifest''—a cipher-folio of names taken from shipping manifests and punishment ledgers. His response to prompt 66a captures the shift:
 
{| class="wikitable"
|-
! Skill !! Status !! Description (excerpt)
|-
| Ash-Tongue || {{Checked}} || "I speak in soot and suggestion, in the cracks between laws."
|-
| Bloodthirsty || {{Lost}} || "The scent of mortal blood calls to me like a hymn in the dark."
|-
| Ceremonial Composure || {{Checked}} || "In the presence of gods or kings, my face is unreadable."
|-
| Decipher Ancient Texts || {{Lost}} || "I can read languages long dead and spot forgeries."
|-
| I Control the Beast || {{Lost}} || "When the thirst rises, I do not flinch. I meet the monster's gaze."
|-
| Ledger Without End || Normal || "Born from the quiet rituals of inventory and account."
|-
| Physick of the Pale Vein || Normal || "I feed gently, beneath the guise of care."
|-
| Remain in Unknowing || {{Checked}} || "I do not flee the edges of memory."
|-
| Silent Cartography || {{Checked}} || "I trace the unseen paths between people, places, and power."
|-
| Snare and Stillness || {{Checked}} || "I move with silence that makes mortals forget they heard me."
|-
| Tongue of the Unnamed || Normal || "I no longer understand the languages of my past, but I wear the sounds of others like masks."
|}
 
==== Resources ====
 
{| class="wikitable"
|-
! Resource !! Type !! Status !! Notes
|-
| Kept Against Forgetting || Portable / '''Diary''' || Active || The character's diary; written with Mirelde
|-
| Tablet Bearing My Name || Portable || Active || A clay tablet from the ancient past, rediscovered
|-
| The Margins of Manifest || Portable || Active || A cipher-folio cataloguing names of the enslaved
|-
| Burrowed Sanctum || Portable || Lost || An earthen chamber beneath a forgotten altar
|-
| Chamber of Whispers || Stationary || Lost || A hidden storeroom beneath the temple of Ishtar
|-
| Clay Tablets and Reed Stylus || Portable || Lost || Writing implements used by instinct, not understanding
|-
| Hidden Archive Tablet || Portable || Lost || A tablet inscribed with dynasty-breaking truths
|-
| The Monastery Diary || Portable / Diary || Lost || A former diary, lost when the monastery fell
|-
| The Silent Authority || Portable || Lost || A carved seal whose symbols can no longer be read
|-
| The Walls of Forgetting || Stationary / Diary || Lost || Memory carvings in five scripts beneath a forgotten shrine
|}
 
==== Known Characters ====
 
{| class="wikitable"
|-
! Name !! Type !! Relationship !! Status
|-
| Ashurban the Veiled || Immortal || Neutral || Active
|-
| Mirelde of Bracha || Immortal || Friend || Active
|-
| The One Who Does Not Answer || Mortal || Neutral || Active
|-
| Thoöni (spectral) || Immortal || Neutral || Active
|-
| Belatu || Mortal || Rival || Lost
|-
| Ennatum || Mortal || Friend || Lost
|-
| Hessa || Mortal || Love || Lost
|-
| Ibbi-Zamri || Mortal || Mentor || Lost
|-
| Mekha || Mortal || Enemy || Lost
|-
| Pelagon || Mortal || Enemy || Lost
|-
| Thoöni (scholar) || Mortal || Friend || Lost
|}
 
==== Marks ====
 
{| class="wikitable"
|-
! Mark !! Description
|-
| The Unblinking Eye || An ancient eye-like scar below the collarbone; burns faintly in the presence of lies
|-
| The Gesture Forbidden || An involuntary three-fingered gesture of silence, often mistaken for mockery
|}
 
==== Example Experience: "The Bellies of Ships and Men" (Prompt 64a) ====


<blockquote>
<blockquote>
They arrived in numbers too vast to count: pressed between barrels, chained to holds slick with piss and seawater, branded, sick, broken. And the sailors were not much better.
''It is no longer a question of restraint. That was a younger version of me—one who believed self-control made me different from the others. Now I am different in purpose, not method. I feed from slavers and call it justice, but the blood tastes the same.''
</blockquote>


The ships would come in [...]
His companion in the final centuries is Mirelde of Bracha—an immortal who becomes his mirror, partner, and ultimately his historian. Together they rediscover a clay tablet bearing his original name in a Venetian bookseller's back room (prompt 68a, 1704 CE), and in a ruined estate outside Smyrna Mirelde finds the remains of a life Karmiš can no longer remember.
</blockquote>


''Date: around 1510 CE''
At the story's end Karmiš retains only five active memories, three diary memories, two marks (''The Unblinking Eye''—a scar that burns in the presence of lies—and ''The Gesture Forbidden''—an involuntary court gesture mistaken for mockery), and three possessions: his diary ''Kept Against Forgetting'', the clay tablet bearing his name, and the slave-trade folio. Of the eleven characters he encountered, only four remain: Ashurban (his distant maker), Mirelde (his companion), Thoöni's ghost, and a nameless figure known only as ''The One Who Does Not Answer''. Over 34 prompts he accumulated 37 experiences, gained and lost a memory slot each, and burned through three separate diaries.


This experience is part of the memory "We Were Not Counted Among the Cargo", illustrating the vampire's witness to the Atlantic slave trade—one of many historical eras the character passes through during a millennium-spanning story.
The character's story is narrated in the epilogue not by Karmiš himself but by Mirelde, who insists: "He has not died. [...] Just a man who remembered too much, for too long. And chose, in the end, ''peace''."


== Testing ==
== Testing ==

Revision as of 11:21, 12 April 2026

Infobox
nameThousand Year Old Vampire – Web Helper
developerMichel Vuijlsteke
programming languagePython (Django 5), TypeScript (Vue 3)
operating systemCross-platform (Web)
genreSolo RPG Digital Companion
licensecustom
websitehttps://tyov.yusupov.cloud

Thousand Year Old Vampire – Web Helper (TYOV-Web) is a modern web-based implementation of the solo tabletop role-playing game Thousand Year Old Vampire by Tim Hutchings. The application digitises the game's complex mechanics of memory, experience, and character development, allowing players to create and guide a vampire character across centuries of unlife through an interactive browser interface. The backend is built with Django 5 and Django REST Framework; the frontend is a Vue 3 single-page application.

Overview

Thousand Year Old Vampire is a solo storytelling RPG in which a player creates a vampire character and guides it through an increasingly fragmented existence spanning hundreds or thousands of years. The central tension lies in the vampire's deteriorating memory: as an immortal being it accumulates experiences, but its ancient mind can only retain a limited number of memories at any time. Players must constantly choose what to remember and what to forget.

TYOV-Web fully implements the game's rules—character creation, dice-driven prompt navigation, memory management, diary storage, skills, resources, marks, known characters, and game-ending conditions—in a reactive web interface with persistent server-side storage and a complete audit trail.

Game Rules

Core Concept

The player creates a vampire and progresses through numbered prompts (story scenarios). Each prompt asks the player to narrate what happens to the vampire during a particular era. The game is non-linear: dice rolls determine which prompt to visit next, and most prompts have multiple variations (A, B, C) to prevent repetition.

Character Creation

Character creation follows a guided six-step wizard:

Step Description
1. Mortal Life Establish the vampire's name, background, and mortal existence
2. Mortal Characters Define 2–4 NPCs from the vampire's human life (friends, mentors, rivals, lovers)
3. Skills Choose 2–3 starting skills representing mortal expertise
4. Resources Select initial possessions and locations
5. Combination Experience Write a pivotal mortal-life experience that ties characters, skills, and resources together
6. The Turning Narrate how and why the character was turned into a vampire

Memory System

The memory system is the heart of the game:

  • A character may hold a maximum of 5 active memories (adjustable by prompt rules and permanent effects).
  • Each memory can contain up to 3 experiences (individual narrative entries).
  • When the limit is exceeded, the player must forget (permanently lose) a memory, or move one to a diary (if the character possesses one).
  • A diary is a special resource that stores up to 4 additional memories outside the active limit.
  • If the diary resource is lost, all memories stored in it are also lost.
  • Some prompts grant or remove permanent memory slots (memory_slot_gain, memory_slot_loss).

Dice Rolling and Prompt Navigation

Each turn the player rolls two dice:

  • D10 (1–10) minus D6 (1–6) = result (range: −5 to +9).
  • A positive result moves forward that many prompts.
  • A negative result moves backward.
  • A result of zero stays at the current prompt number but selects the next unused variation.

Variations are visited in order A → B → C. If all variations of a prompt have been used, the system finds the next available variation in prompt order.

Turn Structure

Each game turn follows this sequence:

  1. Prompt Presentation – The current prompt text and any special rules are displayed.
  2. Dice Roll – The player rolls D10 − D6 to determine the next prompt.
  3. Experience Creation – The player writes at least one narrative experience for the current prompt.
  4. Character Updates – Skills, resources, marks, and known characters may be added, edited, checked, or removed.
  5. Memory Management – The player resolves any memory overflow (forget or move to diary).
  6. Validation – The system verifies all rules are satisfied (at least one experience added, memory limits respected, etc.).
  7. Continue – All pending changes are atomically committed and the game advances to the next prompt.

Character Attributes

Attribute Description
Skills Learned abilities; can be normal (latent), checked (used/active), or lost (permanently removed).
Resources Possessions or locations; typed as portable or stationary. One resource may be flagged as a diary.
Marks Physical or psychological scars that accumulate over time.
Known Characters NPCs the vampire has encountered; typed as mortal or immortal; may be lost (dead, vanished, etc.).

Special Prompt Rules

Certain prompts carry special mechanics encoded as rules:

Rule Effect
no_experience Experience creation is disabled for this turn
allow_name_change The player may change the vampire's name
memory_modification The player may edit the text of existing experiences
memory_slot_loss Permanent reduction of memory capacity by 1
memory_slot_gain Permanent increase of memory capacity by 1
game_over The game ends; the player writes an epilogue

Game Ending

The game ends when the player reaches a prompt with the game_over rule (or under other conditions defined by the original game). The player writes an epilogue—a final reflection on the vampire's story—and the character is archived. A statistics summary shows prompt count, memories, experiences, skills, resources, marks, and characters accumulated during the playthrough.

Architecture

Technology Stack

Layer Technology Version
Backend framework Django 5.0.6
REST API Django REST Framework 3.15.1
Authentication Simple JWT (custom) 5.3.0
CORS django-cors-headers 4.3.1
Image handling Pillow 11.3.0
Production server Gunicorn 21.2.0
Frontend framework Vue.js 3.5
State management Pinia 3.0
Routing Vue Router 4.5
HTTP client Axios 1.10
CSS framework Bootstrap 5.3
Build tool Vite 7.0
Language TypeScript 5.8
Testing (backend) pytest + pytest-django 8.0 / 4.8
Testing (frontend) Playwright 1.53

High-Level Diagram

┌─────────────────────────────────────────────────┐
│               Vue 3 SPA (TypeScript)            │
│  ┌──────────┐ ┌──────────┐ ┌──────────────────┐│
│  │  Views   │ │Components│ │  Pinia Stores     ││
│  │ (7 pages)│ │ (14+)    │ │ auth/game/theme/  ││
│  │          │ │          │ │ characterCreation  ││
│  └────┬─────┘ └────┬─────┘ └────────┬─────────┘│
│       └─────────────┴────────────────┘          │
│                     │ Axios                     │
│                     ▼                           │
├─────────────────────────────────────────────────┤
│              Django REST API                    │
│  /api/auth/login    /api/auth/verify            │
│  /api/characters/   (CRUD + game_state,         │
│   roll_dice, continue_story, audit_trail)       │
│  /api/characters/<id>/memories/                 │
│  /api/characters/<id>/experiences/              │
│  /api/characters/<id>/skills/                   │
│  /api/characters/<id>/resources/                │
│  /api/characters/<id>/marks/                    │
│  /api/characters/<id>/characters/               │
│  /api/characters/<id>/pending-changes/          │
│  /api/prompts/                                  │
├─────────────────────────────────────────────────┤
│          Django ORM / SQLite                    │
│  Character · Memory · Experience · Skill        │
│  Resource · Mark · GameCharacter · Prompt        │
│  PendingChange · GameStateSnapshot              │
└─────────────────────────────────────────────────┘

Backend

Django Apps

The project is organised into three Django apps plus a settings module:

App Purpose
vampire Core domain models: Character, Memory, Experience, Skill, Resource, Mark, GameCharacter, Prompt, PendingChange, GameStateSnapshot
tyov_api REST API layer: serialisers, viewsets, URL routing, permissions, middleware
authentication JWT-based login and token verification endpoints
tyov_backend Django project settings, URL root, WSGI/ASGI configuration

Data Models

Character

The central model, owned by a Django User via a ForeignKey:

Field Type Description
name CharField(200) Current name of the vampire
description TextField Background/appearance description
image ImageField Optional character portrait
current_prompt CharField(10) ID of the current prompt (e.g. "17a")
last_dice_roll JSONField Stores D10, D6, and result of the last roll
visited_prompts JSONField(list) List of all prompt IDs visited in order
memory_slots_lost IntegerField Permanent memory slot reductions
memory_slots_gained IntegerField Permanent memory slot increases
creation_step IntegerField(1–6) Tracks progress through the creation wizard
is_creation_complete BooleanField True when all six creation steps are finished
creation_data JSONField Temporary storage during creation
epilogue TextField Player's final reflection (set at game end)
is_completed BooleanField Whether the story has ended
completed_at DateTimeField Timestamp of story completion

Memory

A memory belongs to a Character and holds up to 3 Experiences:

  • title – descriptive name (unique per character)
  • in_diary – whether the memory is stored in the diary
  • is_lost – whether the memory has been forgotten

Experience

An individual narrative entry within a Memory:

  • title – optional short description
  • content – the narrative text
  • prompt_id – which prompt generated this experience
  • date_info – temporal context (e.g. "Winter, 431 CE")

Skill

  • name, description
  • status – one of normal, checked, or lost

Resource

  • name, description
  • resource_typeportable or stationary
  • is_diary – flags the special diary resource (constrained to one active diary per character)
  • is_lost

Mark

  • name, description
  • is_lost

GameCharacter

  • name, description
  • character_typemortal or immortal
  • relationship – e.g. friend, rival, mentor, love, enemy, neutral
  • is_lost

Prompt

  • prompt_id – e.g. "17a", "32b"
  • number, variation – parsed components for sorting
  • content – the scenario text
  • rules – special mechanics as comma-separated tokens (e.g. no_experience, allow_name_change)

PendingChange

Temporary storage for changes before they are atomically committed on "Continue":

  • change_type – experience, memory, skill, resource, mark, or character
  • change_data – JSON payload of the change

GameStateSnapshot (Audit Trail)

Automatically created each turn to record the complete game state:

  • turn_number – sequential, starting from 1
  • prompt_id / next_prompt_id – transition
  • dice_roll – raw dice data
  • game_state – full JSON snapshot of all memories, skills, resources, etc.
  • changes_applied – JSON array of pending changes that were committed
  • created_at – timestamp

API Endpoints

All endpoints require JWT authentication (except login).

Authentication

Method Endpoint Description
POST /api/auth/login/ Authenticate with username/password; returns JWT token
POST /api/auth/verify/ Verify current token and return user info

Login is rate-limited to 5 requests per minute per IP.

Characters

Method Endpoint Description
GET /api/characters/ List all characters for the authenticated user
POST /api/characters/ Create a new character
GET /api/characters/{id}/ Retrieve full character detail (prefetched relations)
PUT/PATCH /api/characters/{id}/ Update character fields
DELETE /api/characters/{id}/ Delete a character
GET /api/characters/{id}/game_state/ Complete game state including prompt, validation, dice info
POST /api/characters/{id}/roll_dice/ Roll D10 − D6 and store the result
POST /api/characters/{id}/continue_story/ Validate, commit pending changes, advance to next prompt
GET /api/characters/{id}/audit_trail/ View turn-by-turn audit history
GET /api/characters/{id}/game-state-changes/ Timeline of state changes between turns

Nested Character Resources

For each character, full CRUD is available on:

  • /api/characters/{id}/memories/ (plus move_to_diary and restore_lost_memory actions)
  • /api/characters/{id}/experiences/
  • /api/characters/{id}/skills/
  • /api/characters/{id}/resources/
  • /api/characters/{id}/marks/
  • /api/characters/{id}/characters/ (known NPCs)
  • /api/characters/{id}/pending-changes/

Prompts

Method Endpoint Description
GET /api/prompts/ List all game prompts
GET /api/prompts/{prompt_id}/ Retrieve a single prompt by ID

Permissions and Security

  • All character data is scoped to the authenticated user via IsCharacterOwner permission.
  • JWT tokens are sent as Authorization: Bearer {token} headers.
  • The login endpoint is throttled (5/minute) to prevent brute-force attacks.
  • All pending changes are processed inside a database transaction to guarantee atomicity.
  • CORS headers are configured via django-cors-headers.

Frontend

Views (Pages)

View Route Description
LoginView /login Authentication form; redirects authenticated users to home
HomeView / Dashboard showing all active characters in a card grid
CharacterCreationView /character-creation/:id Six-step creation wizard
GameView /game/:characterId Main game loop: prompt display, experience form, dice rolling, entity management, memory display
GameEndedView /game/:characterId/ended End-of-story screen with statistics and epilogue
RecordsView /records Archive of current and completed characters
StoryView /story/:characterId Narrative timeline with experience history, character stats sidebar, and epilogue

All routes except /login require authentication. The router guard redirects unauthenticated users.

Key Components

Component Purpose
PromptSection Displays the current prompt text, special rules, and dice roll results
ExperienceForm Form for writing new experiences with title, date, content, and memory selection
MemoriesDisplay Shows active, diary, and lost memories with experience counts; includes move-to-diary, delete, and restore actions
GameDataSection Reusable list for skills, resources, or marks with add/edit/delete controls
CharactersSection Grid of known NPCs with type badges and edit/delete
EntityCreationWindow Draggable modal for creating/editing skills, resources, marks
CharacterCreationWindow Draggable modal for creating/editing NPC characters
ExperienceEditWindow Window for editing existing experience text
DiaryCreationModal Modal for creating a diary resource and moving a memory into it
ConfirmationDialog Generic confirmation dialog for destructive actions
ValidationWarning Displays validation errors when game rules are not satisfied

State Management (Pinia)

Four Pinia stores manage application state:

Store Responsibility
auth JWT token, user object, login/logout, token expiry handling
game Characters list, current character, game state, all CRUD actions for entities, dice rolling, story continuation
characterCreation Multi-step wizard data, initial character creation
theme Dark/light mode toggle with localStorage persistence

API Service Layer

  • api.ts – Axios instance with base URL auto-detection (development vs. production), request interceptor for JWT headers, response interceptor for 401/token-expiry handling.
  • gameService.ts – All API call methods: authentication, character CRUD, dice rolling, story continuation, memory/experience/skill/resource/mark/character management.
  • entityService.ts – Generic EntityService class instantiated for skills, resources, marks, and characters for DRY CRUD operations.

Composables

Vue composables encapsulate reusable business logic:

  • useGameData() – Computed properties derived from the game store (current character, memories, skills, resources, etc.)
  • useExperienceForm() – Form state, validation, memory selection, and reset logic
  • useEntityManagement() – Generic CRUD operations with optional confirmation dialogs
  • useSkillManagement(), useResourceManagement(), useMarkManagement(), useMemoryManagement() – Specialised wrappers
  • useConfirmationDialog() – Promise-based modal confirmation
  • useMemoryDisplay() – Display logic for memories including pending-change awareness
  • useExperienceDisplay() – Markdown rendering for experience content
  • usePendingChangesDisplay() – Badge rendering for undo-able pending changes

Audit Trail

The application includes an automatic audit trail that captures the complete game state every time the player advances to a new prompt.

Each GameStateSnapshot records:

  • The turn number (sequential from 1)
  • The prompt transition (e.g. 36a → 34a)
  • The dice roll
  • A complete JSON snapshot of all memories, skills, resources, marks, and known characters
  • All pending changes that were committed

The audit trail is accessible via:

  • API: GET /api/characters/{id}/audit_trail/ with optional ?turn=N and ?limit=N parameters
  • Management command: python manage.py view_audit_trail {id} [--summary] [--turn N] [--export file.json]
  • Timeline API: GET /api/characters/{id}/game-state-changes/ for a structured timeline of all state changes

Deployment

Local Development

.\startup.ps1

This script creates a virtual environment, installs dependencies, runs migrations, and starts both the Django backend (http://localhost:8000) and the Vue dev server (http://localhost:5173).

Production

  • Set DJANGO_ENV=production
  • Build the frontend: cd frontend && npm run build
  • Serve with Gunicorn behind a reverse proxy
  • Static files are collected into the static/ directory
  • The production frontend points to https://tyov.yusupov.cloud

Example: A Finished Character

The following is an example of a character from an actual playthrough stored in the application database. It demonstrates how the game's mechanics play out over an extended narrative.

Karmiš (formerly Ekurzu / Naram)

Description: Naram was a temple scribe in the ancient city of Mari: meticulous, observant, and deeply entangled in the political and spiritual life of the city. His life was shaped by clay tablets, omens, and whispered secrets carried through palace corridors and temple courtyards. Behind his calm demeanor lies a mind constantly at work—recording, interpreting, and surviving.

Prompts visited: 34 prompts across variations, including: 1a, 4a, 11a–c, 12a, 17a, 20a, 24a, 25a, 32a–b, 34a–36a, 37a–41c, 43a–c, 47a, 52a, 57a, 64a, 66a, 68a, 71a, 73a.

Final prompt: 73a

Memory slots lost: 1 · Memory slots gained: 1 (net effect: standard 5-memory limit)

Epilogue

He has not died. Let me say that plainly. There will be no grave, no crumbling tomb, no name worn smooth by time. He is alive—quietly, deliberately, and utterly outside your reach.

This book is not a memorial. It is a reckoning.

When I first met him, I did not understand what he was. He wore his years like dust on old vellum—thin, barely visible, but ever present. [...] Over time, I became his mirror, then his partner, and—finally—his historian.

But someone has to begin the telling. Someone has to place the first stone. So this book begins in a house of quiet gardens and low ceilings, where he writes in the mornings and feeds only when he must. [...]

Just a man who remembered too much, for too long. And chose, in the end, peace.

Mirelde

A Life in Summary

Karmiš's story spans roughly three and a half thousand years. It begins in Bronze Age Mesopotamia, where the mortal scribe Naram serves in the temple of Ishtar at Mari—writing omens, navigating palace intrigue, and falling in love with a woman named Hessa who moves through the court "like smoke." His mentor, the High Priest Ibbi-Zamri, is the first person he kills after being turned by the ancient vampire Ashurban the Veiled.

The early centuries are defined by guilt and exile. Fleeing with his mortal companion Ennatum, the newly-made vampire—now calling himself Ekurzu—struggles to control his hunger. He builds walls and plays flutes in the wilderness, but it is "never penance, only a cage with softer bars." Eventually the hunger wins: he devours Ennatum during a sandstorm and spends the following decades alone.

The middle game carries the character through the great civilisations of antiquity—Amarna under Akhenaten, the Achaemenid Royal Road, the libraries of Alexandria—always in the role of scribe, forger, or quiet observer. He accumulates skills like Silent Cartography ("I trace the unseen paths between people, places, and power") and Ash-Tongue ("I speak in soot and suggestion, in the cracks between laws"), but loses others as the centuries erode his identity: the ability to Decipher Ancient Texts and even I Control the Beast both slip away.

A pivotal relationship develops with the scholar Thoöni, who recognises the name Ekurzu not as myth but as continuity. Later—after Thoöni's mortal death—her spectral echo begins appearing in the margins of his diary, scratching fragments in languages he can no longer read.

By the late Roman period Karmiš has lost so many memories that he can barely hold a conversation. The game's memory mechanics enforce this viscerally: eight of his sixteen memories were permanently forgotten, including The Turning itself—the vampire no longer remembers how he was made. He compensates by keeping diaries, but even those are lost: The Walls of Forgetting (memory carvings in five scripts beneath a shrine near Carchemish) and The Monastery Diary both vanish, taking their stored memories with them.

The final act takes place in early modern Europe. Karmiš witnesses the Atlantic slave trade first-hand and begins compiling The Margins of Manifest—a cipher-folio of names taken from shipping manifests and punishment ledgers. His response to prompt 66a captures the shift:

It is no longer a question of restraint. That was a younger version of me—one who believed self-control made me different from the others. Now I am different in purpose, not method. I feed from slavers and call it justice, but the blood tastes the same.

His companion in the final centuries is Mirelde of Bracha—an immortal who becomes his mirror, partner, and ultimately his historian. Together they rediscover a clay tablet bearing his original name in a Venetian bookseller's back room (prompt 68a, 1704 CE), and in a ruined estate outside Smyrna Mirelde finds the remains of a life Karmiš can no longer remember.

At the story's end Karmiš retains only five active memories, three diary memories, two marks (The Unblinking Eye—a scar that burns in the presence of lies—and The Gesture Forbidden—an involuntary court gesture mistaken for mockery), and three possessions: his diary Kept Against Forgetting, the clay tablet bearing his name, and the slave-trade folio. Of the eleven characters he encountered, only four remain: Ashurban (his distant maker), Mirelde (his companion), Thoöni's ghost, and a nameless figure known only as The One Who Does Not Answer. Over 34 prompts he accumulated 37 experiences, gained and lost a memory slot each, and burned through three separate diaries.

The character's story is narrated in the epilogue not by Karmiš himself but by Mirelde, who insists: "He has not died. [...] Just a man who remembered too much, for too long. And chose, in the end, peace."

Testing

Backend

Tests are run with pytest and pytest-django:

pytest

Test files include:

  • vampire/test_models.py – model unit tests
  • tyov_api/test_api.py – API endpoint integration tests
  • tyov_api/test_models.py – API model tests
  • authentication/test_views.py – authentication tests

Frontend

End-to-end tests use Playwright:

cd frontend
npx playwright test

Test specs include:

  • e2e/character-creation-workflow.spec.ts – character creation wizard
  • e2e/tyov-game.spec.ts – game loop testing
  • e2e/vue.spec.ts – general Vue component tests

See Also

References