@craftrpgs/cli 0.2.0 → 0.2.1
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/dist/bin.js +271 -23
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -17851,6 +17851,9 @@ function contentTypeForContentExtension(extension) {
|
|
|
17851
17851
|
// ../shared/src/constants/gm-engine.ts
|
|
17852
17852
|
var GM_ENGINES = ["v1", "v2"];
|
|
17853
17853
|
|
|
17854
|
+
// ../shared/src/game-time.ts
|
|
17855
|
+
var TIME_TRACKER_KEY = "time";
|
|
17856
|
+
|
|
17854
17857
|
// ../shared/src/platform-instructions.ts
|
|
17855
17858
|
var PLATFORM_INSTRUCTIONS = [
|
|
17856
17859
|
{
|
|
@@ -17875,20 +17878,22 @@ var PLATFORM_INSTRUCTIONS = [
|
|
|
17875
17878
|
},
|
|
17876
17879
|
{
|
|
17877
17880
|
id: "places-not-squares",
|
|
17878
|
-
revision:
|
|
17881
|
+
revision: 3,
|
|
17879
17882
|
instruction: {
|
|
17880
17883
|
name: "Places, not squares",
|
|
17881
|
-
when: "
|
|
17884
|
+
when: "situational",
|
|
17885
|
+
trigger: "When someone moves within the scene, or a fight is on",
|
|
17882
17886
|
section: "Narration",
|
|
17883
17887
|
body: `Never write grid squares or coordinates in the story ("moves to G5", "the goblin at I14"): they are for the current state and your tool calls, and the map shows where everyone stands. Say where someone goes by what is there and how far: "walks over to the well", "ducks behind the overturned cart", "closes to within a sword's reach of the bandit".`
|
|
17884
17888
|
}
|
|
17885
17889
|
},
|
|
17886
17890
|
{
|
|
17887
17891
|
id: "travel-times",
|
|
17888
|
-
revision:
|
|
17892
|
+
revision: 2,
|
|
17889
17893
|
instruction: {
|
|
17890
17894
|
name: "Journeys, not timetables",
|
|
17891
|
-
when: "
|
|
17895
|
+
when: "situational",
|
|
17896
|
+
trigger: "When the party travels somewhere, or the player asks how far or how long a way is",
|
|
17892
17897
|
section: "Narration",
|
|
17893
17898
|
body: `research() gives exact distances and travel times ("about 2.8 km north, ~35 min on foot"). They keep the world consistent and belong in your tool calls (how much time passes), not recited in the story: "You walk 35 minutes to the butcher's shop" reads like a timetable. Tell the way as the character lives it: "a short walk across the market square", "the sun is well up by the time you reach the butcher's", "half a day's ride along the coast road". Give a figure only when it matters to the moment (a deadline, a chase, a choice between routes) or the player asks, and then say it the way a person would: "a good half hour", "a couple of miles".`
|
|
17894
17899
|
}
|
|
@@ -17932,12 +17937,12 @@ var PLATFORM_INSTRUCTIONS = [
|
|
|
17932
17937
|
},
|
|
17933
17938
|
{
|
|
17934
17939
|
id: "objectives-tracker",
|
|
17935
|
-
revision:
|
|
17940
|
+
revision: 4,
|
|
17936
17941
|
instruction: {
|
|
17937
17942
|
name: "Objectives",
|
|
17938
17943
|
when: "always",
|
|
17939
17944
|
section: "Rules",
|
|
17940
|
-
body: `The party's objectives are one list for the whole game: what they are working toward beyond this scene. Add one when the story gives the party one (an order, a job or request taken on, a goal of their own that outlasts the scene), worded as the party knows it then, short, one
|
|
17945
|
+
body: `The party's objectives are one list for the whole game: what they are working toward beyond this scene. Add one when the story gives the party one (an order, a job or request taken on, a goal of their own that outlasts the scene), worded as the party knows it then, short, one step to an objective: "Objectives": "Add: Find who opened the lid". A job with several steps they already know (go somewhere, learn something, report back) is one objective per step, in order, never one line joined with "and" or "then", so each is marked done when it happens: "Objectives": "Add: Get into the harbourmaster's office tonight", "Objectives": "Add: Find who signed the berth order", "Objectives": "Add: Tell Captain Vey whose name is on it". Mark one done or failed when the story settles it: "Objectives": "Find who opened the lid: done". Steps they do not know yet are never written ahead of the story: they come as objectives of their own when the story gives them. One that runs over time (keep watch, log every contact) is done only when its end comes.`,
|
|
17941
17946
|
tracker: {
|
|
17942
17947
|
key: "objectives",
|
|
17943
17948
|
start: {
|
|
@@ -17951,7 +17956,7 @@ var PLATFORM_INSTRUCTIONS = [
|
|
|
17951
17956
|
question: "In `earlier`, `player_turn` or `narration`, has the party been given an objective, something they are now working toward beyond this scene, that {tracker} does not hold under any wording?",
|
|
17952
17957
|
yes: 'Yes: an order or an assignment handed to them (they act on it or do not refuse it), a request or a job they take on, or a goal they set themselves that will outlast this scene ("Find what woke the Sleeper", "Get the ledger to the Abbot", "Learn who signed the panel"), and nothing in {tracker} says it.',
|
|
17953
17958
|
no: "No: {tracker} already holds it, however worded; or it is only floated, refused or still being weighed; or it is only what this scene is for, the next move (go to the archives, ask the clerk, cross the yard), or something done in the same moment.",
|
|
17954
|
-
remind: `the story gave the party an objective the current state's Objectives list does not hold (add it as the party knows it now, short, one
|
|
17959
|
+
remind: `the story gave the party an objective the current state's Objectives list does not hold (add it as the party knows it now, short, one step to an objective: "Objectives": "Add: <the objective>")`
|
|
17955
17960
|
}
|
|
17956
17961
|
}
|
|
17957
17962
|
}
|
|
@@ -18071,6 +18076,97 @@ var PLATFORM_INSTRUCTIONS = [
|
|
|
18071
18076
|
section: "World",
|
|
18072
18077
|
body: "When research() finds nothing concrete, decide it yourself: how people speak and react from who their files say they are; a small detail a game master decides (a price, a name, what a shop sells) you decide and say, with an update() when it is a value a file keeps (a shop's prices, an innkeeper's name). Only lore, rules and history stay vague. When a place or person the player names has no file, take the nearest real one and play there. When research() says no file is called a name, nobody by that name is in this world: no character speaks of them as someone they know. Take the near match it offers when the player plainly means it, else let the story show nobody knows the name."
|
|
18073
18078
|
}
|
|
18079
|
+
},
|
|
18080
|
+
{
|
|
18081
|
+
id: "travel-procedure",
|
|
18082
|
+
revision: 1,
|
|
18083
|
+
instruction: {
|
|
18084
|
+
name: "Travel",
|
|
18085
|
+
when: "procedure",
|
|
18086
|
+
trigger: "When the party goes to another place, sets out on a journey, or boards or leaves a ride (a ship, a caravan)",
|
|
18087
|
+
pruning: "after-use",
|
|
18088
|
+
section: "World",
|
|
18089
|
+
body: `Going to another place is a "scene" change naming everyone who goes and where, in words that find the place without your narration. Anyone not named stays behind.
|
|
18090
|
+
- The place exactly as the current state or research() names it: "Move Character A and Character B to Place A", never "to the inn" or "back home".
|
|
18091
|
+
- A part of a place by the part and the place ("… to the north gate of Place A", "… to the cellar of Place A"), and the way in when the story gives one ("… into Place A through the kitchen door").
|
|
18092
|
+
- When the story stops at one of the places inside a region or a city, name that place. Somewhere the world has no place for (a camp, a clearing, a stretch of road) is said by what is there and where it lies from a place the current state or research() names ("… to a sheltered hollow a mile north of Place A", "… to the alley behind Place A"), never only the region or the city, and the current state makes it.
|
|
18093
|
+
|
|
18094
|
+
A place's own map is ground for a scene whatever its size, a district's or a city's too: the party stands and moves on it, and stepping out of a place inside it (out of a tavern into its district) is a move onto that map beside the place they left, never a new place. Only a place with no map of its own, shown on a city's or a region's map, is not ground for a scene: when the story stops somewhere in one (a camp, a clearing, a stretch of road, a gate, a shop), that spot is a place of its own, and the current state makes it and draws its map when you move the party there.
|
|
18095
|
+
|
|
18096
|
+
A journey the story plays out before they get there starts as "Character A and Character B set out for Place A by the river road; it takes about an hour" and ends as "Move Character A and Character B to Place A", and nothing in between. Setting out, the update() result tells the same.
|
|
18097
|
+
|
|
18098
|
+
A place the current state says the party rides (its "ride": a ship, a caravan, an airship) carries them with the current state aboard it: "Ship A sails for Place A with Character A and Character B aboard; it takes about two days" sets out, "Ship A reaches Place A" gets there, and leaving it is a move off it: "Move Character A and Character B off Ship A onto the docks of Place A". Going below deck or to the bow is a move within it, never a voyage.
|
|
18099
|
+
|
|
18100
|
+
Before you narrate the way somewhere or the arrival, or what lies further on, ask research() what the maps say ("What is Place A, and how far is it from here?", "What lies 15 minutes east of here?", "What is further along the path?"): it gives the way and how long it takes, the places inside and around, and where the party comes in. A travel time the files give wins over the map's. What the maps say is what the party knows of the land, never something they read: no one in the story looks at a map unless they hold one. Tell the way as the character lives it ("a short walk across the market square", "half a day's ride along the coast road"), not as research()'s figures.
|
|
18101
|
+
|
|
18102
|
+
Someone who goes off without the party is their own change, "Leaves the scene"; where they went belongs in whatHappened, never in the instruction ("Leaves for the next village").`
|
|
18103
|
+
}
|
|
18104
|
+
},
|
|
18105
|
+
{
|
|
18106
|
+
id: "fight-procedure",
|
|
18107
|
+
revision: 1,
|
|
18108
|
+
instruction: {
|
|
18109
|
+
name: "Running a fight",
|
|
18110
|
+
when: "procedure",
|
|
18111
|
+
trigger: "When a fight starts, and on its turns while it runs",
|
|
18112
|
+
pruning: "on-compaction",
|
|
18113
|
+
section: "Rules",
|
|
18114
|
+
body: `A fight runs on its turn order, a tracker in the current state. Start it as the fight starts, as the turn order's line under "Trackers this game keeps" says ("scene": "Start a turn order, Combat at Place A: Character A 18, Lookout 1 15, Character B 12"). An initiative roll (one whose reason says initiative) puts whoever rolled it in the turn order at its total, so the order needs no change for it. As each turn ends, record whose it is now ("Combat": "Lookout 1's turn") and each new round ("Combat": "New round") in the update() with what the turn did.
|
|
18115
|
+
|
|
18116
|
+
Sides. When the fight starts, give each NPC in it a side if theirs is not set: the current state marks everyone as the player's character or an NPC with a side. A side is always the one they are now, ally, neutral or enemy ("Character C": "Is now an enemy", "Character D": "Is now an ally"), never only what they stopped being ("Is no longer an enemy"). Hostile arrivals say so ("Add three hostile extras, Bandit 1 to Bandit 3 …").
|
|
18117
|
+
|
|
18118
|
+
Turns. Every NPC's turn is yours to play, an ally's too (a companion travelling with the party): their move, their roll, what it did, then on to the next. Stop for the player only on the player's character's turn. Tell each turn a moment at a time: its words, its roll, then the update() of what it did, before the next turn's words.
|
|
18119
|
+
|
|
18120
|
+
Reach. On a battle map with a grid, when the turn order moves to someone, or someone in it moves, the update() result gives how far everyone is from them and who is within reach ("From Lookout 1 (J9, speed 30 ft): Character A 15 ft south … Within reach (5 ft): nobody"). A melee attack needs its target within reach, so close the distance first, as a move.
|
|
18121
|
+
|
|
18122
|
+
Stats on the map. Tokens move, hits and heals float over them and the downed are marked, in the order your update() changes give them. You choose which stats show under every token, for this scene: a bar (hit points, stress), a number (armour class), pips (spell slots, uses) or tags (conditions). Turn one on with a "scene" change when it matters, usually as the fight starts ("Show hit points on the map as a bar, hidden for enemies"); change it ("Show HP from hp and maxHp", "Show enemies' HP too"); turn it off when it no longer matters ("Stop showing HP on the map"). The current state's "shown_on_tokens" says what is shown. An extra shows the stat from its detail by that label ("HP": "6/10"), so give extras in the fight that detail. Anyone without the stat shows nothing.
|
|
18123
|
+
|
|
18124
|
+
Going down, in one word, once it has happened: "Falls unconscious", "Is dead", "Surrenders", "Gets back up"; never "falls unconscious if …": work it out from the numbers first. The downed stay in the current state; only someone who is gone (fled, carried off) leaves it.
|
|
18125
|
+
|
|
18126
|
+
Table talk. Talk at the table as a game master running a fight would, as much as the moment needs: hand the turn over ("Character B's up.", "Character A, it's your turn. What do you do?"), say how a roll went and what it did ("Seventeen: that hits, for seven damage."), give the logistics a choice turns on (who is in reach, how far, who is flanked, what a move would provoke), and say how hurt someone looks ("he's barely standing") rather than their hit points left. That is table talk; your own bookkeeping stays unsaid. Never write grid squares in the story ("the goblin at I14"): say where someone is by what is there and how far ("behind the overturned cart").`
|
|
18127
|
+
}
|
|
18128
|
+
},
|
|
18129
|
+
{
|
|
18130
|
+
id: "trackers-procedure",
|
|
18131
|
+
revision: 1,
|
|
18132
|
+
instruction: {
|
|
18133
|
+
name: "Trackers",
|
|
18134
|
+
when: "procedure",
|
|
18135
|
+
trigger: "When you start, set up or end a tracker (a turn order, a clock, a plan's steps), or need one's value as it stands now",
|
|
18136
|
+
pruning: "after-use",
|
|
18137
|
+
section: "Rules",
|
|
18138
|
+
body: `A tracker keeps what the scene is counting that is no value in a file: a fight's turn order, a clock that fills toward trouble, the steps of a plan, a record for each suspect or companion, and the like. What belongs to someone for good (hit points, gold, an item, a lasting stat) stays in their file, never in a tracker. Where and how a tracker shows on screen is decided for you.
|
|
18139
|
+
|
|
18140
|
+
Starting one. Start one when the story begins counting something the player should follow (a fight starts: its turn order; a threat that grows with each noise or minute: a clock), with a "scene" change that names it and says what it counts in plain words, and the current state builds it: "Start a turn order, Fight at the mill: Character A 18, Lookout 1 15, Character B 12", "Start a hidden 6-step clock, The watch arrives. At 6: crossbowmen at the gate", "Start a tracker, The heist plan: case the vault, bribe the night guard, crack the safe" (a plan the party made, every step known), "Start a tracker for the whole game, Suspects: a record for each of Character B and Lookout 1, suspicion 0 of 5 and their alibi". A tracker the game keeps needs no start: a change with its name as the subject starts it the first time, as its line under "Trackers this game keeps" says.
|
|
18141
|
+
|
|
18142
|
+
How long it lasts. A turn order, a clock or a count ends when the party leaves the place unless you say "for the whole game"; a list of steps (a plan, a ritual) lasts the game unless you say it ends here.
|
|
18143
|
+
|
|
18144
|
+
Stats. A tracker can show any stat from the sheets of the people it names (HP, Mana, Stamina) and where each stands, read live and never copied in: "Start a turn order, Fight at the mill: Character A 18, Lookout 1 15, showing HP, Mana and where each stands", "Fight at the mill": "Show HP, Mana and Stamina", "Suspects": "Show each suspect's HP too". That is the tracker's own; the stats under the tokens are the map's.
|
|
18145
|
+
|
|
18146
|
+
Hidden. One marked hidden is yours alone, for pressure the party cannot see yet; reveal it when they would know.
|
|
18147
|
+
|
|
18148
|
+
Changing it. Keep it in step with the story in the same update() as the rest of what happened, with its name as the subject: "Fight at the mill": "Lookout 1's turn" (whose turn it is now), "Fight at the mill": "New round", "The watch arrives": "+1", "The watch arrives": "Fill it" (its trouble came before it filled), "The heist plan": "Bribe the night guard: done", "Suspects": "Character B: Suspicion +1", "The watch arrives": "At 4: the gate bell rings" (what reaching a value brings). A step the story completes is done; the hours or days that pass move a countdown of them, once: recording that time again later in the reply moves nothing; a clock whose trouble the story brings is filled, never left short of it.
|
|
18149
|
+
|
|
18150
|
+
As it stands now. The <current-state> block that opens your context is the current state as it stood then, and a tracker's changes since are in the update() results: when you need one as it stands now (before you narrate from its value or change it by an amount that depends on it, or once the story has moved it several times), ask research() for it by its name, "Tracker: The watch arrives", in the same research() as the turn's other questions.
|
|
18151
|
+
|
|
18152
|
+
Ending it. End it when it stops mattering (the fight is over, the clock filled and its trouble came): "Fight at the mill": "End".`
|
|
18153
|
+
}
|
|
18154
|
+
},
|
|
18155
|
+
{
|
|
18156
|
+
id: "hidden-things-procedure",
|
|
18157
|
+
revision: 1,
|
|
18158
|
+
instruction: {
|
|
18159
|
+
name: "Hidden things",
|
|
18160
|
+
when: "procedure",
|
|
18161
|
+
trigger: "When someone or something is hidden from the party (an ambush, a stalker, a disguise), or is revealed",
|
|
18162
|
+
pruning: "after-use",
|
|
18163
|
+
section: "Rules",
|
|
18164
|
+
body: `What the party has not seen yet is still in the current state. When it matters (the party hears, smells or spots a sign of it, or it lies in wait), add it as what it is, marked hidden, where it is (on a grid, its square), with what the party perceives of it: "Add a hostile Black Bear at J9, hidden; the party hears only slow, heavy breathing", "Add Character B at the gate, disguised as a hooded pilgrim". A sound, a smell or tracks are never a token.
|
|
18165
|
+
|
|
18166
|
+
When the party sees it for what it is, say so ("Black Bear 1": "Is revealed"); an extra that turns out to be one of a kind becomes one ("scene": "The slow, heavy breathing is revealed as a Black Bear"). A note only you keep: ("Black Bear 1": "GM note: asleep; wakes at any loud noise").
|
|
18167
|
+
|
|
18168
|
+
The current state shows you who everyone really is and the player's screen only what the party knows: narrate only what the party perceives ("a rustle beneath the thicket"), never the name or kind of what is hidden, until it is revealed.`
|
|
18169
|
+
}
|
|
18074
18170
|
}
|
|
18075
18171
|
];
|
|
18076
18172
|
var RESPONSE_LENGTH = "Response length";
|
|
@@ -18236,9 +18332,6 @@ function sameJson(a, b) {
|
|
|
18236
18332
|
return JSON.stringify(sortedKeys(a)) === JSON.stringify(sortedKeys(b));
|
|
18237
18333
|
}
|
|
18238
18334
|
|
|
18239
|
-
// ../shared/src/game-time.ts
|
|
18240
|
-
var TIME_TRACKER_KEY = "time";
|
|
18241
|
-
|
|
18242
18335
|
// ../shared/src/trackers.ts
|
|
18243
18336
|
var TRACKER_TONES = ["good", "bad"];
|
|
18244
18337
|
var TRACKER_ITEM_STATES = ["open", "done", "failed"];
|
|
@@ -18564,6 +18657,8 @@ function trackerDefinitionProblem(definition) {
|
|
|
18564
18657
|
var GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH = 120;
|
|
18565
18658
|
var GAME_OPTION_INSTRUCTION_BODY_MAX_LENGTH = 2000;
|
|
18566
18659
|
var GAME_OPTION_SITUATIONAL_BODY_MAX_LENGTH = 1000;
|
|
18660
|
+
var GAME_OPTION_PROCEDURE_BODY_MAX_LENGTH = 1e4;
|
|
18661
|
+
var GAME_OPTION_STORED_BODY_MAX_LENGTH = 12000;
|
|
18567
18662
|
var GAME_OPTION_LABEL_MAX_LENGTH = 60;
|
|
18568
18663
|
var GAME_OPTION_DESCRIPTION_MAX_LENGTH = 200;
|
|
18569
18664
|
var GAME_OPTION_KEY_MAX_LENGTH = 60;
|
|
@@ -18573,16 +18668,26 @@ var GAME_OPTION_MAX_CHOICES = 8;
|
|
|
18573
18668
|
var GAME_OPTIONS_MAX_INSTRUCTIONS = 200;
|
|
18574
18669
|
var GAME_OPTIONS_MAX_ALWAYS_LENGTH = 1e4;
|
|
18575
18670
|
var GAME_OPTIONS_MAX_SITUATIONAL = 100;
|
|
18576
|
-
var
|
|
18671
|
+
var GAME_OPTIONS_MAX_PROCEDURES = 25;
|
|
18672
|
+
var GAME_OPTION_INSTRUCTION_WHEN = [
|
|
18673
|
+
"always",
|
|
18674
|
+
"situational",
|
|
18675
|
+
"procedure"
|
|
18676
|
+
];
|
|
18677
|
+
var GAME_OPTION_PROCEDURE_PRUNING = [
|
|
18678
|
+
"after-use",
|
|
18679
|
+
"on-compaction"
|
|
18680
|
+
];
|
|
18577
18681
|
var GAME_OPTION_KINDS = ["one", "several", "toggle"];
|
|
18578
18682
|
var keySchema = string2().trim().min(1).max(GAME_OPTION_KEY_MAX_LENGTH).regex(/^[a-z0-9][a-z0-9-]*$/, "Keys are lowercase letters, digits and -");
|
|
18579
18683
|
var GAME_OPTION_SECTION_MAX_LENGTH = 40;
|
|
18580
18684
|
var GAME_OPTIONS_MAX_SECTIONS = 30;
|
|
18581
18685
|
var gameOptionInstructionSchema = object({
|
|
18582
18686
|
name: string2().trim().max(GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH).optional(),
|
|
18583
|
-
body: string2().trim().min(1).max(
|
|
18687
|
+
body: string2().trim().min(1).max(GAME_OPTION_STORED_BODY_MAX_LENGTH),
|
|
18584
18688
|
when: _enum(GAME_OPTION_INSTRUCTION_WHEN),
|
|
18585
18689
|
trigger: string2().trim().max(GAME_OPTION_INSTRUCTION_NAME_MAX_LENGTH).optional(),
|
|
18690
|
+
pruning: _enum(GAME_OPTION_PROCEDURE_PRUNING).optional(),
|
|
18586
18691
|
section: string2().trim().max(GAME_OPTION_SECTION_MAX_LENGTH).optional(),
|
|
18587
18692
|
tracker: trackerDefinitionSchema.optional(),
|
|
18588
18693
|
platform: string2().max(GAME_OPTION_KEY_MAX_LENGTH).optional()
|
|
@@ -18655,7 +18760,28 @@ function sectionsOf(options) {
|
|
|
18655
18760
|
].map((name) => name.trim()).filter(Boolean);
|
|
18656
18761
|
return [...new Set(names)];
|
|
18657
18762
|
}
|
|
18658
|
-
function
|
|
18763
|
+
function instructionBodyMax(instruction) {
|
|
18764
|
+
if (instruction.when === "procedure") {
|
|
18765
|
+
return GAME_OPTION_PROCEDURE_BODY_MAX_LENGTH;
|
|
18766
|
+
}
|
|
18767
|
+
return instruction.when === "situational" && !instruction.tracker ? GAME_OPTION_SITUATIONAL_BODY_MAX_LENGTH : GAME_OPTION_INSTRUCTION_BODY_MAX_LENGTH;
|
|
18768
|
+
}
|
|
18769
|
+
function bodyLengthProblem(instruction) {
|
|
18770
|
+
const max = instructionBodyMax(instruction);
|
|
18771
|
+
const has = instruction.body.length;
|
|
18772
|
+
if (has <= max) {
|
|
18773
|
+
return null;
|
|
18774
|
+
}
|
|
18775
|
+
if (instruction.when === "procedure") {
|
|
18776
|
+
return `it is a procedure, so its text is at most ${max} characters (it has ${has}). Trim it, or split it into procedures for its parts.`;
|
|
18777
|
+
}
|
|
18778
|
+
if (instruction.when === "situational" && !instruction.tracker) {
|
|
18779
|
+
return `it is situational, so its text is at most ${max} characters (it has ${has}). Trim it, or make it a procedure: a procedure holds up to ${GAME_OPTION_PROCEDURE_BODY_MAX_LENGTH} characters, read whole when its moment comes.`;
|
|
18780
|
+
}
|
|
18781
|
+
const kind = instruction.tracker ? "a tracker's rules" : "always on";
|
|
18782
|
+
return `it is ${kind}, so its text is at most ${max} characters (it has ${has}). Trim it, or make the part for one moment situational or a procedure.`;
|
|
18783
|
+
}
|
|
18784
|
+
function instructionProblem(instruction, base, bodyLengths) {
|
|
18659
18785
|
if (instruction.tracker) {
|
|
18660
18786
|
const problem = trackerDefinitionProblem(instruction.tracker);
|
|
18661
18787
|
if (problem) {
|
|
@@ -18668,25 +18794,45 @@ function instructionProblem(instruction, base) {
|
|
|
18668
18794
|
if (base && instruction.when === "always" && !instruction.name?.trim()) {
|
|
18669
18795
|
return "an always instruction needs a title.";
|
|
18670
18796
|
}
|
|
18671
|
-
if (instruction.when === "
|
|
18672
|
-
|
|
18797
|
+
if (instruction.when === "procedure") {
|
|
18798
|
+
if (instruction.tracker) {
|
|
18799
|
+
return `"${instruction.name?.trim() || instructionTrigger(instruction)}" is a procedure, which can't define a tracker: make the tracker its own instruction.`;
|
|
18800
|
+
}
|
|
18801
|
+
if (!instruction.trigger?.trim()) {
|
|
18802
|
+
return "a procedure needs to say when to use it.";
|
|
18803
|
+
}
|
|
18804
|
+
if (base && !instruction.name?.trim()) {
|
|
18805
|
+
return "a procedure needs a title.";
|
|
18806
|
+
}
|
|
18807
|
+
}
|
|
18808
|
+
const long = bodyLengthProblem(instruction);
|
|
18809
|
+
if (bodyLengths && long) {
|
|
18810
|
+
return `"${instruction.name?.trim() || instructionTrigger(instruction)}": ${long}`;
|
|
18673
18811
|
}
|
|
18674
18812
|
return null;
|
|
18675
18813
|
}
|
|
18676
|
-
function gameOptionsProblem(options) {
|
|
18677
|
-
const baseProblem = options.instructions.map((instruction) => instructionProblem(instruction, true)).find(Boolean);
|
|
18814
|
+
function gameOptionsProblem(options, { bodyLengths = true } = {}) {
|
|
18815
|
+
const baseProblem = options.instructions.map((instruction) => instructionProblem(instruction, true, bodyLengths)).find(Boolean);
|
|
18678
18816
|
if (baseProblem) {
|
|
18679
18817
|
return `In the base instructions, ${baseProblem}`;
|
|
18680
18818
|
}
|
|
18681
18819
|
for (const option of options.options) {
|
|
18682
18820
|
for (const choice of option.choices) {
|
|
18683
|
-
const problem = choice.instructions.map((instruction) => instructionProblem(instruction, false)).find(Boolean);
|
|
18821
|
+
const problem = choice.instructions.map((instruction) => instructionProblem(instruction, false, bodyLengths)).find(Boolean);
|
|
18684
18822
|
if (problem) {
|
|
18685
18823
|
return `In "${option.label} · ${choice.label}", ${problem}`;
|
|
18686
18824
|
}
|
|
18687
18825
|
}
|
|
18688
18826
|
}
|
|
18689
|
-
return trackerKeysProblem(options) ?? gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
|
|
18827
|
+
return craftTimeProblem(options) ?? trackerKeysProblem(options) ?? gameOptionsStructureProblem(options) ?? gameOptionsLimitProblem(options);
|
|
18828
|
+
}
|
|
18829
|
+
var TIME_TRACKER_ID = "time-tracker";
|
|
18830
|
+
function craftTimeProblem(options) {
|
|
18831
|
+
const time = options.instructions.find((instruction) => instruction.platform === TIME_TRACKER_ID);
|
|
18832
|
+
if (!time || time.tracker?.key === TIME_TRACKER_KEY) {
|
|
18833
|
+
return null;
|
|
18834
|
+
}
|
|
18835
|
+
return `Craft's Time tracker ("${time.name}") must keep its tracker with the key "${TIME_TRACKER_KEY}": code moves the clock by that key. Reword its name, rules and trigger, or turn it off.`;
|
|
18690
18836
|
}
|
|
18691
18837
|
function trackerKeysProblem(options) {
|
|
18692
18838
|
const lists = [
|
|
@@ -18754,14 +18900,67 @@ function gameOptionsLimitProblem(options) {
|
|
|
18754
18900
|
if (situational > GAME_OPTIONS_MAX_SITUATIONAL) {
|
|
18755
18901
|
return `One game can have up to ${situational} situational instructions; the limit is ${GAME_OPTIONS_MAX_SITUATIONAL}.`;
|
|
18756
18902
|
}
|
|
18903
|
+
const procedures = worstCaseProcedureCount(options);
|
|
18904
|
+
if (procedures > GAME_OPTIONS_MAX_PROCEDURES) {
|
|
18905
|
+
return `One game can have up to ${procedures} procedures; the limit is ${GAME_OPTIONS_MAX_PROCEDURES}, Craft's included. Merge some, or make the short ones situational.`;
|
|
18906
|
+
}
|
|
18757
18907
|
return null;
|
|
18758
18908
|
}
|
|
18759
18909
|
var gameOptionsSchema = storedGameOptionsShape.superRefine((options, ctx) => {
|
|
18760
|
-
const
|
|
18910
|
+
for (const { path, instruction } of storedInstructions(options)) {
|
|
18911
|
+
const long = bodyLengthProblem(instruction);
|
|
18912
|
+
if (long) {
|
|
18913
|
+
ctx.addIssue({
|
|
18914
|
+
code: "custom",
|
|
18915
|
+
path: [...path, "body"],
|
|
18916
|
+
message: long
|
|
18917
|
+
});
|
|
18918
|
+
}
|
|
18919
|
+
}
|
|
18920
|
+
const problem = gameOptionsProblem(withPlatform(options), {
|
|
18921
|
+
bodyLengths: false
|
|
18922
|
+
});
|
|
18761
18923
|
if (problem) {
|
|
18762
18924
|
ctx.addIssue({ code: "custom", message: problem });
|
|
18763
18925
|
}
|
|
18764
18926
|
});
|
|
18927
|
+
function storedInstructions(options) {
|
|
18928
|
+
const found = [];
|
|
18929
|
+
const choicesOf = (path, choices) => {
|
|
18930
|
+
choices.forEach((choice, c) => {
|
|
18931
|
+
choice.instructions.forEach((instruction, i) => {
|
|
18932
|
+
found.push({
|
|
18933
|
+
path: [...path, "choices", c, "instructions", i],
|
|
18934
|
+
instruction
|
|
18935
|
+
});
|
|
18936
|
+
});
|
|
18937
|
+
});
|
|
18938
|
+
};
|
|
18939
|
+
options.instructions.forEach((instruction, i) => {
|
|
18940
|
+
if ("body" in instruction) {
|
|
18941
|
+
found.push({ path: ["instructions", i], instruction });
|
|
18942
|
+
}
|
|
18943
|
+
});
|
|
18944
|
+
options.options.forEach((option, o) => {
|
|
18945
|
+
if ("choices" in option) {
|
|
18946
|
+
choicesOf(["options", o], option.choices);
|
|
18947
|
+
}
|
|
18948
|
+
});
|
|
18949
|
+
for (const [id, override] of Object.entries(options.platform?.instructions ?? {})) {
|
|
18950
|
+
if ("body" in override) {
|
|
18951
|
+
found.push({
|
|
18952
|
+
path: ["platform", "instructions", id],
|
|
18953
|
+
instruction: override
|
|
18954
|
+
});
|
|
18955
|
+
}
|
|
18956
|
+
}
|
|
18957
|
+
for (const [key, override] of Object.entries(options.platform?.options ?? {})) {
|
|
18958
|
+
if ("choices" in override) {
|
|
18959
|
+
choicesOf(["platform", "options", key], override.choices);
|
|
18960
|
+
}
|
|
18961
|
+
}
|
|
18962
|
+
return found;
|
|
18963
|
+
}
|
|
18765
18964
|
var storedGameOptionsSchema = storedGameOptionsShape.superRefine((options, ctx) => {
|
|
18766
18965
|
const problem = gameOptionsStructureProblem(withPlatform(options));
|
|
18767
18966
|
if (problem) {
|
|
@@ -19058,6 +19257,12 @@ function worstCaseAlwaysLength(options) {
|
|
|
19058
19257
|
function worstCaseSituationalCount(options) {
|
|
19059
19258
|
return worstCase(options, situationalCount);
|
|
19060
19259
|
}
|
|
19260
|
+
function procedureCount(instructions, _place) {
|
|
19261
|
+
return instructions.filter((instruction) => instruction.when === "procedure").length;
|
|
19262
|
+
}
|
|
19263
|
+
function worstCaseProcedureCount(options) {
|
|
19264
|
+
return worstCase(options, procedureCount);
|
|
19265
|
+
}
|
|
19061
19266
|
|
|
19062
19267
|
// ../shared/src/gm-tool-settings.ts
|
|
19063
19268
|
var gmToolSettingsInputSchema = object({
|
|
@@ -37411,7 +37616,7 @@ async function refreshWorkspaceScriptTypes({
|
|
|
37411
37616
|
// package.json
|
|
37412
37617
|
var package_default = {
|
|
37413
37618
|
name: "@craftrpgs/cli",
|
|
37414
|
-
version: "0.2.
|
|
37619
|
+
version: "0.2.1",
|
|
37415
37620
|
description: "Sync Craft projects with a local folder — clone, edit with any coding agent, push back.",
|
|
37416
37621
|
license: "UNLICENSED",
|
|
37417
37622
|
homepage: "https://craftrpgs.com",
|
|
@@ -46122,10 +46327,53 @@ var sceneHeadingProps = object({
|
|
|
46122
46327
|
});
|
|
46123
46328
|
|
|
46124
46329
|
// ../shared/src/gm-narration/clean-narration-text.ts
|
|
46125
|
-
var
|
|
46330
|
+
var POSTPROCESSING_MARKER = "STARTING POSTPROCESSING";
|
|
46331
|
+
var WHITESPACE_CHAR_RE = /\s/;
|
|
46332
|
+
var DIVIDER_LINE_END_RE = /[ \t]*\n/y;
|
|
46126
46333
|
var TRANSLATED_POSTPROCESSING_MARKER_RE = /(?:\n?---[ \t]*\r?\n)?^[ \t]*(?:`{3,}[ \t]*)?\\?(?:-{3,}|\*{2})[ \t]*(?:正在)?(?:开始后处理|開始後處理)[ \t]*(?:-{3,}|\*{2})?[ \t]*\r?$/gm;
|
|
46334
|
+
function isWhitespaceAt(text, index) {
|
|
46335
|
+
const char = text[index];
|
|
46336
|
+
return char !== undefined && WHITESPACE_CHAR_RE.test(char);
|
|
46337
|
+
}
|
|
46338
|
+
function markerPrefixStart(text, floor, markerAt) {
|
|
46339
|
+
let start = markerAt;
|
|
46340
|
+
while (start > floor && isWhitespaceAt(text, start - 1)) {
|
|
46341
|
+
start--;
|
|
46342
|
+
}
|
|
46343
|
+
while (start > floor && text[start - 1] === "-") {
|
|
46344
|
+
start--;
|
|
46345
|
+
}
|
|
46346
|
+
while (start > floor && text[start - 1] === "*") {
|
|
46347
|
+
start--;
|
|
46348
|
+
}
|
|
46349
|
+
while (start > floor && isWhitespaceAt(text, start - 1)) {
|
|
46350
|
+
start--;
|
|
46351
|
+
}
|
|
46352
|
+
if (start - 3 >= floor && text.startsWith("---", start - 3)) {
|
|
46353
|
+
DIVIDER_LINE_END_RE.lastIndex = start;
|
|
46354
|
+
if (DIVIDER_LINE_END_RE.test(text)) {
|
|
46355
|
+
start -= 3;
|
|
46356
|
+
if (start > floor && text[start - 1] === `
|
|
46357
|
+
`) {
|
|
46358
|
+
start--;
|
|
46359
|
+
}
|
|
46360
|
+
}
|
|
46361
|
+
}
|
|
46362
|
+
return start;
|
|
46363
|
+
}
|
|
46127
46364
|
function stripGmPostprocessingMarkers(text) {
|
|
46128
|
-
|
|
46365
|
+
let stripped = "";
|
|
46366
|
+
let keepFrom = 0;
|
|
46367
|
+
let markerAt = text.indexOf(POSTPROCESSING_MARKER);
|
|
46368
|
+
while (markerAt >= 0) {
|
|
46369
|
+
stripped += text.slice(keepFrom, markerPrefixStart(text, keepFrom, markerAt));
|
|
46370
|
+
const lineEnd = text.indexOf(`
|
|
46371
|
+
`, markerAt + POSTPROCESSING_MARKER.length);
|
|
46372
|
+
keepFrom = lineEnd < 0 ? text.length : lineEnd;
|
|
46373
|
+
markerAt = text.indexOf(POSTPROCESSING_MARKER, keepFrom);
|
|
46374
|
+
}
|
|
46375
|
+
stripped += text.slice(keepFrom);
|
|
46376
|
+
return stripped.replace(TRANSLATED_POSTPROCESSING_MARKER_RE, "");
|
|
46129
46377
|
}
|
|
46130
46378
|
|
|
46131
46379
|
// ../shared/src/tts/text.ts
|
package/package.json
CHANGED