Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>Ren'Py AnalyticsNew to Visual Studio Code? Get it now.
Ren'Py Analytics

Ren'Py Analytics

Mike Fletcher

|
1 install
| (0) | Free
Structure folding, story-flow analysis, save-file safety checks, and writing metrics for Ren'Py projects. Companion to vscode-language-renpy and renpy-magic.
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Ren'Py Analytics

Structure folding, save-file safety checks, and (coming) story-flow analysis and writing metrics for Ren'Py projects in VS Code.

Designed as a companion to vscode-language-renpy (highlighting, completion, navigation) and renpy-magic (LSP: rename, references, lint). This extension deliberately stays out of their lanes — its focus is story structure, reachability, and writing statistics. See SPEC.md for the architecture and ROADMAP.md for what's next.

Story flow graph: labels, menu choices, condition-labeled edges, entry points in green, unreachable scenes in red

Features (0.6 — M1–M6)

Structural folding

Real folding ranges for .rpy/.rpym files — not indentation guessing:

  • label bodies (which end at the next top-level statement, something indentation folding gets wrong)
  • menu blocks and each individual choice
  • screen, init, init python, and python blocks

Commands:

  • Ren'Py: Fold All Labels — collapse the active file to its label skeleton.
  • Ren'Py: Fold All Menus — collapse every menu block and choice (prose stays visible).
  • Ren'Py: Fold All Labels and Menus — fully collapse everything foldable; menus stay collapsed even after you unfold a label. Unfold with VS Code's built-in Unfold All (Ctrl+K Ctrl+J).

Save-file safety diagnostics

Ren'Py only stores variables changed after init in save files, and rollback only tracks what it knows about. This extension statically detects the classic footguns and reports them in the Problems panel:

Rule Example Why it's a risk
define-mutable-literal define inventory = [] In-place changes to a define'd list/dict/set are not saved and not rolled back
define-reassigned define max_hp = 100 … $ max_hp = 200 define is for constants; runtime changes break rollback/save-load assumptions
define-mutated $ inventory.append("sword") The mutation silently disappears on save/load
missing-default $ mood = "happy" with no default mood = ... Older saves loaded into a newer build hit NameError when the variable is read before that line runs
init-variable-changed init python: chapter = 1 … $ chapter = 2 Init assignments re-run on every start and fight with values restored from saves

Suppress a finding with a comment on the same or preceding line:

define palette = []  # renpy-analytics: save-safe

Disable entirely with the renpy-analytics.saveSafety.enabled setting. Run on demand across the workspace with Ren'Py: Analyze Save-File Safety (Workspace).

Project analysis

Ren'Py: Analyze Project builds a label flow graph across the whole workspace (jumps, calls, menu-choice bodies, fall-through, renpy.jump("…"), and screen actions like Jump("x")) and reports:

  • Inaccessible labels — labels unreachable from any entry point (start, splashscreen, after_load, screen actions, …). Reported in the Problems panel and the Ren'Py Analytics activity-bar view. Dynamic jumps (jump expression …) are counted and noted, since they make reachability undecidable — set renpy-analytics.dynamicJumpMode to lenient to downgrade findings when they exist, or tag intentionally-dynamic targets with # renpy-analytics: reachable.
  • Per-file statistics — labels, menus, choices, and words per file.
  • Per-character statistics — words, dialogue lines, and average sentence length per character (Ren'Py text tags stripped; extend lines attributed to the previous speaker; narrator tracked separately). Ad-hoc string speakers ("Name" "dialogue") are distinct characters in Ren'Py and are labeled "Name" (string speaker) so they can't be mistaken for a defined Character.
  • Speaker consistency — a duplicate-speaker finding (Problems panel + tree view) whenever a string speaker matches a defined character's display name or variable, e.g. "Lady Eleanor" "..." in a script that defines lady_eleanor = Character("Lady Eleanor"). Usually an authoring slip — the string form ignores the Character's styling and splits word counts. Suppress intentional distinct voices with # renpy-analytics: string-speaker on or above the line.

Ren'Py: Export Analysis Report writes the full analysis as Markdown (for humans) or JSON (for CI).

Project Analysis view: playtime estimate, inaccessible labels, dead ends, save-file safety, files and characters

Current File pane

Below the project tree, the Current File pane follows the active editor and shows writing/narrative stats for just that script:

  • Overview — word count and reading time (renpy-analytics.readingSpeedWpm, default 200), labels/menus/choices, dialogue lines with narration share, average sentence length, and pacing (words per choice; "kinetic" when the file has no choices).
  • Inaccessible labels and Issues (save-safety + speaker findings) scoped to this file, click-to-jump.
  • Characters — per-character words with share-of-file %, line counts, and average sentence length.
  • Scenes — dialogue words per label, largest first, for spotting unbalanced scenes.
  • Connections — which labels in other files jump/call into this file, and where this file jumps out to.

Entry points can be extended with the renpy-analytics.extraEntryPoints setting.

Current File pane: word count, reading time, pacing, per-character stats, and issues for the active script

Flag & variable explorer

The Variables pane (activity bar) replaces the flag-tracking spreadsheet: every story variable with its declaration, every write — including which menu choice triggers it — and every read, with condition reads marked as gates. Click any site to jump to it. Two orphan groups surface likely bugs at the top:

  • Gated but never set — conditions read the flag, but nothing ever assigns it: the gated branch can never trigger.
  • Never read — flags written and then forgotten (defines are exempt: scene/show usages aren't expression reads).

Engine namespaces (config.*, gui.*, style.*, …) and Character(...)/image defines are excluded; attribute writes like player.hp -= 5 are credited to the declared player object; persistent.* variables are tracked automatically.

Variables pane: orphan groups for flags gated but never set and never read, plus the full variable index

Choice-consequence CodeLens

Above every menu choice, a summary of what picking it does — stat changes, function calls, and where the story goes:

+corruption  story.set_prologue_why_write=…  ƒ apply_balanced_effect  → day100_2_evening_flashback
"To catalogue what power hides. [[Curious, corrupting]]":

Choice-consequence CodeLens above a menu choice showing function calls and stat changes

Consequence extraction adapted from universal-renpy-walkthrough (MIT), computed statically at edit time. Toggle with renpy-analytics.codeLens.enabled.

Story flow graph

Ren'Py: Show Story Flow Graph opens an interactive map of the whole story: label nodes plus menu-choice nodes, jump/call/fall-through edges, entry points outlined green, inaccessible labels red, dynamic jumps as a dashed pseudo-node. Drag to pan, scroll to zoom, click any node to open it in the editor.

Ren'Py: Show Story Flow Graph (Current File) scopes the map to the active script (e.g. one day file): everything in the file plus a dimmed, dashed ring of external neighbors — the labels in other files that jump in, and the ones this file exits to — all still clickable.

Ren'Py: Export Flow Graph (DOT) writes either graph (it asks: whole project or current file) as Graphviz DOT for rendering with dot -Tsvg or any DOT viewer.

Edges leaving an if/elif/while block carry the condition as an edge label (webview and DOT), and hovering a choice node shows its consequence summary — which flags it sets and where it leads.

Playtest from here

Ren'Py: Playtest From Here (command palette or editor right-click) launches the game warped to the line under the cursor — no more replaying from start to reach the scene you just wrote. It saves the file, finds the project root (the folder above game/), and runs the SDK with --warp game/…/file.rpy:line.

The SDK is located via the renpy-analytics.sdkPath setting; when unset, common install locations are scanned for renpy-*-sdk folders (newest first) and your pick is remembered. Warp requires config.developer to be true, which Ren'Py enables by default for un-built projects. To seed variables for warped scenes, define an after_warp label in your game.

Dead-end detection

Reachable labels whose flow runs off the end of the file without return/jump — a runtime error waiting to happen — are flagged in the Problems panel and listed in the Project Analysis view.

Playtime estimate

The Project Analysis view shows an estimated total playtime (all reachable dialogue at renpy-analytics.readingSpeedWpm) — an upper bound, since branches are mutually exclusive.

Generated file footer

Ren'Py: Generate File Footer (or … for All Files) appends a summary comment block to the end of a script:

# ==== renpy-analytics:begin (auto-generated - do not edit; regenerate via VS Code) ====
# File summary - generated 2026-07-06
# Labels: 12 | Menus: 4 (11 choices) | Total words: 3,482
#
# Words per character:
#   e (Eileen): 1,204 words | avg sentence 9.3
#   narrator: 1,388 words | avg sentence 12.0
#
# Classes/functions called:
#   Character, bonus_points, renpy.pause
#
# Variables referenced:
#   mood, route_flag, trust
# ==== renpy-analytics:end ====

Regeneration replaces the block in place — never duplicates it — and preserves the file's line endings. Configure which sections appear with renpy-analytics.footer.sections; enable renpy-analytics.footer.onSave to auto-refresh footers on save (only in files that already have one).

Scoping to one game folder

By default every .rpy in the workspace is analyzed. In a monorepo with several copies of a game (e.g. prod/ and nonprod/), run Ren'Py: Select Game Folder — it detects candidate game roots (folders containing options.rpy/script.rpy) and saves your pick to the workspace setting renpy-analytics.gameDir. The current scope is shown at the top of the Ren'Py Analytics view; click it to change.

Commands (Ctrl+Shift+P)

All commands are prefixed Ren'Py: in the command palette. First-time users can also follow the Get started with Ren'Py Analytics walkthrough (Help → Get Started), which runs each of the core commands with one click.

Command What it does
Ren'Py: Analyze Project Build the flow graph + metrics; populate views and Problems panel
Ren'Py: Show Story Flow Graph Interactive story map (whole project)
Ren'Py: Show Story Flow Graph (Current File) Story map scoped to the active script
Ren'Py: Export Flow Graph (DOT) Graphviz DOT export of either graph
Ren'Py: Playtest From Here Launch the game warped to the cursor line
Ren'Py: Fold All Labels Collapse the file to its label skeleton
Ren'Py: Fold All Menus Collapse menu blocks and choices
Ren'Py: Fold All Labels and Menus Collapse everything foldable
Ren'Py: Analyze Save-File Safety (Workspace) Save-safety scan on demand
Ren'Py: Export Analysis Report Markdown (humans) or JSON (CI) report
Ren'Py: Generate File Footer Append/update the summary footer in the active file
Ren'Py: Generate Footers for All Files Same, workspace-wide
Ren'Py: Select Game Folder Scope analysis to one game root (monorepos)

Development

npm install
npm run compile   # type-check + build to out/
npm test          # unit tests (vitest)

Press F5 in VS Code to launch an Extension Development Host.

Credits

  • Story-graph concepts and annotation-tag conventions inspired by renpy-graphviz (AGPLv3 — no code reused; independent TypeScript implementation).
  • Choice-consequence analysis ideas (planned) adapted from universal-renpy-walkthrough (MIT).

License

MIT

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft