Thousand Year Old Vampire: Difference between revisions
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 | 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, | 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> | ||
==== | ==== 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: | |||
<blockquote> | <blockquote> | ||
''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> | |||
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 == | == Testing == | ||
Revision as of 11:21, 12 April 2026
| Infobox | |
|---|---|
| name | Thousand Year Old Vampire – Web Helper |
| developer | Michel Vuijlsteke |
| programming language | Python (Django 5), TypeScript (Vue 3) |
| operating system | Cross-platform (Web) |
| genre | Solo RPG Digital Companion |
| license | custom |
| website | https://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:
- Prompt Presentation – The current prompt text and any special rules are displayed.
- Dice Roll – The player rolls D10 − D6 to determine the next prompt.
- Experience Creation – The player writes at least one narrative experience for the current prompt.
- Character Updates – Skills, resources, marks, and known characters may be added, edited, checked, or removed.
- Memory Management – The player resolves any memory overflow (forget or move to diary).
- Validation – The system verifies all rules are satisfied (at least one experience added, memory limits respected, etc.).
- 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 diaryis_lost– whether the memory has been forgotten
Experience
An individual narrative entry within a Memory:
title– optional short descriptioncontent– the narrative textprompt_id– which prompt generated this experiencedate_info– temporal context (e.g. "Winter, 431 CE")
Skill
name,descriptionstatus– one ofnormal,checked, orlost
Resource
name,descriptionresource_type–portableorstationaryis_diary– flags the special diary resource (constrained to one active diary per character)is_lost
Mark
name,descriptionis_lost
GameCharacter
name,descriptioncharacter_type–mortalorimmortalrelationship– e.g. friend, rival, mentor, love, enemy, neutralis_lost
Prompt
prompt_id– e.g. "17a", "32b"number,variation– parsed components for sortingcontent– the scenario textrules– 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 characterchange_data– JSON payload of the change
GameStateSnapshot (Audit Trail)
Automatically created each turn to record the complete game state:
turn_number– sequential, starting from 1prompt_id/next_prompt_id– transitiondice_roll– raw dice datagame_state– full JSON snapshot of all memories, skills, resources, etc.changes_applied– JSON array of pending changes that were committedcreated_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/(plusmove_to_diaryandrestore_lost_memoryactions)/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
IsCharacterOwnerpermission. - 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– GenericEntityServiceclass 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 logicuseEntityManagement()– Generic CRUD operations with optional confirmation dialogsuseSkillManagement(),useResourceManagement(),useMarkManagement(),useMemoryManagement()– Specialised wrappersuseConfirmationDialog()– Promise-based modal confirmationuseMemoryDisplay()– Display logic for memories including pending-change awarenessuseExperienceDisplay()– Markdown rendering for experience contentusePendingChangesDisplay()– 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=Nand?limit=Nparameters - 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 teststyov_api/test_api.py– API endpoint integration teststyov_api/test_models.py– API model testsauthentication/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 wizarde2e/tyov-game.spec.ts– game loop testinge2e/vue.spec.ts– general Vue component tests
See Also
- Thousand Year Old Vampire by Tim Hutchings – the original tabletop game
- Django (web framework)
- Vue.js
- Single-page application