pf2e-subsystems 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +31 -0
- package/README.md +110 -0
- package/bin/cli.mjs +86 -0
- package/data/reference.generated.js +6 -0
- package/data/subsystems.generated.js +7 -0
- package/dist/icon.svg +7 -0
- package/dist/index.html +1930 -0
- package/dist/manifest.webmanifest +13 -0
- package/dist/sw.js +60 -0
- package/notice.md +132 -0
- package/package.json +52 -0
- package/src/content.js +234 -0
- package/src/engine.js +1320 -0
- package/src/icon.svg +7 -0
- package/src/manifest.webmanifest +13 -0
- package/src/styles.css +294 -0
- package/src/sw.js +60 -0
- package/src/template.html +69 -0
- package/tools/build.py +62 -0
- package/tools/build_reference.py +252 -0
- package/tools/build_subsystems.py +315 -0
- package/tools/test.mjs +370 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "PF2e Subsystems",
|
|
3
|
+
"short_name": "PF2e Subsystems",
|
|
4
|
+
"description": "Run Pathfinder 2e's Victory Point subsystems at the table: chases, infiltrations, influence encounters and research.",
|
|
5
|
+
"start_url": ".",
|
|
6
|
+
"scope": ".",
|
|
7
|
+
"display": "standalone",
|
|
8
|
+
"background_color": "#14161b",
|
|
9
|
+
"theme_color": "#14161b",
|
|
10
|
+
"icons": [
|
|
11
|
+
{ "src": "icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any maskable" }
|
|
12
|
+
]
|
|
13
|
+
}
|
package/dist/sw.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* Service worker — offline support for the hosted (GitHub Pages) PWA.
|
|
2
|
+
Network-first for the app page (fresh when online, cached when offline),
|
|
3
|
+
cache-first for static assets. Cache version is stamped at build time so a
|
|
4
|
+
redeploy refreshes clients automatically. */
|
|
5
|
+
const CACHE = "pf2e-subsystems-20260914005114";
|
|
6
|
+
/* Every cache this app has ever made starts with PREFIX. The sweep below is
|
|
7
|
+
scoped to it on purpose: the PF2e tools share an origin when they are
|
|
8
|
+
served from one host (GitHub Pages does it, and so does the toolbox), and an
|
|
9
|
+
unscoped sweep would delete the other two apps' offline caches every time
|
|
10
|
+
this one activated. */
|
|
11
|
+
const PREFIX = "pf2e-subsystems-";
|
|
12
|
+
const SHELL = ["./", "./index.html", "./manifest.webmanifest", "./icon.svg"];
|
|
13
|
+
|
|
14
|
+
self.addEventListener("install", (e) => {
|
|
15
|
+
e.waitUntil(
|
|
16
|
+
caches.open(CACHE)
|
|
17
|
+
.then((c) => c.addAll(SHELL).catch(() => {}))
|
|
18
|
+
.then(() => self.skipWaiting())
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
self.addEventListener("activate", (e) => {
|
|
23
|
+
e.waitUntil(
|
|
24
|
+
caches.keys()
|
|
25
|
+
.then((keys) => Promise.all(keys.filter((k) => k.startsWith(PREFIX) && k !== CACHE)
|
|
26
|
+
.map((k) => caches.delete(k))))
|
|
27
|
+
.then(() => self.clients.claim())
|
|
28
|
+
);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
self.addEventListener("fetch", (e) => {
|
|
32
|
+
const req = e.request;
|
|
33
|
+
if (req.method !== "GET") return;
|
|
34
|
+
|
|
35
|
+
if (req.mode === "navigate") {
|
|
36
|
+
e.respondWith(
|
|
37
|
+
fetch(req)
|
|
38
|
+
.then((res) => {
|
|
39
|
+
const copy = res.clone();
|
|
40
|
+
caches.open(CACHE).then((c) => c.put("./index.html", copy));
|
|
41
|
+
return res;
|
|
42
|
+
})
|
|
43
|
+
.catch(() => caches.match("./index.html").then((r) => r || caches.match("./")))
|
|
44
|
+
);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
e.respondWith(
|
|
49
|
+
caches.match(req).then((r) =>
|
|
50
|
+
r ||
|
|
51
|
+
fetch(req).then((res) => {
|
|
52
|
+
if (res.ok && new URL(req.url).origin === self.location.origin) {
|
|
53
|
+
const copy = res.clone();
|
|
54
|
+
caches.open(CACHE).then((c) => c.put(req, copy));
|
|
55
|
+
}
|
|
56
|
+
return res;
|
|
57
|
+
}).catch(() => r)
|
|
58
|
+
)
|
|
59
|
+
);
|
|
60
|
+
});
|
package/notice.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Third-Party Notices & Attribution
|
|
2
|
+
|
|
3
|
+
**pf2e-subsystems**
|
|
4
|
+
|
|
5
|
+
This file records the licensing of the two distinct kinds of material in this
|
|
6
|
+
package: the application **code**, and the Pathfinder **rules text** it bundles
|
|
7
|
+
(the GM Core subsystems chapter, plus condition and action descriptions). They
|
|
8
|
+
are covered by different licenses and the distinction below is intentional.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Application code
|
|
13
|
+
|
|
14
|
+
All original source code in this package is released under the **MIT License**.
|
|
15
|
+
See the [`LICENSE`](./LICENSE) file for the full text.
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
Copyright (c) 2026 Whyte Erminae
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The MIT License covers only the code authored for this project. It does **not**
|
|
22
|
+
cover the Pathfinder game content described in section 2, which carries its own
|
|
23
|
+
license and notice. The encounters a GM builds in this app are their own; the app
|
|
24
|
+
stores them in the GM's browser and transmits them nowhere.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 2. Pathfinder rules content (ORC License)
|
|
29
|
+
|
|
30
|
+
The bundled reference data consists of the **rules expressions of Pathfinder
|
|
31
|
+
Second Edition subsystems, conditions and actions** — their names and functional
|
|
32
|
+
effect text, including the sample chase obstacles and the Victory Point scales
|
|
33
|
+
tables. Under the Open RPG Creative License this is **Licensed Material** (the
|
|
34
|
+
copyrighted expression of game mechanics), used here under that license. It
|
|
35
|
+
originates with **Paizo Inc.** and is derived from the open-source [Foundry VTT
|
|
36
|
+
`pf2e` system](https://github.com/foundryvtt/pf2e) data — the subsystem text from
|
|
37
|
+
the GM Screen journal (`packs/pf2e/journals/gm-screen.json`), the conditions and
|
|
38
|
+
actions from their own packs.
|
|
39
|
+
|
|
40
|
+
Every page of subsystem text carries the Paizo page range it came from; those
|
|
41
|
+
citations are generated into `data/subsystems.generated.js` alongside the text
|
|
42
|
+
and are shown in the app's Reference tab.
|
|
43
|
+
|
|
44
|
+
### ORC Notice
|
|
45
|
+
|
|
46
|
+
This product is licensed under the ORC License located at the Library of Congress
|
|
47
|
+
at TX 9-307-067 and available online at various locations including
|
|
48
|
+
paizo.com/orclicense, azoralaw.com/orclicense, and others. All warranties are
|
|
49
|
+
disclaimed as set forth therein.
|
|
50
|
+
|
|
51
|
+
### Attribution
|
|
52
|
+
|
|
53
|
+
This product is based on the following Licensed Material:
|
|
54
|
+
|
|
55
|
+
Pathfinder Player Core © 2023 Paizo Inc., Designed by Logan Bonner, Jason
|
|
56
|
+
Bulmahn, Stephen Radney-MacFarland, and Mark Seifter.
|
|
57
|
+
|
|
58
|
+
Pathfinder Player Core 2 © 2024 Paizo Inc.
|
|
59
|
+
|
|
60
|
+
Pathfinder GM Core © 2023 Paizo Inc., Designed by Logan Bonner and Mark Seifter.
|
|
61
|
+
|
|
62
|
+
Pathfinder Treasure Vault (Remastered) © 2025 Paizo Inc.
|
|
63
|
+
|
|
64
|
+
Pathfinder Dark Archive (Remastered) © 2025 Paizo Inc.
|
|
65
|
+
|
|
66
|
+
> NOTE — The precise list of source books is generated into `GENERATED_REF_META`
|
|
67
|
+
> in `data/reference.generated.js` and `GENERATED_SUB_META` in
|
|
68
|
+
> `data/subsystems.generated.js`, from the licenses and citations recorded in the
|
|
69
|
+
> Foundry pf2e data. Re-sync this section whenever either is rebuilt
|
|
70
|
+
> (`npm run build:ref`, `npm run build:sub`).
|
|
71
|
+
|
|
72
|
+
### Reserved Material
|
|
73
|
+
|
|
74
|
+
Reserved Material elements are excluded from this product. To avoid confusion,
|
|
75
|
+
such items include all trademarks, registered trademarks, and proper nouns,
|
|
76
|
+
artwork, dialogue, organizations, plots, and storylines. Where a rule's mechanics
|
|
77
|
+
are included, any such Reserved Material has been omitted or genericized.
|
|
78
|
+
|
|
79
|
+
### Crediting this work downstream
|
|
80
|
+
|
|
81
|
+
pf2e-primer © 2026 Whyte Erminae.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## 3. Pathfinder rules content (OGL)
|
|
86
|
+
|
|
87
|
+
Three of the bundled action descriptions originate in pre-Remaster books that
|
|
88
|
+
were published under the **Open Game License v1.0a** rather than the ORC License
|
|
89
|
+
(2 from *Pathfinder Secrets of Magic*, 1 from *Pathfinder Gamemastery Guide*).
|
|
90
|
+
They are included here as Open Game Content under that license.
|
|
91
|
+
|
|
92
|
+
### OGL Section 15
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
Open Game License v 1.0a © 2000, Wizards of the Coast, Inc.
|
|
96
|
+
|
|
97
|
+
System Reference Document © 2000, Wizards of the Coast, Inc.; Authors Jonathan
|
|
98
|
+
Tweet, Monte Cook, and Skip Williams, based on material by E. Gary Gygax and
|
|
99
|
+
Dave Arneson.
|
|
100
|
+
|
|
101
|
+
Pathfinder Secrets of Magic © 2021, Paizo Inc.; Authors: Amirali Attar Olyaee,
|
|
102
|
+
Alexander Augunas, Kate Baker, James Case, Jeff Collins, Vanessa Hoskins,
|
|
103
|
+
James Jacobs, Jason Keeley, Lyz Liddell, Ron Lundeen, Patchen Mortimer, Stephen
|
|
104
|
+
Radney-MacFarland, David N. Ross, Michael Sayre, Owen K.C. Stephens, Mark
|
|
105
|
+
Seifter, Ashton Sperry, Andrew White.
|
|
106
|
+
|
|
107
|
+
Pathfinder Gamemastery Guide © 2020, Paizo Inc.; Authors: Alexander Augunas,
|
|
108
|
+
Jesse Benner, John Bennett, Logan Bonner, Clinton J. Boomer, Jason Bulmahn,
|
|
109
|
+
James Case, Paris Crenshaw, Jesse Decker, Robert N. Emerson, Eleanor Ferron,
|
|
110
|
+
Jaym Gates, Matthew Goetz, T.H. Gulliver, Kev Hamilton, Sasha Laranoa Harving,
|
|
111
|
+
BJ Hensley, Vanessa Hoskins, Brian R. James, Jason LeMaitre, Lyz Liddell, Luis
|
|
112
|
+
Loza, Ron Lundeen, Colm Lundberg, Stephen Radney-MacFarland, Jessica Redekop,
|
|
113
|
+
Patrick Renie, Mikhail Rekun, Alistair Rigg, David N. Ross, David Schwartz,
|
|
114
|
+
Michael Sayre, Amber Stewart, Christina Stiles, Landon Winkler, Linda Zayas-Palmer.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
> NOTE — Regenerate this list from `GENERATED_REF_META` whenever the reference
|
|
118
|
+
> data is rebuilt (`npm run build:ref`); the licence recorded per entry decides
|
|
119
|
+
> which section it belongs in.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 4. Compatibility / trademark notice
|
|
124
|
+
|
|
125
|
+
Pathfinder and all associated marks and logos are trademarks of Paizo Inc.
|
|
126
|
+
This package is an independent work, is **not** published, endorsed, sponsored,
|
|
127
|
+
or affiliated with Paizo Inc., and no such association is implied. Any reference
|
|
128
|
+
to Pathfinder is made under nominative fair use solely to identify the game
|
|
129
|
+
system with which this material is compatible.
|
|
130
|
+
|
|
131
|
+
The explanatory prose, the two pregenerated characters, the teaching NPC and all
|
|
132
|
+
the interactive demos are original work by the author of this package.
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pf2e-subsystems",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Run Pathfinder 2e's Victory Point subsystems at the table — chases, infiltrations, influence encounters and research (single self-contained HTML).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pathfinder",
|
|
7
|
+
"pf2e",
|
|
8
|
+
"gm",
|
|
9
|
+
"subsystems",
|
|
10
|
+
"victory-points",
|
|
11
|
+
"chase",
|
|
12
|
+
"infiltration",
|
|
13
|
+
"influence",
|
|
14
|
+
"research",
|
|
15
|
+
"ttrpg"
|
|
16
|
+
],
|
|
17
|
+
"homepage": "https://thewhytewolf.github.io/pf2esubsystems/",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/TheWhyteWolf/pf2esubsystems.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/TheWhyteWolf/pf2esubsystems/issues"
|
|
24
|
+
},
|
|
25
|
+
"author": "Whyte",
|
|
26
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
27
|
+
"type": "module",
|
|
28
|
+
"bin": {
|
|
29
|
+
"pf2e-subsystems": "bin/cli.mjs"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=18"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"bin/",
|
|
36
|
+
"src/",
|
|
37
|
+
"tools/",
|
|
38
|
+
"data/",
|
|
39
|
+
"dist/",
|
|
40
|
+
"notice.md"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "python3 tools/build.py",
|
|
44
|
+
"build:ref": "python3 tools/build_reference.py --src \"$PF2E_DATA/packs/pf2e\" --out data/reference.generated.js",
|
|
45
|
+
"build:sub": "python3 tools/build_subsystems.py --src \"$PF2E_DATA/packs/pf2e/journals/gm-screen.json\" --out data/subsystems.generated.js",
|
|
46
|
+
"test": "node tools/test.mjs",
|
|
47
|
+
"check": "npm run build && npm test"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"jsdom": "^24.0.0"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/content.js
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
src/content.js — the app's own configuration.
|
|
3
|
+
|
|
4
|
+
Every word of rules PROSE comes from data/subsystems.generated.js and
|
|
5
|
+
data/reference.generated.js, which are built from the open-source Foundry
|
|
6
|
+
pf2e data so they can't drift from GM Core. This file is the other half:
|
|
7
|
+
the machine-readable shape of each subsystem — its point pools, how a
|
|
8
|
+
degree of success moves them, and the defaults a GM can override.
|
|
9
|
+
|
|
10
|
+
engine.js only renders what's here. Adding a sixth subsystem means adding
|
|
11
|
+
an entry to SUBSYSTEMS, not writing a new screen.
|
|
12
|
+
============================================================ */
|
|
13
|
+
|
|
14
|
+
/* The four outcomes, in the order they appear on every button row. */
|
|
15
|
+
const DEGREES = [
|
|
16
|
+
{ key: "critSuccess", label: "Critical success", short: "Crit succ", tone: "heal" },
|
|
17
|
+
{ key: "success", label: "Success", short: "Success", tone: "ok" },
|
|
18
|
+
{ key: "failure", label: "Failure", short: "Failure", tone: "warn" },
|
|
19
|
+
{ key: "critFailure", label: "Critical failure", short: "Crit fail", tone: "harm" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/* Where the rules say "the GM decides", the app asks instead of choosing.
|
|
23
|
+
These strings surface in the prep editor next to the field in question. */
|
|
24
|
+
const GM_CALLS = {
|
|
25
|
+
obstacleCost:
|
|
26
|
+
"How many points an obstacle needs is yours to set. The default follows " +
|
|
27
|
+
"GM Core's rule of thumb; raise it for a particularly demanding obstacle.",
|
|
28
|
+
awarenessFail:
|
|
29
|
+
"When the infiltration fails is yours to set. The default is twice the " +
|
|
30
|
+
"Infiltration Points the job needs, with a complication at each quarter.",
|
|
31
|
+
roundLength:
|
|
32
|
+
"A round is however long you say it is — the book gives a range, not a number.",
|
|
33
|
+
thresholds:
|
|
34
|
+
"Thresholds and what reaching one grants are entirely yours; the book " +
|
|
35
|
+
"gives scales, not contents.",
|
|
36
|
+
thresholdProgress:
|
|
37
|
+
"Counted in obstacles cleared, not points — the points reset at every " +
|
|
38
|
+
"obstacle, so they are not what measures progress across the whole run.",
|
|
39
|
+
researchCap:
|
|
40
|
+
"Each research check has a maximum RP. Past it the PCs learn nothing new " +
|
|
41
|
+
"from that source and have to go looking elsewhere.",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const SUBSYSTEMS = {
|
|
45
|
+
/* ---------------------------------------------------------- */
|
|
46
|
+
chase: {
|
|
47
|
+
key: "chase",
|
|
48
|
+
name: "Chase",
|
|
49
|
+
glyph: "»",
|
|
50
|
+
rulesPage: "chases",
|
|
51
|
+
blurb: "A run of obstacles, one action per PC per round. The group's " +
|
|
52
|
+
"points clear the obstacle in front of them — and the extras " +
|
|
53
|
+
"don't carry over.",
|
|
54
|
+
pools: [{ key: "cp", name: "Chase Points", abbr: "CP", min: 0, role: "goal" }],
|
|
55
|
+
scale: {
|
|
56
|
+
critSuccess: { cp: 2 },
|
|
57
|
+
success: { cp: 1 },
|
|
58
|
+
failure: {},
|
|
59
|
+
critFailure: { cp: -1 },
|
|
60
|
+
},
|
|
61
|
+
structure: "sequence",
|
|
62
|
+
/* Chase Points reset at every obstacle, so a threshold counted in points
|
|
63
|
+
would be meaningless. What advances across a chase is obstacles cleared. */
|
|
64
|
+
thresholdBasis: "progress",
|
|
65
|
+
/* GM Core: half the obstacles need one fewer point than the party size,
|
|
66
|
+
half need two fewer, minimum 1. */
|
|
67
|
+
obstacleCost: { base: "party", offsets: [1, 2], min: 1 },
|
|
68
|
+
presets: [
|
|
69
|
+
{ key: "short", label: "Short", obstacles: 6, note: "about 10–20 minutes of game time" },
|
|
70
|
+
{ key: "medium", label: "Medium", obstacles: 8, note: "about 15–25 minutes" },
|
|
71
|
+
{ key: "long", label: "Long", obstacles: 10, note: "about 20–30 minutes" },
|
|
72
|
+
],
|
|
73
|
+
extras: [
|
|
74
|
+
{ key: "cantAct", label: "Couldn't act", deltas: { cp: -1 },
|
|
75
|
+
note: "A PC who passes their turn or can't act costs the group 1 CP." },
|
|
76
|
+
{ key: "bypass", label: "Bypassed it", deltas: { cp: 1 },
|
|
77
|
+
note: "An action that gets past the obstacle without a check — a spell, " +
|
|
78
|
+
"usually 1 CP, or 2 if it was exceptionally helpful." },
|
|
79
|
+
],
|
|
80
|
+
obstacleLibrary: true,
|
|
81
|
+
asks: ["obstacleCost", "thresholdProgress"],
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/* ---------------------------------------------------------- */
|
|
85
|
+
infiltration: {
|
|
86
|
+
key: "infiltration",
|
|
87
|
+
name: "Infiltration",
|
|
88
|
+
glyph: "◑",
|
|
89
|
+
rulesPage: "infiltration",
|
|
90
|
+
blurb: "Two pools running against each other: points toward the job, and " +
|
|
91
|
+
"the opposition slowly noticing you. A failed check costs you both.",
|
|
92
|
+
pools: [
|
|
93
|
+
{ key: "ip", name: "Infiltration Points", abbr: "IP", min: 0, role: "goal" },
|
|
94
|
+
{ key: "ap", name: "Awareness Points", abbr: "AP", min: 0, role: "hazard" },
|
|
95
|
+
],
|
|
96
|
+
scale: {
|
|
97
|
+
critSuccess: { ip: 2 },
|
|
98
|
+
success: { ip: 1 },
|
|
99
|
+
failure: { ap: 1 },
|
|
100
|
+
critFailure: { ap: 2 },
|
|
101
|
+
},
|
|
102
|
+
/* The one everybody forgets at the table. */
|
|
103
|
+
roundEnd: { ap: 1 },
|
|
104
|
+
roundEndNote: "Each infiltration round that passes adds 1 Awareness Point.",
|
|
105
|
+
structure: "sequence",
|
|
106
|
+
/* As with a chase: progress is obstacles, not the points inside one. The
|
|
107
|
+
ladder that matters here is Awareness, and that one is derived. */
|
|
108
|
+
thresholdBasis: "progress",
|
|
109
|
+
obstacleModes: [
|
|
110
|
+
{ key: "group", label: "Group", note: "The party pools its points; cleared once for everyone." },
|
|
111
|
+
{ key: "individual", label: "Individual", note: "Every PC has to earn the points separately." },
|
|
112
|
+
],
|
|
113
|
+
/* Failure at twice the IP the job needs, with a complication each quarter. */
|
|
114
|
+
awarenessRule: { multiplier: 2, steps: 4 },
|
|
115
|
+
edge: {
|
|
116
|
+
key: "ep", name: "Edge Points", abbr: "EP",
|
|
117
|
+
note: "Earned by preparation activities beforehand. Spend 1 to turn a " +
|
|
118
|
+
"failure or critical failure into a success — no points to the " +
|
|
119
|
+
"opposition, 1 IP to you.",
|
|
120
|
+
},
|
|
121
|
+
prepActivities: [
|
|
122
|
+
{ slug: "bribe-contact", name: "Bribe Contact" },
|
|
123
|
+
{ slug: "forge-documents", name: "Forge Documents" },
|
|
124
|
+
{ slug: "gain-contact", name: "Gain Contact" },
|
|
125
|
+
{ slug: "gossip", name: "Gossip" },
|
|
126
|
+
{ slug: "scout-location", name: "Scout Location" },
|
|
127
|
+
{ slug: "secure-disguises", name: "Secure Disguises" },
|
|
128
|
+
],
|
|
129
|
+
extras: [
|
|
130
|
+
{ key: "helped", label: "Helped without a check", deltas: { ip: 1 },
|
|
131
|
+
note: "A spell or action that just works — usually 1 IP, 2 if it was " +
|
|
132
|
+
"particularly helpful." },
|
|
133
|
+
{ key: "noticed", label: "Something went wrong", deltas: { ap: 1 },
|
|
134
|
+
note: "Anything disruptive the rules don't price. Your call." },
|
|
135
|
+
],
|
|
136
|
+
asks: ["awarenessFail", "obstacleCost", "thresholdProgress"],
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
/* ---------------------------------------------------------- */
|
|
140
|
+
influence: {
|
|
141
|
+
key: "influence",
|
|
142
|
+
name: "Influence",
|
|
143
|
+
glyph: "◇",
|
|
144
|
+
rulesPage: "influence",
|
|
145
|
+
blurb: "A social encounter in rounds. Each PC either works on the NPC or " +
|
|
146
|
+
"works out what the NPC actually responds to.",
|
|
147
|
+
pools: [{ key: "ip", name: "Influence Points", abbr: "IP", min: 0, role: "goal" }],
|
|
148
|
+
scale: {
|
|
149
|
+
critSuccess: { ip: 2 },
|
|
150
|
+
success: { ip: 1 },
|
|
151
|
+
failure: {},
|
|
152
|
+
critFailure: { ip: -1 },
|
|
153
|
+
},
|
|
154
|
+
structure: "single",
|
|
155
|
+
roundLength: { low: "15 minutes", high: "an hour" },
|
|
156
|
+
/* Each PC acts once per round, choosing one of these. */
|
|
157
|
+
actions: [
|
|
158
|
+
{ key: "influence", name: "Influence", slug: "influence", scored: true },
|
|
159
|
+
{ key: "discover", name: "Discover", slug: "discover", scored: false,
|
|
160
|
+
/* How many facts a Discover reveals. -1 means the GM feeds them a
|
|
161
|
+
plausible lie, which the app records but does not invent. */
|
|
162
|
+
reveals: { critSuccess: 2, success: 1, failure: 0, critFailure: -1 } },
|
|
163
|
+
],
|
|
164
|
+
discoverNote:
|
|
165
|
+
"On a critical failure the PC learns something incorrect. The app marks " +
|
|
166
|
+
"the round; what the lie is, is yours.",
|
|
167
|
+
asks: ["roundLength", "thresholds"],
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
/* ---------------------------------------------------------- */
|
|
171
|
+
research: {
|
|
172
|
+
key: "research",
|
|
173
|
+
name: "Research",
|
|
174
|
+
glyph: "❖",
|
|
175
|
+
rulesPage: "research",
|
|
176
|
+
blurb: "Points against thresholds that hand over information — with each " +
|
|
177
|
+
"source drying up once it has given all it has.",
|
|
178
|
+
pools: [{ key: "rp", name: "Research Points", abbr: "RP", min: 0, role: "goal" }],
|
|
179
|
+
scale: {
|
|
180
|
+
critSuccess: { rp: 2 },
|
|
181
|
+
success: { rp: 1 },
|
|
182
|
+
failure: {},
|
|
183
|
+
critFailure: { rp: -1 },
|
|
184
|
+
},
|
|
185
|
+
structure: "checks",
|
|
186
|
+
roundLength: { low: "10 minutes", high: "a full day" },
|
|
187
|
+
checkNote:
|
|
188
|
+
"List the skills for a source lowest DC first — that's the order the " +
|
|
189
|
+
"book uses, and it reads as a hint to the players.",
|
|
190
|
+
asks: ["researchCap", "roundLength", "thresholds"],
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
/* ---------------------------------------------------------- */
|
|
194
|
+
custom: {
|
|
195
|
+
key: "custom",
|
|
196
|
+
name: "Victory Points",
|
|
197
|
+
glyph: "◆",
|
|
198
|
+
rulesPage: "victory-points",
|
|
199
|
+
blurb: "The bare framework the other four are built on. Name your points, " +
|
|
200
|
+
"set your thresholds, and run anything — reputation, a duel, a " +
|
|
201
|
+
"hexploration, a race against a rival crew.",
|
|
202
|
+
pools: [{ key: "vp", name: "Victory Points", abbr: "VP", min: 0, role: "goal" }],
|
|
203
|
+
structure: "single",
|
|
204
|
+
renameable: true,
|
|
205
|
+
/* The two structures GM Core defines. The scale comes from the mode. */
|
|
206
|
+
modes: [
|
|
207
|
+
{ key: "accumulating", label: "Accumulating",
|
|
208
|
+
note: "Start at nothing and climb toward the end point.",
|
|
209
|
+
scale: {
|
|
210
|
+
critSuccess: { vp: 2 },
|
|
211
|
+
success: { vp: 1 },
|
|
212
|
+
failure: {},
|
|
213
|
+
critFailure: { vp: -1 },
|
|
214
|
+
} },
|
|
215
|
+
{ key: "diminishing", label: "Diminishing",
|
|
216
|
+
note: "Start at the end point and try not to lose it. On a critical " +
|
|
217
|
+
"success the PCs gain a point only if regaining ground is " +
|
|
218
|
+
"possible — otherwise treat it as a success.",
|
|
219
|
+
startAtGoal: true,
|
|
220
|
+
scale: {
|
|
221
|
+
critSuccess: { vp: 1 },
|
|
222
|
+
success: {},
|
|
223
|
+
failure: { vp: -1 },
|
|
224
|
+
critFailure: { vp: -2 },
|
|
225
|
+
} },
|
|
226
|
+
],
|
|
227
|
+
/* Presets come from the VP Scales table in the generated data. */
|
|
228
|
+
scalePresets: true,
|
|
229
|
+
asks: ["thresholds"],
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
/* The order they appear in the "new encounter" picker. */
|
|
234
|
+
const SUBSYSTEM_ORDER = ["chase", "infiltration", "influence", "research", "custom"];
|