QuestSmith is a mobile-first, single-player choose-your-own-adventure fantasy RPG. Gemini acts as the AI Dungeon Master—generating scenes, choices, dialogue, and flavor text—while the app controls deterministic game systems: HP, inventory, gold, XP, combat, quest status, and saved progress.
Campaign generation in QuestSmith: a randomly generated quest with hero stats and saved progress.
This project separates creative generation from deterministic game logic:
| Layer | Responsibility |
|---|---|
| Gemini | Scene narration, player choices, NPC dialogue, item descriptions, quest ideas |
| App (game engine) | Dice rolls, combat outcomes, stat changes, inventory updates, quest status, persistence |
| Firebase | Authentication, Firestore persistence, Hosting, Cloud Functions |
Gemini suggests narrative consequences; the app calculates and saves the actual game state. This keeps the experience dynamic while remaining fair, testable, and recoverable.
- Frontend: React 19, Ionic React, TypeScript, Vite
- Backend: Firebase Auth, Firestore, Cloud Functions, Hosting
- AI: Gemini API (planned via Cloud Functions)
- Mobile: Capacitor (optional native builds)
| Event | When | Parameters |
|---|---|---|
screen_view |
Login page loads | screen_name |
login |
Email or Google sign-in | auth_method |
sign_up |
Email or Google account creation | auth_method |
character_created |
Character saved to Firestore | character_class, campaign_id |
campaign_started |
Campaign generated and saved | campaign_id, character_class, is_new_adventure |
story_turn_completed |
Player choice advances the story | campaign_id, turn_number, campaign_phase |
item_acquired |
Item added from story or combat | campaign_id, item_type, item_rarity, source, turn_number |
combat_started |
Random encounter triggers | campaign_id, enemy_id, turn_number |
combat_completed |
Combat ends in victory or defeat | campaign_id, enemy_id, combat_result, turn_number |
campaign_completed |
Campaign reaches ending scene | campaign_id, turns_played |
| Epic | Feature | Status |
|---|---|---|
| 1 | Authentication (email/password, Google, sign-out, protected routes) | Done |
| 2 | Character creation | Done |
| 3 | Random campaign generation | Done |
| 4+ | Gameplay loop, combat, inventory, quests, persistence | Done |
| 5 | Analytics | Done |
| 6 | Landing Page | Done |
- Node.js 20+
- A Firebase project with Auth and Firestore enabled
- Firebase CLI (
npm install -g firebase-tools)
git clone https://github.com/treyeckels/questsmith.git
cd questsmith
npm install
npm install --prefix functionsFirebase config is loaded at build time through Vite import.meta.env in src/firebase.ts. Only variables prefixed with VITE_ are exposed to the client.
| File | When it is used | Committed? |
|---|---|---|
.env.example |
Template with placeholder values | Yes |
.env.local |
Local development (npm run dev) |
No — gitignored |
.env.production |
Production builds (npm run build) before Firebase Hosting deploys |
No — gitignored |
Local development
cp .env.example .env.localFill in your Firebase web app config from the Firebase Console → Project Settings → Your apps. Restart the dev server after changing env files.
Production deploys
Before building for Firebase Hosting, create a production env file with the same variables:
cp .env.example .env.productionVite loads .env.production automatically when you run npm run build. Then deploy:
npm run build
firebase deploy --only hostingImportant: Never commit .env.local, .env.production, or any file containing real Firebase credentials. Only .env.example (placeholders) belongs in the repo.
Required variables:
VITE_FIREBASE_API_KEY=
VITE_FIREBASE_AUTH_DOMAIN=
VITE_FIREBASE_PROJECT_ID=
VITE_FIREBASE_STORAGE_BUCKET=
VITE_FIREBASE_MESSAGING_SENDER_ID=
VITE_FIREBASE_APP_ID=
VITE_FIREBASE_MEASUREMENT_ID=
In the Firebase Console under Authentication → Sign-in method, enable:
- Email/Password
Deploy Firestore rules and indexes:
firebase login
firebase deploy --only firestore:rules,firestore:indexesCampaign generation calls Gemini from a Cloud Function. The API key is stored in Google Cloud Secret Manager via Firebase — never in the client app.
Production (one-time setup)
firebase functions:secrets:set GEMINI_API_KEYEnter your Gemini API key when prompted. Then deploy functions:
cd functions && npm run build
firebase deploy --only functionsLocal emulator
Create functions/.secret.local (gitignored):
GEMINI_API_KEY=your-gemini-api-key
Run the emulator with secrets access:
firebase emulators:start --only functionsDo not add VITE_GEMINI_* to the client — the key must stay server-side.
Campaign generation is invoked via same-origin POST /api/generateCampaign (Firebase Hosting rewrite → Cloud Function). This avoids browser CORS issues with Gen2 Cloud Run.
Deploy both after function changes:
cd functions && npm run build
firebase deploy --only functions,hostingnpm run devOpen the URL shown in the terminal (typically http://localhost:5173).
Ensure .env.production exists with your Firebase config (see Environment Variables), then:
npm run build
firebase deploy --only hostingsrc/
app/ # App routing
features/
auth/ # Sign-in, sign-up, Google OAuth
character/ # Character creation (stub)
game/ # Game screen and game queries (stub)
shared/
components/ # Route guards, loading states
context/ # AuthProvider
hooks/ # useAuth
firebase/ # Firestore path helpers
firebase.ts # Firebase client initialization
functions/ # Firebase Cloud Functions (TypeScript)
docs/ # SRS, user stories, architecture, prompts
| Command | Description |
|---|---|
npm run dev |
Start Vite dev server |
npm run build |
Type-check and production build |
npm run preview |
Preview production build locally |
npm run test.unit |
Run Vitest unit tests |
npm run test.e2e |
Run Cypress end-to-end tests |
npm run lint |
Run ESLint |
- Software Requirements Specification
- User Stories & Acceptance Criteria
- Architecture
- Gemini Prompt Strategy
This project was intentionally developed using an AI-assisted workflow:
- Generate requirements (SRS)
- Define user stories
- Create architecture
- Create implementation plan
- Generate scaffolding
- Review and refine generated code
- Write tests and documentation
The MVP is complete when a user can create an account, create a character, start a randomly generated campaign, play 10–15 turns, survive basic combat, gain or lose items, and resume their game later.
Private project.
