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
+28
-13
@@ -12,6 +12,8 @@
|
||||
import { h } from '../dom.js';
|
||||
import { statPanel, characterCard, changeList, statusBar } from '../components.js';
|
||||
import { requirementText, turnHeading, upcomingUpkeep } from '../format.js';
|
||||
import { presentPeople } from '../../engine/cast.js';
|
||||
import { fillText } from '../../engine/text.js';
|
||||
|
||||
export function turnScreen(pack, state, schema, view, actions) {
|
||||
const heading = turnHeading(pack, state);
|
||||
@@ -33,12 +35,12 @@ export function turnScreen(pack, state, schema, view, actions) {
|
||||
h('div', { class: 'layout' },
|
||||
h('main', { class: 'scene' },
|
||||
view.phase === 'outcome'
|
||||
? outcomeView(pack, view, actions)
|
||||
: choosingView(pack, view, actions),
|
||||
? outcomeView(pack, state, view, actions)
|
||||
: choosingView(pack, state, view, actions),
|
||||
),
|
||||
h('aside', { class: 'people' },
|
||||
h('h2', { class: 'people__heading' }, pack.peopleHeading ?? 'The people'),
|
||||
(pack.characters ?? []).map((character) => characterCard(pack, state, schema, character)),
|
||||
presentPeople(pack, state).map((person) => characterCard(pack, state, schema, person)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -84,44 +86,55 @@ function diary(pack, state) {
|
||||
);
|
||||
}
|
||||
|
||||
function choosingView(pack, view, actions) {
|
||||
function choosingView(pack, state, view, actions) {
|
||||
const { event, options } = view.turn;
|
||||
const text = (t) => fillText(pack, state, t, view.turn.bindings);
|
||||
const visible = options.filter((o) => !o.hidden);
|
||||
// The first option a player can actually take gets the focus, so Enter plays
|
||||
// it and Tab walks to the others.
|
||||
const firstAvailable = visible.find((o) => o.available)?.option.id;
|
||||
|
||||
return h('section', { class: 'scenario' },
|
||||
h('p', { class: 'scenario__text' }, event.description),
|
||||
h('p', { class: 'scenario__text' }, text(event.description)),
|
||||
h('ul', { class: 'options' },
|
||||
visible.map(({ option, available, failed }) => h('li', { class: 'options__item' },
|
||||
visible.map(({ option, available, failed, odds }) => h('li', { class: 'options__item' },
|
||||
h('button', {
|
||||
class: `option${available ? '' : ' option--locked'}`,
|
||||
disabled: !available,
|
||||
dataset: option.id === firstAvailable ? { autofocus: 'true' } : {},
|
||||
onClick: available ? () => actions.onChoose(option.id) : null,
|
||||
},
|
||||
h('span', { class: 'option__label' }, option.label),
|
||||
!available && h('span', { class: 'option__requirement' }, requirementText(pack, failed)),
|
||||
h('span', { class: 'option__label' }, text(option.label)),
|
||||
// Odds are shown in words, never as a number: "a long shot", "likely".
|
||||
available && odds && h('span', { class: 'option__odds' }, odds.words),
|
||||
!available && h('span', { class: 'option__requirement' }, requirementText(pack, failed, state)),
|
||||
),
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
function outcomeView(pack, view, actions) {
|
||||
function outcomeView(pack, state, view, actions) {
|
||||
const { record, option } = view;
|
||||
// The state has already moved on to the next event, so the names in this
|
||||
// outcome come from the bindings the turn was played with, not the new ones.
|
||||
const text = (t) => fillText(pack, state, t, record.bindings ?? {});
|
||||
const branch = record.check ? option[record.check.outcome] : null;
|
||||
const result = branch ? branch.result : option.result;
|
||||
const said = pack.checks?.said ?? { success: 'It came off.', failure: 'It did not come off.' };
|
||||
|
||||
return h('section', { class: 'outcome' },
|
||||
h('p', { class: 'outcome__chose' }, option.label),
|
||||
option.result && h('p', { class: 'outcome__text' }, option.result),
|
||||
h('p', { class: 'outcome__chose' }, record.optionLabel ?? text(option.label)),
|
||||
record.check && h('p', { class: `outcome__check outcome__check--${record.check.outcome}` },
|
||||
`${capitalise(record.check.odds)} — ${said[record.check.outcome]}`),
|
||||
result && h('p', { class: 'outcome__text' }, text(result)),
|
||||
|
||||
h('div', { class: 'outcome__ledger' },
|
||||
h('h3', { class: 'outcome__heading' }, 'What that changed'),
|
||||
changeList(pack, record.changes),
|
||||
changeList(pack, record.changes, { state }),
|
||||
record.upkeep.length > 0 && h('div', { class: 'outcome__upkeep' },
|
||||
h('h3', { class: 'outcome__heading' }, 'And then the day happened'),
|
||||
changeList(pack, record.upkeep),
|
||||
changeList(pack, record.upkeep, { state }),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -185,3 +198,5 @@ function feedbackWidget(view, actions) {
|
||||
feedback.saved && h('p', { class: 'feedback__saved' }, 'Noted — thank you.'),
|
||||
);
|
||||
}
|
||||
|
||||
const capitalise = (words) => words.charAt(0).toUpperCase() + words.slice(1);
|
||||
|
||||
Reference in New Issue
Block a user