Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
24c61e1
Add 800-topic Claude-authored tiered content corpus (biology/chemistr…
Aug 25, 2026
411d2e6
Fix combat battle log bug and replace in-combat alert() calls (Epic #11)
Aug 25, 2026
d88388d
Complete Gemini judge results for the 800-topic tiered content corpus
Aug 25, 2026
f01f6d3
Merge 800-topic tiered corpus into the live game's question pool
Aug 25, 2026
cdbb8ee
Let players choose difficulty (easy/medium/hard) per realm before combat
Aug 25, 2026
676510c
Replace Simple/Deep hint buttons with three distinct Easy/Medium/Hard…
Aug 25, 2026
7af1f6f
Clarify "level" (syllabus difficulty) vs "tier" (hint verbosity) in t…
Aug 25, 2026
2e5c643
Calibrate difficulty-level caption to specific real-world institutions
Aug 25, 2026
c3e81c4
Recalibrate difficulty-level caption to Ramaz / Cornell / graduate sc…
Aug 25, 2026
dcd7a7b
Hide the real-world institution calibration from the player-facing ca…
Aug 25, 2026
ecd9351
Add Gemma-as-judge comparison tool for the 800-topic tiered corpus
Aug 26, 2026
fab8c2c
Remove dead code in cf-pages: stub, orphaned log target, debug loggin…
Aug 26, 2026
8835f3e
Finish battle log acceptance criteria: heading, placeholder, turn-gro…
Aug 26, 2026
92825ef
Replace remaining alert() calls with in-game messaging (#19)
Aug 26, 2026
bee394d
Archive stray root-level docs and index the backend script pile (#25)
Aug 26, 2026
7c8d60b
Fix 3 answer-key bugs in the live math corpus
Aug 26, 2026
bfc5a10
Add .github/ issue templates, PR template, and CI (#26)
Aug 26, 2026
96dbc83
Add idle nudge: pulse the next expected action after 10s (#14)
Aug 26, 2026
f6cdf83
Make action buttons self-describing: cost, effect, and reasons (#15)
Aug 26, 2026
d7e7eb8
Explain HP, CAP, and Resolve on the combat HUD (#17)
Aug 26, 2026
b3be7a5
Add skippable first-run coach-mark tutorial (#16)
Aug 26, 2026
60458d5
Wire up scoring: server-authoritative score, streak, and hint penalty…
Aug 26, 2026
c7031ef
Add run summary with review-what-you-missed to victory/defeat (#21)
Aug 26, 2026
2f38964
Add persistent player profile: lifetime XP, per-realm records, histor…
Aug 26, 2026
af023b9
Add upgrade shop: permanent, deterministic upgrades bought with XP (#23)
Aug 26, 2026
2cad7b0
Fix severe answer-position bias: shuffle options server-side
Aug 27, 2026
06d7ac6
Finish issue #9's remaining scope: multiplier, per-tier persistence, …
Aug 27, 2026
1ac2ccd
Add per-tier question timer: Easy longer, Hard shorter (#29)
Aug 27, 2026
69bdd20
Reconcile corpus drift and flip cf-pages to master (issue #24)
Aug 27, 2026
0a4a3bf
Add geography/history/literature/computer_science to live corpus
Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
48 changes: 48 additions & 0 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Bug report
description: Something in the game or pipeline is broken
labels: [bug]
body:
- type: textarea
id: what-happened
attributes:
label: What happened
description: What did you see?
validations:
required: true
- type: textarea
id: expected
attributes:
label: What you expected instead
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce
placeholder: |
1. Go to '...'
2. Click '...'
3. See error
validations:
required: true
- type: dropdown
id: environment
attributes:
label: Where did this happen?
options:
- "study-saga.pages.dev (production)"
- "Local wrangler pages dev"
- "Local Flask (backend/app.py)"
- "Not sure"
validations:
required: true
- type: input
id: browser
attributes:
label: Browser / device
placeholder: e.g. Chrome 128 on Android, Safari on iOS
- type: textarea
id: extra
attributes:
label: Anything else
description: Console errors, screenshots, whatever's useful.
2 changes: 2 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
blank_issues_enabled: true
contact_links: []
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/content.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: Content / corpus problem
description: A question, answer, or hint in the game itself is wrong
labels: [content-pipeline]
body:
- type: dropdown
id: realm
attributes:
label: Realm
options:
- Biology
- Chemistry
- Math
- Physics
- Geography
- History
- Literature
- Computer Science
- Not sure
validations:
required: true
- type: textarea
id: question-text
attributes:
label: Question text
description: Paste the exact question text (or as much as you remember) so it can be found in the corpus.
validations:
required: true
- type: dropdown
id: problem-type
attributes:
label: What's wrong
options:
- Wrong answer marked correct
- Hint gives away the answer
- Hint is wrong or confusing
- Broken formula / math rendering
- Typo or unclear wording
- Other
validations:
required: true
- type: textarea
id: details
attributes:
label: Details
description: "Example of a real past instance of this class of bug: an answer option read \"7 - (-2)\" where the arithmetic didn't check out."
validations:
required: false
40 changes: 40 additions & 0 deletions .github/ISSUE_TEMPLATE/feature.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Feature request
description: Propose a new feature or enhancement
labels: [enhancement]
body:
- type: textarea
id: problem
attributes:
label: Problem
description: What's missing or awkward today?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposal
description: What should change?
validations:
required: true
- type: textarea
id: acceptance
attributes:
label: Acceptance criteria
description: How would we know this is done? A checklist is ideal.
placeholder: |
- [ ] ...
- [ ] ...
validations:
required: true
- type: dropdown
id: area
attributes:
label: Affected area
multiple: true
options:
- Frontend (cf-pages/public)
- Functions (cf-pages/functions)
- Corpus / content pipeline (backend/)
- Other
validations:
required: true
9 changes: 9 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## What changed

## Why

## How this was verified

## Deploy

- [ ] Redeployed to Cloudflare Pages (`npx wrangler pages deploy public --project-name study-saga --branch main` from `cf-pages/`) — leave unchecked if this PR doesn't touch `cf-pages/`, but note that in that case a merge alone does **not** ship anything; deploys are manual.
69 changes: 69 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: CI

on:
pull_request:
branches: [main]
push:
branches: [main]

jobs:
corpus-validation:
name: Corpus validation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: python backend/validate_corpus.py

corpus-drift:
name: Corpus drift check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
# Issue #24: cf-pages/functions/_lib/data.json is the authoritative
# corpus; backend/data.json is a generated mirror. Regenerate it and
# fail if the committed version doesn't match -- this is what makes
# drift impossible to commit unnoticed, the single highest-value check
# in this file per #26's own description of it.
- working-directory: backend
run: python sync_corpus.py
- run: git diff --exit-code -- backend/data.json

js-syntax:
name: JS syntax check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
- run: bash cf-pages/check-js-syntax.sh

python-lint:
name: Python lint (non-blocking)
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install ruff
# backend/ has ~75 inherited scripts never linted before; report only
# for now so this doesn't block merges until it's been cleaned up.
- run: ruff check backend/ --exit-zero

readme-links:
name: README link check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: python check-readme-links.py
44 changes: 38 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,23 @@ study-saga/
│ │ │ # reset-game, auth-resume, syllabi
│ │ └── _lib/ # game.js (session/KV helpers), auth.js (Firebase
│ │ # token verification), profile.js (Firestore),
│ │ # data.json/config.json (question corpus)
│ │ # data.json (master question corpus)/config.json
│ └── public/ # Static frontend — index.html, game-simple.js,
│ # holo-card.js/css, neural-bg.js, style-neural.css
├── backend/ # Original Flask prototype + content pipeline
│ ├── app.py # Local dev server (same game logic as cf-pages,
│ │ # used for iterating before porting to Functions)
│ ├── data.json # Master question corpus (mirrored into cf-pages)
│ └── *.py, *.md # Hint-generation/audit/bakeoff scripts — see below
│ ├── data.json # Generated mirror of cf-pages' corpus (see sync_corpus.py)
│ ├── archive/ # Retired standalone scripts, kept for reference
│ └── *.py, *.md # Hint-generation/audit/bakeoff scripts — indexed
│ # in backend/README.md, see below
├── frontend/ # Templates/static assets consumed by backend/app.py
└── docs/ # Static GitHub Pages landing page
└── docs/ # Static GitHub Pages landing page + archived history
├── history/ # Superseded session-status/planning docs
└── reports/ # Benchmark/diagnostic report snapshots
```

The **cf-pages/** app is what's actually live at study-saga.pages.dev — it's the entire production stack, and it's JavaScript end to end (Pages Functions + vanilla JS frontend), not Python. **`backend/app.py`** is a Flask app kept around only as a local mirror for faster iteration on game logic before porting changes to Functions — it is never deployed. The rest of `backend/` is a large collection of one-off scripts used to build and QA the question/hint corpus (see [Content pipeline](#content-pipeline) below).
The **cf-pages/** app is what's actually live at study-saga.pages.dev — it's the entire production stack, and it's JavaScript end to end (Pages Functions + vanilla JS frontend), not Python. **`backend/app.py`** is a Flask app kept around only as a local mirror for faster iteration on game logic before porting changes to Functions — it is never deployed. The rest of `backend/` is a large collection of one-off scripts used to build and QA the question/hint corpus — see [`backend/README.md`](backend/README.md) for an index, and [Content pipeline](#content-pipeline) below for the broader picture.

## Gameplay

Expand Down Expand Up @@ -62,6 +66,10 @@ python app.py

Open `http://localhost:5000`. Live hint generation needs `GEMINI_API_KEY`/`GROQ_API_KEY` in `backend/.env`; without them, only pre-generated hints from `data.json`/`final_corpus_gemini_hints.json` are served. This is a local-only dev mirror — it is never deployed anywhere.

### Question corpus: single source of truth (issue #24)

`cf-pages/functions/_lib/data.json` is the **authoritative** question corpus — it's what Pages Functions actually serves to players. `backend/data.json` is a **generated mirror** for the local Flask dev server; it is never edited directly. After any edit to the live corpus, run `python backend/sync_corpus.py` to regenerate the mirror. CI's `corpus-drift` job (`.github/workflows/ci.yml`) fails the build if `backend/data.json` doesn't match a fresh regeneration, so drift can't land unnoticed. `backend/validate_corpus.py` runs schema/invariant checks against the master file (types, option/hint completeness, no duplicate questions within a realm, no duplicate options within a question, `answer_index`/`answer_indices` range and consistency with `isCorrect` flags).

## Deployment

The `study-saga` Cloudflare Pages project has **no Git integration** — it does not auto-build from this (or any) GitHub repo. Every deploy is a manual push of the built directory straight to Cloudflare's edge from a local machine:
Expand Down Expand Up @@ -98,6 +106,31 @@ Required bindings/secrets (set in the Cloudflare Pages dashboard or `wrangler.to

Because deploys aren't tied to `git push`, the state of this repo's `main` branch on GitHub can lag behind what's actually live — check `npx wrangler pages deployment list --project-name study-saga` for the real deployment history rather than assuming the latest commit is what's served.

### CI

GitHub Actions (`.github/workflows/ci.yml`) runs on every PR to `main` and every push to `main`:

| Check | Blocking? |
|---|---|
| Corpus validation (`backend/validate_corpus.py` — schema, exactly-one-correct-answer for single-select, all 3 hint tiers present) | Yes |
| JS syntax check (`cf-pages/check-js-syntax.sh` — `node --check` over every frontend script and Functions module) | Yes |
| Python lint (`ruff` over `backend/`) | No — report-only, given ~75 inherited scripts never linted before |
| README link check (`check-readme-links.py`) | Yes |

**CI does not deploy anything and does not replace the manual deploy step above** — a merged, green PR still requires the `npx wrangler pages deploy` command to actually ship. A corpus-drift check (verifying `backend/data.json` and `cf-pages/functions/_lib/data.json` haven't diverged) is intentionally not included yet — the two files have already diverged and which one should be authoritative is an open question (issue #24); adding the check before that's resolved would just fail on every PR.

### Difficulty tier guidelines (issue #9)

Every question in the corpus carries a `difficulty` field of `"easy"`, `"medium"`, or `"hard"` (untagged questions default to `medium`). Question authors — human or LLM-prompted — should write to these definitions so tiers stay meaningfully different in practice, not just in name:

| Tier | Reasoning | Score multiplier |
|---|---|---|
| Easy | Recall and definitions. Answerable by directly remembering a single fact, term, or definition. Single-step reasoning only. | 1x |
| Medium | Applying a concept. Uses a definition/concept in a new context, or two-step reasoning (combining two related facts, or a two-operation calculation). | 1.5x |
| Hard | Multi-step problems, distractor-heavy options. At least three reasoning steps or calculation stages, or a non-trivial scenario requiring synthesis. | 2x |

A realm's tier is unselectable in the UI until it has at least **15 questions** at that difficulty (`MIN_TIER_QUESTIONS` in `cf-pages/functions/_lib/game.js`) — a tier under that floor is disabled rather than silently falling back to the full question pool.

## Content pipeline

`backend/` doubles as the workspace for building and grading the question/hint corpus — generator bake-offs (Gemini vs. Groq vs. Gemma across Math/Biology/Chemistry/Physics), an LLM-judge comparison harness, difficulty classification, and audit scripts that catch things like glued-together text artifacts or mismatched answer keys. Results and intermediate corpora are checked in as `*_results.json`/`*_report.json` next to the scripts that produced them. This is R&D scaffolding, not part of the served app — treat scripts here as a lab notebook rather than a stable API.
Expand All @@ -106,7 +139,6 @@ Because deploys aren't tied to `git push`, the state of this repo's `main` branc

- **Points/gacha economy** (spend earned credits on upgrades — potions, attack-power boosts) is scoped but deferred; the hint-credit system above is the first slice of it.
- **Chemistry hint quality** trails Biology/Physics in bake-off scoring (~14.5% of sampled hints score below the quality floor, vs. ~3-5% for the other two subjects) — root cause still open.
- User profile storage is being moved from Cloudflare KV to Firestore (direct REST calls with the caller's own Firebase ID token, no Admin SDK) so profile data isn't tied to a single Cloudflare account.

## License

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loading
Loading