VS Simulator is a character battle simulation system grounded in the VS Battles Wiki.
The simulator resolves fights turn by turn using only the documented abilities of each character—no invented powers, no numeric stats, and no arbitrary rule sets. The outcome of any action is determined by whether the acting character's ability set can logically produce that effect against the defending character's documented resistances and nature. The wiki acts as the absolute ground truth.
┌──────────────┐
│ Character DB│ ← SQLite + FAISS semantic index
└──────┬───────┘
│
┌───────────▼───────────┐
│ Session Setup │ Resolve names → Load cores → Build combat personalities
│ + Context Caching │ Create Gemini CachedContent per fighter (full ability sheet)
└───────────┬───────────┘
│
┌───────────▼───────────┐
│ Turn Loop │
│ │
│ ┌────────┬────────┐ │ Fighter A and B decide in parallel
│ │Fighter │Fighter │ │ (neither sees the other's choice)
│ │Agent A │Agent B │ │
│ └────┬───┴───┬────┘ │
│ └───┬───┘ │
│ ┌──────▼──────┐ │ Resolver determines what actually happened
│ │ Resolver │ │ (ability interactions, state changes)
│ └──────┬──────┘ │
│ ┌──────▼──────┐ │ Prose agent narrates (overlaps with next turn)
│ │ Prose Agent│ │
│ └─────────────┘ │
└───────────────────────┘
Each agent is powered by Gemini 3.5 Flash with structured output schemas (Pydantic) and function calling for ability retrieval.
Warning
Data Quality & Recovery Note:
The raw scraped wiki data (vswiki.json) suffered from various formatting issues, including unescaped quotation marks and parsing errors (e.g., the {{Nihongo}} template scrapers putting literal placeholder names instead of the actual Japanese translation names).
Due to these raw JSON issues, a custom boundary-based item-by-item recovery script using json_repair was run during data ingestion to reconstruct valid JSON objects. While this pipeline successfully recovered 100% of the character entities, some resolved text content, ability names, or descriptions may still contain slight parsing anomalies or inaccuracies.
- Pit Mode: A fully autonomous, simulated fight between two characters.
- Context Caching: Pre-loads complete character sheets (all abilities, no truncation) into Gemini explicit context caches at fight start. Subsequent turns only send dynamic state, reducing per-turn cost by ~75% on cached tokens.
- Parallel Fighters: Both fighter agents decide simultaneously via
ThreadPoolExecutor. Neither sees the other's choice for the current turn—correct simultaneous action selection. - Overlapped Prose: Prose generation runs in a background thread that overlaps with the next turn's fighter decisions, reducing total fight time.
- Stateful Prose Generation: Attempts to use the Gemini Interactions API for narrative continuity across turns. Falls back to standard
generateContenton Vertex AI endpoints where the Interactions API is not yet supported. - Structured Outputs: Fighter declarations and resolver resolutions use Pydantic schemas enforced via
response_schema, eliminating JSON parsing failures. - Hybrid Search: Combines SQLite FTS5 full-text search with local sentence-embedding semantic search (via FAISS) to match fuzzy/inconsistent ability concepts (e.g., "immunity to mind control").
- Automatic Retry: The Gemini client automatically retries on 503/429 errors with exponential backoff and jitter.
- Token Logging: Every API call logs prompt, candidate, total, and cached token counts for cost analysis.
src/
├── main.py # CLI entry point (Rich terminal UI)
├── logging_config.py # File-based logging setup
├── agents/
│ ├── gemini_client.py # Gemini API client with retry logic
│ ├── context_cache.py # Explicit CachedContent creation per fighter
│ ├── fighter.py # Fighter agent (cached + uncached modes)
│ ├── resolver.py # Resolver agent (turn adjudication)
│ ├── prose.py # Prose agent (Interactions API + fallback)
│ ├── schemas.py # Pydantic models for structured output
│ └── prompts/ # System prompt templates
│ ├── fighter.txt # Full fighter prompt (uncached mode)
│ ├── fighter_static.txt # Static fighter prompt (cached mode)
│ ├── resolver.txt # Resolver prompt
│ └── prose.txt # Prose writer prompt
├── battle/
│ ├── state.py # Session state, conditions, effects model
│ └── orchestrator.py # Turn loop, parallel execution, prose overlap
├── retrieval/
│ ├── tools.py # Retrieval functions + thread-safe cache
│ ├── character_lookup.py # Character name resolution (FTS5 + fuzzy)
│ └── embeddings.py # FAISS semantic search interface
└── db/
└── connection.py # SQLite connection management
data/
├── pipeline/
│ ├── export_clean_json.py # JSON repair + clean export
│ ├── ingest.py # SQLite ingestion pipeline
│ └── embed.py # FAISS embedding generation
├── vskill.db # SQLite database (local build)
├── abilities.faiss # FAISS index (local build)
└── faiss_id_map.json # FAISS ID mapping (local build)
This project uses dotenv to load configurations. Copy the template .env.example file and configure it:
cp .env.example .envEnsure you have set the GEMINI_API_KEY in your .env:
GEMINI_API_KEY=your_gemini_api_keyEnterprise mode is highly recommended to bypass standard rate limits. Configure the following variables in .env and authenticate your terminal using Google Cloud ADC (gcloud auth application-default login):
GEMINI_ENTERPRISE=true
GEMINI_PROJECT=your_gcp_project_id
GEMINI_LOCATION=asia-northeast1Note
The Gemini Interactions API (used for stateful prose) is currently available on the standard Google AI endpoint but not on Vertex AI. When running in enterprise/Vertex mode, the prose agent automatically falls back to standard generateContent. This is handled transparently.
- Clone the repository (or download the source).
- Set up a virtual environment and install dependencies:
(Note: Ensure you place your source raw
python3 -m venv vskill-venv source vskill-venv/bin/activate pip install -r requirements.txtvswiki.jsoninside theassets/directory if you plan to rebuild the databases).
Because database and FAISS index files exceed GitHub's 100MB limit, you will need to build them locally from the source dataset.
- Reparse & Repair Malformed JSON:
Exports a clean, standard-compliant JSON array to
assets/vswiki_clean.json.python data/pipeline/export_clean_json.py
- Ingest to SQLite:
Populates
data/vskill.dbwith identity tables, abilities, and notable attacks, stripping citations and fixingNihongotemplates.python data/pipeline/ingest.py
- Generate Embeddings (FAISS):
Generates sentence-level embeddings using a local model and compiles
data/abilities.faissanddata/faiss_id_map.json.python data/pipeline/embed.py
You can run character searches or trigger simulations directly from the CLI.
To search the local database for available characters matching a query:
python -m src.main --list "Goku"To simulate a fight between two characters:
python -m src.main "Goku" "Superman" --turns 15Options:
--turns <int>: Set the maximum number of turns before declaring a draw (default: 15).--save <path.json>: Save the canonical simulation state and narrative history to a JSON file.--list <query>: List matching characters instead of starting a fight.
After a fight, token usage is logged to vskill.log. To see per-call token counts:
grep "TOKENS" vskill.log| Token Type | Count | Approx. Cost (USD) |
|---|---|---|
| Input (Standard) | 107,171 | $0.161 |
| Input (Cached Read) | 129,854 | $0.019 |
| Output (Candidates) | 7,318 | $0.066 |
| Total | 247,239 | $0.246 |
Context caching provides ~75% cost reduction on repeated character sheet tokens across turns.
- Interactions API on Vertex AI: The Gemini Interactions API returns
400 Unsupported model interactionwhen called via Vertex AI enterprise endpoints. The prose agent falls back togenerateContentautomatically. - Deadlock Loops: Strategic characters may enter cyclical action patterns (A shields → B switches to utility → A attacks → B shields → ...). This can be mitigated by prompt-level loop detection instructions or resolver intervention.
- Cache Build Time: Creating explicit context caches at fight start takes ~1-2 minutes due to sequential ability fetching and Gemini tokenization overhead. This could be parallelized.
- Data Quality: Some character ability descriptions contain parsing artifacts from the wiki scraping pipeline.