Add chance and a cast that changes between runs, and a pack to try them
An option used to do exactly what it said. Now it may carry a check: one of
four attributes (Nerve, Wits, Charm, Resolve) against a set difficulty or
against a named person's own attribute, leaning on how they regard you, with a
success and a failure. The player sees only the odds in words ("a long shot",
"likely"), never a number. The roll is one draw from the run's generator, so a
run still replays exactly from its seed and choices.
A character may be a role with several authored candidates, one drawn per run,
or a group of two to four drawn into numbered slots. Everyone carries a name, a
description and the same four attributes as the player, so a replayed pack
meets a different office. Text names people as {partner} or {clerk}, and an
event about "a clerk" binds one present clerk for the turn. The draw and the
binding are saved with the run, and a save from before a pack drew a cast gets
the same one every time it is loaded.
All of it is generic and optional: Corporate Ladder and the Frontier use none
of it and play as before. The Counting House, a one-stage office about 1910
built from the story-to-pack catalogue, exercises it, and was tuned against the
conformance suite by measurement. DECISIONS #41. Version 0.4.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018MhABSrqkwTdcybXmheDPr
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
112e558dbb
commit
3108319a2b
+22
-1
@@ -9,6 +9,21 @@
|
||||
|
||||
import { check } from './conditions.js';
|
||||
import { getPath } from './paths.js';
|
||||
import { presentSlots } from './cast.js';
|
||||
import { bindConditions } from './text.js';
|
||||
|
||||
/**
|
||||
* Pick, for each role an event `involves`, one person present in this run. An
|
||||
* event about "a co-worker" is about one particular co-worker for the turn.
|
||||
*/
|
||||
export function bindRoles(pack, state, event, rng) {
|
||||
const bindings = {};
|
||||
for (const role of event.involves ?? []) {
|
||||
const slots = presentSlots(pack, state, role);
|
||||
if (slots.length) bindings[role] = slots.length === 1 ? slots[0] : rng.pick(slots);
|
||||
}
|
||||
return bindings;
|
||||
}
|
||||
|
||||
/** Events that could fire right now, with the reason excluded ones could not. */
|
||||
export function eligibleEvents(pack, state) {
|
||||
@@ -37,6 +52,11 @@ export function eligibleEvents(pack, state) {
|
||||
excluded.push({ event, reason: 'requires', failed: gate.failed });
|
||||
continue;
|
||||
}
|
||||
// An event about a co-worker cannot fire in a run that drew none.
|
||||
if ((event.involves ?? []).some((role) => !presentSlots(pack, state, role).length)) {
|
||||
excluded.push({ event, reason: 'involves' });
|
||||
continue;
|
||||
}
|
||||
eligible.push(event);
|
||||
}
|
||||
|
||||
@@ -60,8 +80,9 @@ export function selectEvent(pack, state, rng) {
|
||||
* strongest signal that the choice set is too narrow.
|
||||
*/
|
||||
export function resolveOptions(state, event) {
|
||||
const bindings = state?.meta?.bindings ?? {};
|
||||
return (event.options ?? []).map((option) => {
|
||||
const gate = check(state, option.requires);
|
||||
const gate = check(state, bindConditions(option.requires, bindings));
|
||||
return {
|
||||
option,
|
||||
available: gate.passed,
|
||||
|
||||
Reference in New Issue
Block a user