Author thoughts on Static Analysis and Guides

The next few paragraphs might be a bit technical, but I have an interest/‘ethics’ question for authors below.

For the past year or so I’ve been working on and off on a Recursive descent parser - Wikipedia for choicescript, mostly for my own curiosity and learning. I’ve reached a point now where I have a one to one representation of the majority of what the language can do in a control flow graph, meaning I can start to do some basic forms of static analysis on any choicescript game as a whole.

I’ve got something that will generate a ‘guide’ for a choicescript game, basically a summary of every choice block + what variables change, and what can change. Additionally I make some rough assumptions about what stats can possibly be at different points of a game (ie. when stats change or are evaluated with an *if).

The Question

My primary concern with just publishing everything as MIT now is author interest. On one hand it may be a valuable way to debug choicescript and understand choice flow especially on larger games, but on the other this may conflict with the sold guides that some authors have opted for.

I’d like to hear from authors here, on that point. Either below or in a dm if you’d like me to generate a guide for your wip.

Example for Choice of the Dragon (spoilers):

choice-of-dragons-guide.md · GitHubThis is a nice rendered version with clickable links, below is raw for on-site viewing. It uses markdown, but this isn’t a requirement.

Snippet For the Opening Two Choices
## <a id="choice-1"></a>Choice 1 [HUB] — startup (purchased)

> …at you.  His horse pounds at the ground, carrying the heavily armored warrior as if he were a child's doll.  The knight sets his lance to attack you.
> 
> How do you defend yourself, O mighty dragon?

1. **I take to the air with a quick beat of my wings.**
   - `brutality %- 10 [50 → 45–50]`
   - → [Choice 2](#choice-2)
2. **I knock the knight from his horse with a slap of my tail.**
   - `cunning %+ 10 [50 → 50–55]`
   - → [Choice 2](#choice-2)
3. **I rush into his charge and tear him to pieces with my claws.**
   - `brutality %+ 10 [50 → 50–55]`
   - → [Choice 2](#choice-2)
4. **A puff of my fiery breath should be enough for him.**
   - `disdain %+ 10 [50 → 50–55]`
   - → [Choice 2](#choice-2)
5. **Restore a saved game.** [if choice_save_allowed]
   - *(no variable changes)*
   - → [Choice 1](#choice-1)

## <a id="choice-2"></a>Choice 2 — startup (victory)

> Do you finish him off, victorious dragon?

1. **Of course!  How dare he attack me?**
   - `brutality %+ 10 [45–55 → 45-60]`
2. **I let him live to warn others of my immense power.**
   - `infamy %+ 15 [50 → 50–58]`
3. **Eh.  Now that the threat is ended, he is beneath my concern.**
   - `infamy %+ 10 [50 → 50–55]`
   - `disdain %+ 10 [50–55 → 50-60]`
Later Snippet
## <a id="branch-split-73"></a>Branch Split 73 — clutchmate
- *if infamy > 70:* → [Choice 75](#choice-75)
- *else:* → [Choice 74](#choice-74)

## <a id="choice-74"></a>Choice 74 [HUB] — clutchmate (victory)

> You have defeated Axilmeus.  He is still alive, but you have beaten him and he will be unable to offer serious resistance.  What do you do with him now?

1. **Axilmeus is too great a threat to leave alive, but he is worthy of a quick and painless death.**
   - `cunning %- 20 [4-98 → 3-78]`
   - `brutality %- 20 [3-98 → 2-78]`
   - `clutchmate_alive false [false → false]`
2. **Like a cat playing with a mouse, I will make Axilmeus suffer before he dies.**
   - *if disdain > 60:*
     - `clutchmate_alive true [false → true]`
     - `cunning %+ 10 [4-98 → 14-98]`
     - `brutality %+ 10 [3-98 → 13-98]`
     - `disdain %+ 15 [61-99 → 67-99]`
     - `infamy %+ 10 [32-78 → 39-80]`
   - *else:*
     - `clutchmate_alive false [false → false]`
     - `brutality %+ 30 [3-98 → 32-99]`
     - `cunning %+ 20 [4-98 → 23-98]`
     - `disdain %- 10 [4-99 → 4-89]`
     - `infamy %+ 30 [32-78 → 52-85]`
3. **Axilmeus must leave my territory, never to return, but he can live.**
   - `clutchmate_alive true [false → true]`
   - `brutality %- 30 [3-98 → 2-69]`
   - `cunning %- 15 [4-98 → 3-83]`
   - `infamy %- 20 [32-78 → 26-78]`

## <a id="choice-75"></a>Choice 75 — heroes

> …a traveling party of adventurers in town.  The people are already calling them "heroes," telling tales of their past deeds.
> 
> Do-gooders like these can be very dangerous.  What do you want to do?

1. **Gather information about them.**
   - `cunning %+ 10 [3-98 → 3-98]`
   - `disdain %- 10 [4-99 → 4-99]`
   - → [Choice 85](#choice-85)
2. **Challenge them to a duel.**
   - *(no variable changes)*
   - → [Choice 76](#choice-76)
3. **Kidnap a princess to lure them out.**
   - *(no variable changes)*
   - → [Choice 81](#choice-81)
4. **Scare them off.**
   - *(no variable changes)*
   - → [Branch Split 86](#branch-split-86)
5. **Ignore them.**
   - `disdain %+ 30 [4-99 → 4-99]`
   - → [Branch Split 79](#branch-split-79)

---

### Path: Challenge them to a duel. *(from [Choice 75](#choice-75), converges at [Choice 87](#choice-87))*

## <a id="choice-76"></a>Choice 76 — heroes (challenge)

> Are you really going to fight them directly on the field of battle?

1. **Yes.**
   - `cunning %- 20 [3-98 → 2-78]`
   - `brutality %+ 10 [2-99 → 12-99]`
   - *if brutality > 60:*
     - `infamy %+ 15 [26-85 → 37-87]`
     - `wealth - 500 [2000-12000 → 1500-11500]`
   - *else:*
     - `wounds + 1 [0–2 → 1-3]`
     - `infamy %- 30 [26-85 → 18-60]`
     - `wealth - 2000 [2000-12000 → 0-10000]`
   - → [Choice 87](#choice-87)
2. **No, I'm luring them into a trap.**
   - `cunning %+ 20 [3-98 → 22-98]`
   - `brutality %- 10 [2-99 → 2-89]`
   - *if brutality < 40:*
     - `infamy %+ 10 [26-85 → 33-87]`
     - `wealth - 500 [2000-12000 → 1500-11500]`
   - *else:*
     - `wounds + 1 [0–2 → 1-3]`
     - `infamy %- 30 [26-85 → 18-60]`
     - `wealth - 2000 [2000-12000 → 0-10000]`
   - → [Choice 87](#choice-87)

---

### Path: Scare them off. *(from [Choice 75](#choice-75), converges at [Choice 87](#choice-87))*

## <a id="branch-split-77"></a>Branch Split 77 — heroes
- *if infamy > 70:* → [Choice 87](#choice-87)
- *else:* → [Choice 78](#choice-78)

## <a id="choice-78"></a>Choice 78 — heroes

> …a brave paladin in shining white armor, challenges you to a duel.  She is flanked by a wizard of some sort, as well as a priest of the goddess of war and a bard.
> 
> Will you accept the challenge?

1. **Yes.**
   - *if brutality > 60:*
     - `infamy %+ 15 [26-85 → 37-87]`
     - `wealth - 500 [2000-12000 → 1500-11500]`
   - *else:*
     - `wounds + 1 [0–2 → 1-3]`
     - `infamy %- 30 [26-85 → 18-60]`
     - `wealth - 2000 [2000-12000 → 0-10000]`
   - → [Choice 87](#choice-87)
2. **No, lure them into a trap.**
   - *if brutality < 40:*
     - `infamy %+ 10 [26-85 → 33-87]`
     - `wealth - 500 [2000-12000 → 1500-11500]`
   - *else:*
     - `wounds + 1 [0–2 → 1-3]`
     - `infamy %- 30 [26-85 → 18-60]`
     - `wealth - 2000 [2000-12000 → 0-10000]`
   - → [Choice 87](#choice-87)

---

### Path: Ignore them. *(from [Choice 75](#choice-75), converges at [Choice 87](#choice-87))*

## <a id="branch-split-79"></a>Branch Split 79 — heroes
- *if infamy < 30:* → [Choice 87](#choice-87)
- *else:* → [Choice 80](#choice-80)

## <a id="choice-80"></a>Choice 80 — heroes

> …a brave paladin in shining white armor, challenges you to a duel.  She is flanked by a wizard of some sort, as well as a priest of the goddess of war and a bard.
> 
> Will you accept the challenge?

1. **Yes.**
   - *if brutality > 60:*
     - `infamy %+ 15 [26-85 → 37-87]`
     - `wealth - 500 [2000-12000 → 1500-11500]`
   - *else:*
     - `wounds + 1 [0–2 → 1-3]`
     - `infamy %- 30 [26-85 → 18-60]`
     - `wealth - 2000 [2000-12000 → 0-10000]`
   - → [Choice 87](#choice-87)
2. **No, lure them into a trap.**
   - `cunning %- 25 [3-98 → 2-98]`
   - `brutality %- 25 [2-99 → 2-99]`
   - `wounds + 1 [0–2 → 1-3]`
   - `infamy %- 30 [26-85 → 18-60]`
   - `wealth - 2000 [2000-12000 → 0-10000]`
   - → [Choice 87](#choice-87)

---

### Path: Kidnap a princess to lure them out. *(from [Choice 75](#choice-75), converges at [Choice 87](#choice-87))*

## <a id="choice-81"></a>Choice 81 — heroes (kidnap)

> Let's be honest.  Are you kidnapping a princess for tactical reasons, or are you kidnapping a princess just because you like kidnapping princesses?

1. **It's not like that!  It's purely strategic!**
   - *(no variable changes)*
2. **A little of column A, a little of column B.**
   - *(no variable changes)*
3. **Mmmm…princesses…**
   - *(no variable changes)*

## <a id="choice-82"></a>Choice 82 — heroes (princess)

> …by air.  You rip off the roof, snatch the princess, and fly away chortling.
> 
> Bound securely in your domain, the princess offers you vast riches if you set her free.  Do you accept her offer?

1. **Yes.**
   - `cunning %- 50 [3-98 → 2-98]`
   - `infamy %- 30 [26-85 → 18-85]`
   - → [Choice 87](#choice-87)
2. **No.**
   - *(no variable changes)*
   - → [Choice 83](#choice-83)

## <a id="choice-83"></a>Choice 83 — heroes (heroes_arrive)

> …the heroes arrive.  Their leader, a brave paladin in shining white armor, challenges you to a duel.  She is flanked by a wizard of some sort, as well as a priest of the goddess of war and a bard.

1. **Accept their challenge.**
   - *(no variable changes)*
   - → [Choice 76](#choice-76)
2. **Kill them on the spot.**
   - *if brutality > 60:*
     - `infamy %+ 15 [26-85 → 37-87]`
     - `wealth - 500 [2000-12000 → 1500-11500]`
   - *else:*
     - `wounds + 1 [0–2 → 1-3]`
     - `infamy %- 30 [26-85 → 18-60]`
     - `wealth - 2000 [2000-12000 → 0-10000]`
   - → [Choice 87](#choice-87)
3. **Kill and eat the princess before their very eyes.**
   - *(no variable changes)*
   - → [Choice 84](#choice-84)

The general idea is that you can make use of this tool to help map out games, what your possible variable ranges are on all playthroughs (not mean/median, but min/max). It should also provide decent error reporting of errors across the entirety of the program similar to quick/random test, but I’ve not developed this very much. You could for instance upload to cogdemos, and run this to double check none of changes you’ve made will generate errors for users on some obscure path you wrote – where you missed a *goto or made an impossible *if in your 400k word wip.

The codebase itself is written in typescript to take advantage of the node installations authors have for choicescript, and to embed in websites if that ends up being direction others are interested in. Currently I’ve left the cfg and generation parts of it unpublished incase the sentiment is generally negative, but the parser is available here:

It’s all here:

GitHub - M3ales/choicescript-tree: Recursive Descent Parser for choicescript · GitHub .

8 Likes

I am not an author so maybe someone who is will disagree but your software doesn’t do anything that a user couldn’t already do, so I don’t see why anyone should/would care.[1]

By that I mean like if you own a choicescript game you can already unpack the game and browse the code, and you could in theory do exactly what your software can do, it would just be time consuming.

So, functionally this is no different then someone writing like a steam achievement guide for a game that has a purchasable guide. Which I think, or maybe hope, most people would agree that nobody in that circumstance would be in the wrong. The author is allowed to sell a guide but equally someone is allowed to write their own.

Hence, I don’t see why anyone should have an issue about this and like ultimately even if someone did have issue imo that isn’t your problem as your software doesn’t do anything illegal.


  1. (Just to be clear I mean specifically the ability to make a guide being an issue as not something anyone should care about.The software itself is neat and you should be proud, especially if you made it without Ai or significant Ai help.) ↩︎

6 Likes

Tools that parse ChoiceScript aren’t anything new. There’s the VS Code extension, and several other scripts and tools people have linked over the years. Even a quick Google reveals stuff like: GitHub - benjaminrosenbaum/choicescript_tools: Some helpful scripts for use with ChoiceScript · GitHub

No one from CoG has taken issue with any of this before, so I’d be surprised if they started now. As for the CFG stuff I hadn’t really thought about using static analysis tools as a way to generate guides (rather than just as debugging/development aids), but it’s an interesting premise. I’m not an author so won’t speak for that, but a high quality, well maintained and reusable parser for Choicescript would be welcome imo.

I’m a little dubious about how much of a more complicated ChoiceScript game you can evaluate statically, but would be happy to see attempts made.

3 Likes

I’ve tried a few WIPs and really I think the hardest thing is large *gosub chains and loop analysis because we don’t have any explicit while/for so I need to write detection logic to try approximate if this looping structure is bounded, what its exit conditions are etc. I originally based the parser off statements on the wiki but unfortunately it seems the random jank that the runtime supports isn’t exactly covered formally :joy:.

I’ve included the full generated guide for Choice of the Dragon choice-of-dragons-guide.md · GitHub

Along with some dataflow statements about possible variable values + their usage sites.

Currently just cleaning up the source and I’ll push it soonish. It’s a bit complicated at the moment but I’m planning to longterm just turn this into a ‘provide a url/files and get outputs’ thing that can run in a browser/as a website.

I am not convinced that the presentation of these guides is meaningfully easier to understand than just downloading and reading the code.

3 Likes

Yeah, I looked over the guide and my first thought was “I’d rather just read the code”.

If there was some way to represent this as a branching graph I think it would be much easier to follow.

Is it mostly ordering and presentation of branches/paths that is unclear?

First pass of this (few bugs at the bottom due to some inlining issue).

You’ll need to scroll right a bunch to find it (its on the right of the page). If you’re viewing it in a browser it was easier to navigate using arrow keys.

Graph SVG

1 Like

This may be a tiny bit off topic but I just wish to point out that this type of use case is fully allowed within intellectual property law (For both U.S and EU at least.) Programming languages can’t be copyrighted but specific written compositions of code can be. Basically the compiler, interpreter, parser, etc types of tooling the language may or may not use are protected works but the language itself like syntax and methods of operation are not. The only relevant EU law I know is SAS Institute Inc. v World Programming Ltd (2012) C-406/10. For the U.S the more established case law on the same kind of idea is Lotus Development Corp. v. Borland International, Inc., 516 U.S. 233 (1996) but there is more recent applicable case law from Google LLC v. Oracle America, Inc., 593 U.S. 1 (2021), and SAS Institute, Inc. v. World Programming Ltd., No. 21-1542 (Fed. Cir. Apr. 6, 2023)[1]. All that is to say is that like it doesn’t really matter if CoG does or does not care because there is no viable path for recourse even if they did take issue with it and even attempting to get recourse if they still wanted to try would be difficult[2].


  1. Yes this is the same case as the EU one,SAS Institute Inc. filed it in both countries. (The country for the EU side was the UK as they were still in the EU at the time but certain issues in the case were heard by an EU court after being referred to said EU court by the UK court.) ↩︎

  2. To explain, attempting is difficult because in the U.S only federal courts can hear copyright cases. So, that means filling with a federal district court which is both expensive and requires that the plaintiffs attorney certify the claim under penalty of Rule 11 sanctions (Which are penalties the court can apply for filing filing frivolous claims, making false statements, or failing to conduct a reasonable inquiry into the facts and law of a case.) And even once filed the court has to agree to hear the case which is not a guarantee as the court could dismiss it if the court feels it does not meet the Twombly/Iqbal standard. ↩︎

This was in the back of my mind and maybe motivated this post’s format more than I realised. Perhaps it should have been an email to CoG about the potential for conflict of interest, before asking the community itself and sort of implicitly asking the question?

In my mind:

  • with an ast parser, you’re most of the way to an interpreter anyways, it’s actually more work doing anything more statically. The whole CFG pipeline is a lot of complexity to make deductions about how the program behaves without running it. For clarity interpreter here is the cs runtime/engine. (Without ui)
  • I prefer to MIT my “works” because I’ve been slapped by other people being vindictive with licencing before. Mostly principle, some ego. I did look at the cs licence and that didn’t make me feel very warm and fuzzy inside.
  • I’ve used LLMs to build the post parser pipeline, namely CFG+data flow. The AST building parts are mine exclusively. So kind of nobody owns those parts? MIT makes that simple.
  • I have reverse engineered most of the behaviour from attempting to parse CS games, and reading either forum posts on related structures or the wiki.

So while it might be no grounds for legal action, I think it’s only fair to ask if there are massive objections to it.

I don’t want to be seen as someone trying to infringe on this platform; I’d like to hopefully make some of my own learning/interest useful to some of the cs community here.

2 Likes

LLMs in the pipeline are a really interesting topic on their own. We’re basically moving toward hybrid systems where traditional static analysis gets augmented with probabilistic models, which kind of shifts how we think about “determinism” in tooling. It also raises questions around reproducibility, since the same codebase might get interpreted differently depending on the model or even the version being used. In the end, it feels important to clearly separate where formal verification ends and where heuristic interpretation begins.

I was a bit unclear about that. I use llms for code generation, not for performing analysis. They could potentially be useful given the outputs of this repo but I wouldn’t use them as part of the generation of this. Inference isn’t very good at being consistent in my mind. It could definitely help explain what is wrong in some of the errors to someone with less experience with choicescript.