@adatechnology/game-stories 0.0.0-stage → 0.2.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.
@@ -0,0 +1,4477 @@
1
+ "use strict";
2
+ var GameStories = (() => {
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/index.ts
22
+ var index_exports = {};
23
+ __export(index_exports, {
24
+ FINAL_STATES: () => FINAL_STATES,
25
+ LIFE_DEFAULTS: () => LIFE_DEFAULTS,
26
+ LIFE_TEXT: () => LIFE_TEXT,
27
+ SHEET_DEFAULTS: () => SHEET_DEFAULTS,
28
+ STORIES_AREA_ID: () => STORIES_AREA_ID,
29
+ STORIES_EXAMPLE: () => STORIES_EXAMPLE,
30
+ applyChanges: () => applyChanges,
31
+ castStories: () => castStories,
32
+ createChronicle: () => createChronicle,
33
+ createLifeWorld: () => createLifeWorld,
34
+ createSheet: () => createSheet,
35
+ createStoriesTestModule: () => createStoriesTestModule,
36
+ createStoryWorld: () => createStoryWorld,
37
+ drawSheet: () => drawSheet,
38
+ fillText: () => fillText,
39
+ fillWorld: () => fillWorld,
40
+ holds: () => holds,
41
+ lifeDay: () => lifeDay,
42
+ lifeNumbersOf: () => lifeNumbersOf,
43
+ lifeTextOf: () => lifeTextOf,
44
+ runBeat: () => runBeat,
45
+ setState: () => setState,
46
+ shortName: () => shortName,
47
+ simulateStories: () => simulateStories
48
+ });
49
+
50
+ // src/data/life.json
51
+ var life_default = {
52
+ sources: {
53
+ file: "specs/001-migracao-entregas-dos-times/sources/sobreviventes__os-vivos-do-vale.js (site/pages/sobreviventes/os-vivos-do-vale.html)",
54
+ states: "LIVE (line 368) for living; life() alive (line 628) for active",
55
+ affinity: "life() aff, line 634: fam 80, ali 60, odio -60, seg 10, medo -20; unknown pairs 10 + ((empathy a + empathy b)/2 - 50)/5",
56
+ clocks: "life() step 1, lines 643-656: heal in 3 + floor(R\xB78) days, a bite turns in 2, the missing come back in 2 + floor(R\xB75) days with chance 0.6; birth with a midwife 3% mother / 5% baby, without 15% / 20%",
57
+ risk: "life() step 2, lines 660-668: base risk by place (REG, default 0.006), exposure by person (EXPO, default 0.3; newcomers 0.05 under 12, else 0.35), \xD7(1 - defence), \xD71.5 under 2 days of food, \xD72 hurt; draw < 0.5 hurt, < 0.8 bitten, < 0.95 dead, else missing",
58
+ bonds: "life() step 3, lines 671-692: from 16 years; drift ((empathy a + empathy c)/2 - 50)/120 + (food < 5 ? -(greed a + greed c)/250 : 0.1) + (R - 0.5)\xB73; quiet 2 days after news; couple from 18, at most 15 years apart, affinity 65, chance 3%, then 90; pregnancy with 8 days of food, mother up to 42, 2%, due in 45 days, no story beat in 20 days; adoption 1%; rivalry at -50; fight at -70 with courage 60, 20%, affinity -40, < 0.6 hurt (heal 3 + floor(R\xB75)), < 0.7 dead, else the less loyal leaves",
59
+ betrayal: "life() lines 694-697: loyalty < 35, greed > 60, food < 5, 3%; takes 4 days of food; affinity -40 with everyone of the base",
60
+ food: "life() step 4, line 701: each day -0.11 (by option per base), between 0 and 60",
61
+ caravan: "lines 703-708: a base under 5 days of food, a host base over 12, someone there with affinity 55, 12%; 5 days of food, affinity +10; 8% the carrier is hurt (heal in 4)",
62
+ raid: "lines 709-718: 3% a day, not within 10 days of the last, targets with 3 days of food, the weakest defence first with \xB10.15 of noise; up to 5 days of food; a hero with courage 70 and affinity 60 arrives with chance 50%; else 25% dead; heal in 5; affinity +20",
63
+ arrivals: "step 5, lines 721-737: every 5 days, chance 0.5 (0.25 after day 60) of 1 + (30% 1 more); population cap 104 (CAP), 8 per base (ROOM); first names unique up to 30; age 18 + floor(R\xB742); personality 20 + R\xB770 (greed 10 + R\xB770); the bond from 16 years; affinity 75; 20% a partner (age max(18, age + floor(R\xB79) - 4)), 35% pregnant if up to 42, due in 10 + floor(R\xB730)",
64
+ fill: "simulate() O.fill, lines 746-749: up to 8 per base, age 18 + floor(R\xB740), personality 20 + R\xB770",
65
+ infection: "the bitten band and the turn are the apocalypse layer: off by default (normal world first), the bitten band then hurts"
66
+ },
67
+ unknownAge: 8,
68
+ livingStates: ["ok", "hurt", "party", "hostile", "moved"],
69
+ activeStates: ["ok", "hurt", "moved", "hostile"],
70
+ affinity: { family: 80, trust: 60, hatred: -60, secret: 10, fear: -20 },
71
+ rivalKind: "hatred",
72
+ unknownAffinity: { base: 10, divisor: 5 },
73
+ clocks: { healBase: 3, healSpread: 8, turnDays: 2, backBase: 2, backSpread: 5, backChance: 0.6 },
74
+ birth: { motherWithMidwife: 0.03, motherAlone: 0.15, babyWithMidwife: 0.05, babyAlone: 0.2, girl: 0.5 },
75
+ risk: {
76
+ defaultPlace: 6e-3,
77
+ defaultExposure: 0.3,
78
+ newcomerChild: 0.05,
79
+ newcomerAdult: 0.35,
80
+ childAge: 12,
81
+ hungryBelow: 2,
82
+ hungry: 1.5,
83
+ hurt: 2,
84
+ bands: { hurt: 0.5, bitten: 0.8, dead: 0.95 }
85
+ },
86
+ bonds: {
87
+ minAge: 16,
88
+ quietDays: 2,
89
+ driftEmpathy: 120,
90
+ lowFood: 5,
91
+ driftGreed: 250,
92
+ driftFed: 0.1,
93
+ driftNoise: 3,
94
+ coupleAge: 18,
95
+ coupleGap: 15,
96
+ coupleAffinity: 65,
97
+ coupleChance: 0.03,
98
+ coupledAffinity: 90,
99
+ pregnancyFood: 8,
100
+ pregnancyAge: 42,
101
+ pregnancyChance: 0.02,
102
+ pregnancyDays: 45,
103
+ pregnancyStoryWindow: 20,
104
+ adoptionChance: 0.01,
105
+ rivalryAt: -50,
106
+ fightAt: -70,
107
+ fightCourage: 60,
108
+ fightChance: 0.2,
109
+ afterFight: -40,
110
+ fightHurt: 0.6,
111
+ fightDead: 0.7,
112
+ fightHealSpread: 5
113
+ },
114
+ betrayal: { loyaltyBelow: 35, greedAbove: 60, foodBelow: 5, chance: 0.03, takes: 4, affinity: -40 },
115
+ food: { drift: -0.11, max: 60 },
116
+ caravan: { below: 5, hostAbove: 12, affinity: 55, chance: 0.12, carries: 5, bond: 10, hurtChance: 0.08, healDays: 4 },
117
+ raid: { chance: 0.03, cooldown: 10, targetFood: 3, noise: 0.3, loot: 5, heroCourage: 70, heroAffinity: 60, heroChance: 0.5, deadChance: 0.25, healDays: 5, heroBond: 20 },
118
+ arrivals: {
119
+ every: 5,
120
+ chance: 0.5,
121
+ lateChance: 0.25,
122
+ lateAfter: 60,
123
+ secondChance: 0.3,
124
+ cap: 104,
125
+ room: 8,
126
+ uniqueNames: 30,
127
+ ageBase: 18,
128
+ ageSpread: 42,
129
+ bondAge: 16,
130
+ bondAffinity: 75,
131
+ pairChance: 0.2,
132
+ pairAgeSpread: 9,
133
+ pairAgeShift: 4,
134
+ pregnantChance: 0.35,
135
+ pregnantDueBase: 10,
136
+ pregnantDueSpread: 30
137
+ },
138
+ fill: { perBase: 8, ageBase: 18, ageSpread: 40 },
139
+ personality: { base: 20, spread: 70, greedBase: 10 },
140
+ infection: false
141
+ };
142
+
143
+ // src/data/life-text.json
144
+ var life_text_default = {
145
+ sources: {
146
+ file: "specs/001-migracao-entregas-dos-times/sources/sobreviventes__os-vivos-do-vale.js (site/pages/sobreviventes/os-vivos-do-vale.html), life(), lines 619-739",
147
+ "pt-BR": "the team's texts, verbatim; {name}, {a}, {b}... are the people's short names (NICK, short), {o} the gender ending (o(id))",
148
+ en: "translation of the pt-BR texts"
149
+ },
150
+ "pt-BR": {
151
+ titles: {
152
+ turn: "Reanima",
153
+ back: "Volta",
154
+ lost: "Sem volta",
155
+ birth: "Nascimento",
156
+ risk: "Risco",
157
+ bond: "La\xE7o",
158
+ pregnancy: "Gravidez",
159
+ adoption: "Ado\xE7\xE3o",
160
+ rivalry: "Rixa",
161
+ fight: "Briga",
162
+ betrayal: "Trai\xE7\xE3o",
163
+ caravan: "Entre bases",
164
+ raid: "Ataque",
165
+ arrival: "Chegada"
166
+ },
167
+ notes: {
168
+ healed: "curado",
169
+ undead_street: "zumbi nomeado na rua",
170
+ undead_near: "zumbi nomeado perto de {place}",
171
+ came_back: "voltou depois de sumir",
172
+ came_back_short: "voltou",
173
+ never_back: "nunca voltou",
174
+ newborn: "rec\xE9m-nascido",
175
+ childbirth: "no parto",
176
+ hurt_out: "ferido fora da base",
177
+ bitten_out: "mordido fora da base",
178
+ hides_bite: "esconde a mordida",
179
+ dead_out: "morto fora da base",
180
+ did_not_return: "n\xE3o voltou",
181
+ missing_out: "sumiu numa sa\xEDda",
182
+ missing_short: "sumiu",
183
+ fight_with: "briga com {other}",
184
+ fight: "briga",
185
+ dead_fight: "morto numa briga com {other}",
186
+ left_after_fight: "foi embora depois da briga",
187
+ betrayed: "roubou {from} e foi para {to}",
188
+ betrayed_short: "traiu a base",
189
+ hurt_caravan: "ferido na caravana",
190
+ caravan: "caravana",
191
+ hurt_raid: "ferido no ataque",
192
+ dead_raid: "morto no ataque de {from}",
193
+ raid: "ataque",
194
+ arrived: "chegou{origin}",
195
+ pair_of: "par de {first}"
196
+ },
197
+ lines: {
198
+ loses: " {partner} perde {name}.",
199
+ turn: "{name} reanima e passa a vagar pela rotina antiga.",
200
+ back: "{name} aparece no port\xE3o, magro e sem as botas, depois de dias sumido.",
201
+ lost: "Acham as coisas de {name} numa vala da estrada.{loss}",
202
+ birth: "{name} d\xE1 \xE0 luz em {place}{help}.",
203
+ birth_help: " com ajuda de quem sabe",
204
+ birth_alone: ", sem ningu\xE9m que saiba fazer parto",
205
+ baby_dies: " O beb\xEA n\xE3o respira. Enterram ao lado da base.",
206
+ baby_born: " Nasce {baby}.",
207
+ mother_dies: "{loss} {name} n\xE3o resiste ao parto.",
208
+ risk_hurt: "{name} volta ferid{o} de uma sa\xEDda perto de {place}.",
209
+ risk_bitten: "{name} volta com uma mordida no antebra\xE7o e n\xE3o conta a ningu\xE9m.",
210
+ risk_dead: "{name} n\xE3o volta de uma sa\xEDda perto de {place}.{loss}",
211
+ risk_missing: "{name} some numa sa\xEDda perto de {place}.",
212
+ couple: "{a} e {b} {line}.",
213
+ pregnancy: "{a} e {b} v\xE3o ter um beb\xEA. Nasce em uns {days} dias de jogo.",
214
+ adoption: "{a} e {b} adotam {child}.",
215
+ rivalry: "{a} e {b} {line}. Viraram rivais.",
216
+ fight_hurt: "A rixa explode: {winner} quebra o nariz de {loser} no p\xE1tio.",
217
+ fight_dead: "A rixa termina com faca: {loser} morre e {winner} carrega a morte para o resto da vida.{loss}",
218
+ fight_leaves: "{name} junta as coisas e vai para {to} depois da briga com {other}.",
219
+ betrayal: "{name} some de madrugada com a comida da despensa e o r\xE1dio de {from}. Dias depois \xE9 visto em {to}.",
220
+ caravan: "Caravana: {carrier} atravessa a cidade com comida de {from} para {to}, onde mora {host}.",
221
+ caravan_hurt: " Na volta, {carrier} \xE9 atacad{o} na avenida e chega ferid{o}.",
222
+ raid: "{who} atacam {target} de madrugada e levam {loot} dias de comida.",
223
+ raid_hero: " {hero}, de {heroBase}, ouve os tiros e chega a tempo: {victim} sai s\xF3 ferid{o}.",
224
+ raid_dead: "{loss} {victim} morre na porta.",
225
+ raid_grazed: " {victim} leva um tiro de rasp\xE3o.",
226
+ arrival: "{first}, {relation} de {bond}, chega{origin}{note}.",
227
+ arrival_gone: " Vinha procurando {bond} e descobre que {bond} {fate}. Fica em {base}.",
228
+ arrival_party: " Pergunta por {bond} e descobre que est\xE1 com o jogador. Fica em {base} esperando.",
229
+ arrival_found: " Encontra {bond} em {base} e fica.",
230
+ arrival_elsewhere: " Descobre que {bond} est\xE1 em {where} e prefere ficar em {base}.",
231
+ arrival_pair_pregnant: " Vem com {pair}, e {mother} est\xE1 gr\xE1vida.",
232
+ arrival_pair: " Vem com {pair}."
233
+ },
234
+ pools: {
235
+ couple: ["come\xE7am a dividir o turno de vigia, e depois o colch\xE3o", "se beijam no telhado durante a r\xE1dio das 20h", "assumem o namoro na frente da base inteira"],
236
+ rivalry: ["brigam pela \xFAltima lata de sardinha", "discutem aos gritos sobre quem dorme perto da porta", "se acusam de roubar ra\xE7\xE3o"]
237
+ },
238
+ fates: { dead: "morreu", undead: "virou um dos mortos", left: "saiu do vale no \xF4nibus", missing: "sumiu", bitten: "foi mordido", other: "n\xE3o est\xE1 mais aqui" },
239
+ places: { where_died: "onde morreu", road: "a estrada", open_field: "campo aberto", nowhere: "lugar nenhum" },
240
+ gender: { female: "a", male: "o" },
241
+ honorifics: ["Dona", "Seu", "Dr.", "Tenente", "Sargento", "Soldado", "Pastora", "Cabo", "Capit\xE3o", "Major"]
242
+ },
243
+ en: {
244
+ titles: {
245
+ turn: "Turns",
246
+ back: "Back",
247
+ lost: "Never back",
248
+ birth: "Birth",
249
+ risk: "Risk",
250
+ bond: "Bond",
251
+ pregnancy: "Pregnancy",
252
+ adoption: "Adoption",
253
+ rivalry: "Rivalry",
254
+ fight: "Fight",
255
+ betrayal: "Betrayal",
256
+ caravan: "Between bases",
257
+ raid: "Raid",
258
+ arrival: "Arrival"
259
+ },
260
+ notes: {
261
+ healed: "healed",
262
+ undead_street: "named undead in the street",
263
+ undead_near: "named undead near {place}",
264
+ came_back: "came back after going missing",
265
+ came_back_short: "came back",
266
+ never_back: "never came back",
267
+ newborn: "newborn",
268
+ childbirth: "in childbirth",
269
+ hurt_out: "hurt outside the base",
270
+ bitten_out: "bitten outside the base",
271
+ hides_bite: "hides the bite",
272
+ dead_out: "dead outside the base",
273
+ did_not_return: "did not return",
274
+ missing_out: "went missing on a run",
275
+ missing_short: "missing",
276
+ fight_with: "fight with {other}",
277
+ fight: "fight",
278
+ dead_fight: "killed in a fight with {other}",
279
+ left_after_fight: "left after the fight",
280
+ betrayed: "robbed {from} and went to {to}",
281
+ betrayed_short: "betrayed the base",
282
+ hurt_caravan: "hurt on the caravan",
283
+ caravan: "caravan",
284
+ hurt_raid: "hurt in the raid",
285
+ dead_raid: "killed in the raid of {from}",
286
+ raid: "raid",
287
+ arrived: "arrived{origin}",
288
+ pair_of: "partner of {first}"
289
+ },
290
+ lines: {
291
+ loses: " {partner} loses {name}.",
292
+ turn: "{name} comes back and wanders the old routine.",
293
+ back: "{name} shows up at the gate, thin and without boots, after days missing.",
294
+ lost: "They find {name}'s things in a ditch by the road.{loss}",
295
+ birth: "{name} gives birth at {place}{help}.",
296
+ birth_help: " with help from someone who knows how",
297
+ birth_alone: ", with no one who knows how to deliver a baby",
298
+ baby_dies: " The baby does not breathe. They bury it beside the base.",
299
+ baby_born: " {baby} is born.",
300
+ mother_dies: "{loss} {name} does not survive the birth.",
301
+ risk_hurt: "{name} comes back hurt from a run near {place}.",
302
+ risk_bitten: "{name} comes back with a bite on the forearm and tells no one.",
303
+ risk_dead: "{name} does not come back from a run near {place}.{loss}",
304
+ risk_missing: "{name} goes missing on a run near {place}.",
305
+ couple: "{a} and {b} {line}.",
306
+ pregnancy: "{a} and {b} are going to have a baby. Due in about {days} days.",
307
+ adoption: "{a} and {b} adopt {child}.",
308
+ rivalry: "{a} and {b} {line}. They are rivals now.",
309
+ fight_hurt: "The rivalry blows up: {winner} breaks {loser}'s nose in the yard.",
310
+ fight_dead: "The rivalry ends with a knife: {loser} dies and {winner} carries that death for the rest of their life.{loss}",
311
+ fight_leaves: "{name} packs up and goes to {to} after the fight with {other}.",
312
+ betrayal: "{name} vanishes before dawn with the pantry's food and the radio of {from}. Days later they are seen at {to}.",
313
+ caravan: "Caravan: {carrier} crosses town with food from {from} to {to}, where {host} lives.",
314
+ caravan_hurt: " On the way back, {carrier} is attacked on the avenue and arrives hurt.",
315
+ raid: "{who} raid {target} before dawn and take {loot} days of food.",
316
+ raid_hero: " {hero}, from {heroBase}, hears the shots and arrives in time: {victim} is only hurt.",
317
+ raid_dead: "{loss} {victim} dies at the door.",
318
+ raid_grazed: " {victim} is grazed by a bullet.",
319
+ arrival: "{first}, {relation} of {bond}, arrives{origin}{note}.",
320
+ arrival_gone: " They were looking for {bond} and learn that {bond} {fate}. They stay at {base}.",
321
+ arrival_party: " They ask for {bond} and learn that they are with the player. They stay at {base}, waiting.",
322
+ arrival_found: " They find {bond} at {base} and stay.",
323
+ arrival_elsewhere: " They learn that {bond} is at {where} and would rather stay at {base}.",
324
+ arrival_pair_pregnant: " They come with {pair}, and {mother} is pregnant.",
325
+ arrival_pair: " They come with {pair}."
326
+ },
327
+ pools: {
328
+ couple: ["start sharing the watch, and then the mattress", "kiss on the roof during the 8 pm radio", "go public in front of the whole base"],
329
+ rivalry: ["fight over the last tin of sardines", "shout about who sleeps by the door", "accuse each other of stealing rations"]
330
+ },
331
+ fates: { dead: "died", undead: "became one of the dead", left: "left on the bus", missing: "went missing", bitten: "was bitten", other: "is not here any more" },
332
+ places: { where_died: "where they died", road: "the road", open_field: "the open field", nowhere: "nowhere" },
333
+ gender: { female: "", male: "" },
334
+ honorifics: ["Mrs.", "Mr.", "Dr.", "Lieutenant", "Sergeant", "Private", "Pastor", "Corporal", "Captain", "Major"]
335
+ }
336
+ };
337
+
338
+ // src/data/sheet.json
339
+ var sheet_default = {
340
+ sources: {
341
+ page: 'site/pages/zumbis-personagens/personagens.html, "Mem\xF3ria e opini\xE3o" and "Modelo de dados"',
342
+ memories: '"Cada NPC guarda at\xE9 30 lembran\xE7as com peso e data"; memorias: [{ evento, alvoId, peso, dia }] // m\xE1x 30',
343
+ opinion: '"A opini\xE3o sobre cada jogador vai de \u2212100 a +100 e decai devagar para o neutro. Em +60 o NPC aceita entrar no grupo; abaixo de \u221260 pode roubar, delatar a base a outro grupo ou atacar \xE0 noite."',
344
+ decayPerDay: `not in the document ("decai devagar"): 1 point a day is the kit's reversible default, listed as a question`,
345
+ memoryDrop: "not in the document: past 30, the lightest memory goes (the oldest first on a tie), listed as a question"
346
+ },
347
+ memories: { max: 30 },
348
+ opinion: { min: -100, max: 100, joins: 60, hostile: -60, decayPerDay: 1 }
349
+ };
350
+
351
+ // src/casting.ts
352
+ var fits = (person, role) => {
353
+ const needs = role.needs;
354
+ if (!needs) return true;
355
+ if (needs.sex && Boolean(person.female) !== (needs.sex === "f")) return false;
356
+ if (needs.age && (person.age === void 0 || person.age < needs.age[0] || person.age > needs.age[1])) return false;
357
+ if (needs.profession && (!person.profession || !needs.profession.includes(person.profession))) return false;
358
+ if (needs.base && person.base !== needs.base) return false;
359
+ if (needs.tags && !needs.tags.every((tag) => person.tags?.includes(tag))) return false;
360
+ return true;
361
+ };
362
+ function castStories(options) {
363
+ const R = options.random;
364
+ const pick = (list) => list[Math.floor(R() * list.length)];
365
+ const people = [...options.people ?? []];
366
+ const byId = new Map(people.map((person) => [person.id, person]));
367
+ const roles = {};
368
+ const made = /* @__PURE__ */ new Map();
369
+ const taken = /* @__PURE__ */ new Set();
370
+ const Q = life_default.personality;
371
+ const A = life_default.arrivals;
372
+ for (const story of options.stories) {
373
+ for (const role of story.roles) {
374
+ if (roles[role.id]) continue;
375
+ if (role.person) {
376
+ if (!byId.has(role.person)) throw new Error(`game-stories: role "${role.id}" of "${story.id}" names "${role.person}", who is not among the people`);
377
+ roles[role.id] = role.person;
378
+ taken.add(role.person);
379
+ continue;
380
+ }
381
+ const candidates = role.create ? [] : people.filter((person2) => !taken.has(person2.id) && !person2.extra && fits(person2, role));
382
+ if (candidates.length > 0) {
383
+ const chosen = pick(candidates);
384
+ roles[role.id] = chosen.id;
385
+ taken.add(chosen.id);
386
+ continue;
387
+ }
388
+ const bond = role.bond ? byId.get(roles[role.bond.role] ?? "") : void 0;
389
+ const female = role.needs?.sex ? role.needs.sex === "f" : R() < 0.5;
390
+ const first = pick(female ? options.names.female : options.names.male);
391
+ const surname = role.bond?.sameSurname && bond ? bond.name.split(" ").filter((x) => !/[“”]/.test(x)).slice(-1)[0] : pick(options.names.surnames);
392
+ const range = role.needs?.age;
393
+ const age = range ? range[0] + Math.floor(R() * (range[1] - range[0] + 1)) : A.ageBase + Math.floor(R() * A.ageSpread);
394
+ const base = role.needs?.base ?? (role.bond?.sameBase !== false && bond ? bond.base : options.defaultBase ?? people[0]?.base ?? "nomad");
395
+ const personality = [Q.base + R() * Q.spread, Q.base + R() * Q.spread, Q.base + R() * Q.spread, Q.greedBase + R() * Q.spread, Q.base + R() * Q.spread].map(Math.round);
396
+ const person = {
397
+ id: role.id,
398
+ name: `${first} ${surname}`,
399
+ age,
400
+ female,
401
+ base,
402
+ personality,
403
+ role: role.id,
404
+ generated: true,
405
+ ...role.needs?.profession?.length ? { profession: pick(role.needs.profession) } : {},
406
+ ...role.tags ? { tags: role.tags } : {},
407
+ ...bond ? { bond: bond.id } : {},
408
+ ...role.bond?.relation ? { relation: role.bond.relation } : {}
409
+ };
410
+ if (byId.has(person.id)) throw new Error(`game-stories: role "${role.id}" would make a person with the id of someone given`);
411
+ people.push(person);
412
+ byId.set(person.id, person);
413
+ roles[role.id] = person.id;
414
+ taken.add(person.id);
415
+ made.set(person.id, role.id);
416
+ }
417
+ }
418
+ const firstRole = /* @__PURE__ */ new Map();
419
+ for (const [role, id] of Object.entries(roles)) if (!firstRole.has(id)) firstRole.set(id, role);
420
+ return { roles, people, roleOf: (id) => made.get(id) ?? firstRole.get(id) };
421
+ }
422
+
423
+ // src/chronicle.ts
424
+ function createChronicle() {
425
+ const entries = [];
426
+ const listeners = /* @__PURE__ */ new Set();
427
+ return {
428
+ entries,
429
+ add(entry) {
430
+ entries.push(entry);
431
+ for (const listener of listeners) listener(entry);
432
+ },
433
+ onDay: (day) => entries.filter((entry) => entry.day === day),
434
+ about: (id) => entries.filter((entry) => entry.people.includes(id) || entry.changes.some((change) => change.id === id)),
435
+ until: (day) => entries.filter((entry) => entry.day <= day),
436
+ on(listener) {
437
+ listeners.add(listener);
438
+ return () => void listeners.delete(listener);
439
+ }
440
+ };
441
+ }
442
+
443
+ // src/conditions.ts
444
+ var stateOf = (context, role) => context.states[context.cast(role)]?.state;
445
+ function holds(condition, context) {
446
+ if ("all" in condition) return condition.all.every((c) => holds(c, context));
447
+ if ("any" in condition) return condition.any.some((c) => holds(c, context));
448
+ if ("not" in condition) return !holds(condition.not, context);
449
+ if ("action" in condition) return Boolean(context.actions[condition.action]);
450
+ if ("flag" in condition) return Boolean(context.flags[condition.flag]);
451
+ if ("state" in condition) {
452
+ const state = stateOf(context, condition.state);
453
+ return state !== void 0 && (typeof condition.is === "string" ? state === condition.is : condition.is.includes(state));
454
+ }
455
+ if ("living" in condition) {
456
+ const state = stateOf(context, condition.living);
457
+ return state !== void 0 && context.living.includes(state);
458
+ }
459
+ if ("base" in condition) return context.states[context.cast(condition.base)]?.base === condition.is;
460
+ if ("value" in condition) return Boolean(context.values?.[condition.value]);
461
+ if ("self" in condition) return context.self === context.cast(condition.self);
462
+ if ("changed" in condition) return (context.changes ?? []).some((change) => change.id === context.cast(condition.changed));
463
+ if ("remembers" in condition) return context.remembers?.(context.cast(condition.remembers), condition.entry) ?? false;
464
+ return false;
465
+ }
466
+
467
+ // src/story/apply.ts
468
+ var FINAL_STATES = ["dead", "left"];
469
+ function applyChanges(states, changes, playerBase) {
470
+ for (const { id, state, note, base } of changes ?? []) {
471
+ const current = states[id];
472
+ if (!current) continue;
473
+ if (FINAL_STATES.includes(current.state)) continue;
474
+ if (current.state === "undead" && state !== "dead") continue;
475
+ states[id] = { state, note, base: base || (state === "party" ? playerBase : current.base) };
476
+ }
477
+ }
478
+ function setState(states, id, state, note, base) {
479
+ const current = states[id];
480
+ if (FINAL_STATES.includes(current.state) || current.state === "undead" && state !== "dead") return;
481
+ states[id] = { state, note, base: base || current.base };
482
+ }
483
+
484
+ // src/text.ts
485
+ var fillText = (template, values) => template.replace(/\{(\w+)\}/g, (_, key) => String(values[key] ?? ""));
486
+ var escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
487
+ function shortName(name, honorifics) {
488
+ const quoted = name.match(/“(.*?)”/);
489
+ if (quoted) return quoted[1];
490
+ const prefix = honorifics.length > 0 ? new RegExp(`^(${honorifics.map(escape).join("|")}) `) : null;
491
+ return (prefix ? name.replace(prefix, "") : name).split(" ")[0];
492
+ }
493
+
494
+ // src/life/life.ts
495
+ var toChanges = (rows) => rows.map(([id, state, note, base]) => ({ id, state, note, ...base ? { base } : {} }));
496
+ function createLifeWorld(options, numbers) {
497
+ const W = {
498
+ raid: {},
499
+ food: {},
500
+ defence: {},
501
+ affinity: {},
502
+ partner: {},
503
+ rival: {},
504
+ adopted: {},
505
+ timers: {},
506
+ made: {},
507
+ count: 0,
508
+ lastNews: {},
509
+ stats: { births: 0, arrivals: 0, deaths: 0, couples: 0, fights: 0, betrayals: 0, caravans: 0, raids: 0, pregnancies: 0 },
510
+ usedNames: /* @__PURE__ */ new Set()
511
+ };
512
+ for (const [id, group] of Object.entries(options.groups)) {
513
+ W.food[id] = group.food;
514
+ W.defence[id] = group.defence;
515
+ }
516
+ W.defence[options.playerBase ?? "player"] = 1;
517
+ for (const [a, b, kind] of options.relations ?? []) if (kind === numbers.rivalKind) W.rival[a < b ? `${a}|${b}` : `${b}|${a}`] = 1;
518
+ for (const [a, b] of options.couples ?? []) {
519
+ W.partner[a] = b;
520
+ W.partner[b] = a;
521
+ }
522
+ return W;
523
+ }
524
+ function lifeDay(d, W, host) {
525
+ const { states: S, flags: F, random: R, numbers: N, text: T, options: O } = host;
526
+ const nomad = O.nomadBase ?? "nomad";
527
+ const playerBase = O.playerBase ?? "player";
528
+ const closed = /* @__PURE__ */ new Set([nomad, ...O.closedBases ?? []]);
529
+ const groups = O.groups;
530
+ const P = (id) => host.people[id] ?? W.made[id];
531
+ const age = (id) => P(id).age ?? N.unknownAge;
532
+ const fem = (id) => Boolean(P(id).female);
533
+ const nm = (id) => P(id).nick ?? shortName(P(id).name, T.honorifics);
534
+ const o = (id) => fem(id) ? T.gender.female : T.gender.male;
535
+ const pers = (id) => {
536
+ const p = P(id).personality;
537
+ return p && p.some((v) => v) ? p : [50, 50, 50, 50, 50];
538
+ };
539
+ const groupName = (b, fallback) => b !== void 0 && groups[b] ? groups[b].name : fallback;
540
+ const conditionContext = () => ({ states: S, flags: F, actions: host.actions, cast: (id) => id, living: N.livingStates });
541
+ const anchorDays = (id, from, to) => host.beatDays(id).some((day) => day > from && day <= to);
542
+ const anchor = (id) => anchorDays(id, d, d + 7) || Boolean(W.timers[id]?.due) || (O.anchors ?? []).some((a) => a.id === id && d < a.beforeDay) || Boolean(W.made[id]?.baby);
543
+ const alive = (id) => N.activeStates.includes(S[id].state);
544
+ const ids = Object.keys(S).filter((id) => alive(id) && age(id) > 0);
545
+ const set = (id, state, note, base) => setState(S, id, state, note, base);
546
+ const say = (rule, text, rows, base, people) => {
547
+ host.log({ day: d, kind: "life", id: rule, title: T.titles[rule], text, changes: toChanges(rows), people, ...base ? { base } : {} });
548
+ if (base) W.lastNews[base] = d;
549
+ };
550
+ const kill = (id, note) => {
551
+ set(id, "dead", note);
552
+ W.stats.deaths++;
553
+ const p = W.partner[id];
554
+ if (p && alive(p)) return fillText(T.lines.loses, { partner: nm(p), name: nm(id) });
555
+ return "";
556
+ };
557
+ const cnt = (b) => Object.keys(S).filter((id) => alive(id) && S[id].base === b).length;
558
+ const pop = () => Object.keys(S).filter((id) => alive(id) || S[id].state === "party").length;
559
+ const key = (a, b) => a < b ? `${a}|${b}` : `${b}|${a}`;
560
+ const aff = (a, b) => {
561
+ const k = key(a, b);
562
+ if (W.affinity[k] === void 0) {
563
+ const r = (O.relations ?? []).find((rel) => rel[0] === a && rel[1] === b || rel[0] === b && rel[1] === a);
564
+ W.affinity[k] = r ? N.affinity[r[2]] : N.unknownAffinity.base + ((pers(a)[1] + pers(b)[1]) / 2 - 50) / N.unknownAffinity.divisor;
565
+ }
566
+ return W.affinity[k];
567
+ };
568
+ const addAff = (a, b, v) => {
569
+ const k = key(a, b);
570
+ W.affinity[k] = Math.max(-100, Math.min(100, aff(a, b) + v));
571
+ };
572
+ const pick = (a) => a[Math.floor(R() * a.length)];
573
+ const hostileNow = () => {
574
+ const out = {};
575
+ for (const [b, rule] of Object.entries(O.hostileBases ?? {})) out[b] = rule === true ? true : holds(rule, conditionContext());
576
+ return out;
577
+ };
578
+ const bestBase = () => {
579
+ const H2 = hostileNow();
580
+ const c = Object.keys(groups).filter((b) => !closed.has(b) && !H2[b] && cnt(b) > 0 && cnt(b) < N.arrivals.room);
581
+ c.sort((a, b) => W.food[b] * (0.5 + W.defence[b]) - W.food[a] * (0.5 + W.defence[a]));
582
+ return c.length ? c[Math.floor(R() * Math.min(3, c.length))] : nomad;
583
+ };
584
+ const newPerson = (person) => {
585
+ const id = `g${++W.count}`;
586
+ W.made[id] = { id, generated: true, ...person };
587
+ S[id] = { state: "ok", note: "", base: person.base };
588
+ return id;
589
+ };
590
+ const heal = (spread) => d + N.clocks.healBase + Math.floor(R() * spread);
591
+ for (const { flag, a, b } of O.flagCouples ?? []) {
592
+ if (F[flag] && !W.partner[a] && !W.partner[b]) {
593
+ W.partner[a] = b;
594
+ W.partner[b] = a;
595
+ }
596
+ }
597
+ for (const [id, t] of Object.entries(W.timers)) {
598
+ if (t.heal === d && S[id].state === "hurt") S[id].note = T.notes.healed;
599
+ if (t.heal === d && S[id].state === "hurt") S[id].state = "ok";
600
+ if (t.turn === d && S[id].state === "bitten") {
601
+ const b = S[id].base;
602
+ set(id, "undead", b === nomad ? T.notes.undead_street : fillText(T.notes.undead_near, { place: groupName(b, T.places.where_died) }));
603
+ say("turn", fillText(T.lines.turn, { name: nm(id) }), [[id, "undead", S[id].note]], S[id].base, [id]);
604
+ }
605
+ if (t.back === d && S[id].state === "missing") {
606
+ if (R() < N.clocks.backChance) {
607
+ set(id, "ok", T.notes.came_back);
608
+ say("back", fillText(T.lines.back, { name: nm(id) }), [[id, "ok", T.notes.came_back_short]], null, [id]);
609
+ } else {
610
+ const x = kill(id, T.notes.never_back);
611
+ say("lost", fillText(T.lines.lost, { name: nm(id), loss: x }), [[id, "dead", T.notes.never_back]], null, [id]);
612
+ }
613
+ }
614
+ if (t.due === d) {
615
+ t.due = 0;
616
+ if (!alive(id)) continue;
617
+ const b = S[id].base;
618
+ const med = (O.midwives ?? []).some((m) => S[m] && alive(m) && S[m].base === b);
619
+ const pm = med ? N.birth.motherWithMidwife : N.birth.motherAlone;
620
+ const pb = med ? N.birth.babyWithMidwife : N.birth.babyAlone;
621
+ const dad = t.father;
622
+ const sx = R() < N.birth.girl ? "f" : "m";
623
+ const bn = sx === "f" ? pick(host.names.babyFemale) : pick(host.names.babyMale);
624
+ const momDies = R() < pm && !anchor(id);
625
+ const babyDies = R() < pb;
626
+ let txt = fillText(T.lines.birth, { name: nm(id), place: groupName(b, T.places.open_field), help: med ? T.lines.birth_help : T.lines.birth_alone });
627
+ const ch = [];
628
+ const people = [id];
629
+ if (babyDies) txt += T.lines.baby_dies;
630
+ else {
631
+ const surname = P(id).name.split(" ").filter((x) => !/[“”]/.test(x)).slice(-1)[0];
632
+ const bid = newPerson({ name: `${bn} ${surname}`, age: 0, base: b, female: sx === "f", baby: true, mother: id, ...dad ? { father: dad } : {} });
633
+ W.stats.births++;
634
+ txt += fillText(T.lines.baby_born, { baby: bn });
635
+ ch.push([bid, "ok", T.notes.newborn, b]);
636
+ people.push(bid);
637
+ }
638
+ if (momDies) {
639
+ txt += fillText(T.lines.mother_dies, { loss: kill(id, T.notes.childbirth), name: nm(id) });
640
+ ch.push([id, "dead", T.notes.childbirth]);
641
+ }
642
+ say("birth", txt, ch, b, people);
643
+ }
644
+ }
645
+ for (const id of ids) {
646
+ if (S[id].state === "hurt" && !(W.timers[id] && W.timers[id].heal > d)) {
647
+ W.timers[id] = W.timers[id] || {};
648
+ W.timers[id].heal = heal(N.clocks.healSpread);
649
+ }
650
+ }
651
+ for (const id of ids) {
652
+ if (!alive(id) || anchor(id) || W.made[id] && W.made[id].baby) continue;
653
+ const b = S[id].base;
654
+ if (b === playerBase) continue;
655
+ const ex = W.made[id] ? age(id) < N.risk.childAge ? N.risk.newcomerChild : N.risk.newcomerAdult : O.exposure?.[id] ?? N.risk.defaultExposure;
656
+ let r = (O.placeRisk?.[b] ?? N.risk.defaultPlace) * ex * (1 - (W.defence[b] ?? 0)) * (W.food[b] < N.risk.hungryBelow ? N.risk.hungry : 1) * (S[id].state === "hurt" ? N.risk.hurt : 1);
657
+ for (const [flag, factor] of Object.entries(O.riskFlags ?? {})) r *= F[flag] ? factor : 1;
658
+ if (R() >= r) continue;
659
+ const x = R();
660
+ const where = groupName(b, T.places.road);
661
+ const hurtBand = N.infection ? N.risk.bands.hurt : N.risk.bands.bitten;
662
+ if (x < hurtBand) {
663
+ set(id, "hurt", T.notes.hurt_out);
664
+ W.timers[id] = { ...W.timers[id], heal: heal(N.clocks.healSpread) };
665
+ say("risk", fillText(T.lines.risk_hurt, { name: nm(id), o: o(id), place: where }), [[id, "hurt", T.notes.hurt_out]], null, [id]);
666
+ } else if (x < N.risk.bands.bitten) {
667
+ set(id, "bitten", T.notes.bitten_out);
668
+ W.timers[id] = { ...W.timers[id], turn: d + N.clocks.turnDays };
669
+ say("risk", fillText(T.lines.risk_bitten, { name: nm(id) }), [[id, "bitten", T.notes.hides_bite]], b, [id]);
670
+ } else if (x < N.risk.bands.dead) {
671
+ const t = kill(id, T.notes.dead_out);
672
+ say("risk", fillText(T.lines.risk_dead, { name: nm(id), place: where, loss: t }), [[id, "dead", T.notes.did_not_return]], b, [id]);
673
+ } else {
674
+ set(id, "missing", T.notes.missing_out);
675
+ W.timers[id] = { ...W.timers[id], back: d + N.clocks.backBase + Math.floor(R() * N.clocks.backSpread) };
676
+ say("risk", fillText(T.lines.risk_missing, { name: nm(id), place: where }), [[id, "missing", T.notes.missing_short]], null, [id]);
677
+ }
678
+ }
679
+ const B = N.bonds;
680
+ const by = {};
681
+ for (const id of Object.keys(S)) {
682
+ if (!alive(id) || age(id) < B.minAge) continue;
683
+ const b = S[id].base;
684
+ if (!b || b === playerBase || b === nomad) continue;
685
+ (by[b] = by[b] || []).push(id);
686
+ }
687
+ for (const [b, m] of Object.entries(by)) {
688
+ const quiet = W.lastNews[b] >= d - B.quietDays;
689
+ for (let i = 0; i < m.length; i++) {
690
+ for (let j = i + 1; j < m.length; j++) {
691
+ const a = m[i];
692
+ const c = m[j];
693
+ const pa = pers(a);
694
+ const pc = pers(c);
695
+ addAff(a, c, ((pa[1] + pc[1]) / 2 - 50) / B.driftEmpathy + (W.food[b] < B.lowFood ? -(pa[3] + pc[3]) / B.driftGreed : B.driftFed) + (R() - 0.5) * B.driftNoise);
696
+ const v = aff(a, c);
697
+ const k = key(a, c);
698
+ if (quiet || W.lastNews[b] >= d) continue;
699
+ if (!W.partner[a] && !W.partner[c] && age(a) >= B.coupleAge && age(c) >= B.coupleAge && Math.abs(age(a) - age(c)) <= B.coupleGap && v >= B.coupleAffinity && R() < B.coupleChance) {
700
+ W.partner[a] = c;
701
+ W.partner[c] = a;
702
+ W.affinity[k] = B.coupledAffinity;
703
+ W.stats.couples++;
704
+ say("bond", fillText(T.lines.couple, { a: nm(a), b: nm(c), line: pick(T.pools.couple) }), [], b, [a, c]);
705
+ continue;
706
+ }
707
+ if (W.partner[a] === c && W.food[b] >= B.pregnancyFood) {
708
+ const f = fem(a) && !fem(c) ? a : fem(c) && !fem(a) ? c : null;
709
+ if (f && age(f) <= B.pregnancyAge && !W.timers[f]?.due && !(anchorDays(a, d, d + B.pregnancyStoryWindow) || anchorDays(c, d, d + B.pregnancyStoryWindow)) && R() < B.pregnancyChance) {
710
+ W.timers[f] = { ...W.timers[f], due: d + B.pregnancyDays, father: f === a ? c : a };
711
+ W.stats.pregnancies++;
712
+ say("pregnancy", fillText(T.lines.pregnancy, { a: nm(a), b: nm(c), days: B.pregnancyDays }), [], b, [a, c]);
713
+ continue;
714
+ }
715
+ if (!f && !W.adopted[k] && R() < B.adoptionChance) {
716
+ const orphan = Object.keys(S).find((x) => alive(x) && S[x].base === b && (O.orphans?.[x] !== void 0 || W.made[x]?.baby && !alive(W.made[x].mother)));
717
+ if (orphan) {
718
+ W.adopted[k] = 1;
719
+ say("adoption", fillText(T.lines.adoption, { a: nm(a), b: nm(c), child: O.orphans?.[orphan] ?? nm(orphan) }), [], b, [a, c, orphan]);
720
+ continue;
721
+ }
722
+ }
723
+ }
724
+ if (!W.rival[k] && v <= B.rivalryAt) {
725
+ W.rival[k] = 1;
726
+ say("rivalry", fillText(T.lines.rivalry, { a: nm(a), b: nm(c), line: pick(T.pools.rivalry) }), [], b, [a, c]);
727
+ continue;
728
+ }
729
+ if (W.rival[k] && v <= B.fightAt && Math.max(pa[0], pc[0]) >= B.fightCourage && R() < B.fightChance) {
730
+ W.stats.fights++;
731
+ W.affinity[k] = B.afterFight;
732
+ const x = R();
733
+ const lo = pa[0] < pc[0] ? a : c;
734
+ const hi = lo === a ? c : a;
735
+ if (x < B.fightHurt && !anchor(lo)) {
736
+ set(lo, "hurt", fillText(T.notes.fight_with, { other: nm(hi) }));
737
+ W.timers[lo] = { ...W.timers[lo], heal: heal(B.fightHealSpread) };
738
+ say("fight", fillText(T.lines.fight_hurt, { winner: nm(hi), loser: nm(lo) }), [[lo, "hurt", T.notes.fight]], b, [hi, lo]);
739
+ } else if (x < B.fightDead && !anchor(lo)) {
740
+ const t = kill(lo, fillText(T.notes.dead_fight, { other: nm(hi) }));
741
+ say("fight", fillText(T.lines.fight_dead, { loser: nm(lo), winner: nm(hi), loss: t }), [[lo, "dead", T.notes.fight]], b, [hi, lo]);
742
+ } else {
743
+ const out = pa[2] < pc[2] ? a : c;
744
+ const dest = bestBase();
745
+ if (dest !== b && !anchor(out)) {
746
+ set(out, "moved", T.notes.left_after_fight, dest);
747
+ say("fight", fillText(T.lines.fight_leaves, { name: nm(out), to: groupName(dest, dest), other: nm(out === a ? c : a) }), [[out, "moved", groupName(dest, dest), dest]], b, [out, out === a ? c : a]);
748
+ }
749
+ }
750
+ }
751
+ }
752
+ }
753
+ const V2 = N.betrayal;
754
+ for (const id of m) {
755
+ const p = pers(id);
756
+ if (quiet || W.lastNews[b] >= d || !alive(id) || anchor(id)) continue;
757
+ if (p[2] < V2.loyaltyBelow && p[3] > V2.greedAbove && W.food[b] < V2.foodBelow && R() < V2.chance) {
758
+ const dest = (O.traitorDestinations ?? []).find((t) => t.base !== b && (!t.when || holds(t.when, conditionContext())))?.base ?? nomad;
759
+ W.food[b] = Math.max(0, W.food[b] - V2.takes);
760
+ W.stats.betrayals++;
761
+ set(id, host.people[id] ? "hostile" : "moved", fillText(T.notes.betrayed, { from: groupName(b, b), to: groupName(dest, dest) }), dest);
762
+ for (const other of m) if (other !== id) addAff(id, other, V2.affinity);
763
+ say("betrayal", fillText(T.lines.betrayal, { name: nm(id), from: groupName(b, b), to: groupName(dest, dest) }), [[id, S[id].state, T.notes.betrayed_short, dest]], b, [id]);
764
+ }
765
+ }
766
+ }
767
+ for (const b of Object.keys(W.food)) {
768
+ const rule = O.foodDrift?.[b];
769
+ const drift = rule === void 0 ? N.food.drift : typeof rule === "number" ? rule : F[rule.flag] ? rule.value : N.food.drift;
770
+ W.food[b] = Math.max(0, Math.min(N.food.max, W.food[b] + drift));
771
+ }
772
+ const H = hostileNow();
773
+ const C = N.caravan;
774
+ for (const [b, m] of Object.entries(by)) {
775
+ if (W.food[b] >= C.below || W.lastNews[b] >= d - B.quietDays) continue;
776
+ caravan: for (const x of m) {
777
+ for (const [c, mc] of Object.entries(by)) {
778
+ if (c === b || H[c] || W.food[c] <= C.hostAbove) continue;
779
+ const y = mc.find((member) => aff(x, member) >= C.affinity);
780
+ if (!y || R() >= C.chance) continue;
781
+ W.food[b] += C.carries;
782
+ W.food[c] -= C.carries;
783
+ addAff(x, y, C.bond);
784
+ W.stats.caravans++;
785
+ let t = fillText(T.lines.caravan, { carrier: nm(y), from: groupName(c, c), to: groupName(b, b), host: nm(x) });
786
+ const ch = [];
787
+ if (R() < C.hurtChance && !anchor(y)) {
788
+ set(y, "hurt", T.notes.hurt_caravan);
789
+ W.timers[y] = { ...W.timers[y], heal: d + C.healDays };
790
+ t += fillText(T.lines.caravan_hurt, { carrier: nm(y), o: o(y) });
791
+ ch.push([y, "hurt", T.notes.caravan]);
792
+ }
793
+ say("caravan", t, ch, b, [y, x]);
794
+ break caravan;
795
+ }
796
+ }
797
+ }
798
+ const A = N.raid;
799
+ const raids = (O.raids ?? []).map((raid) => ({ ...raid, on: holds(raid.when, conditionContext()) }));
800
+ for (const { from: src, on, who } of raids) {
801
+ if (!on || R() >= A.chance || (W.raid[src] || 0) > d - A.cooldown) continue;
802
+ const alvo = Object.keys(by).filter((b) => b !== src && !H[b] && !(O.raidImmune ?? []).includes(b) && W.food[b] >= A.targetFood).sort((a, b) => W.defence[a] - W.defence[b] + (R() - 0.5) * A.noise)[0];
803
+ if (!alvo || W.lastNews[alvo] >= d - B.quietDays) continue;
804
+ W.raid[src] = d;
805
+ const v = pick(by[alvo]);
806
+ W.stats.raids++;
807
+ const loot = Math.min(A.loot, W.food[alvo]);
808
+ W.food[alvo] -= loot;
809
+ W.food[src] = (W.food[src] || 0) + loot;
810
+ const hero = Object.entries(by).filter(([b]) => b !== alvo && !H[b]).flatMap(([, mm]) => mm).find((h) => pers(h)[0] >= A.heroCourage && by[alvo].some((other) => aff(h, other) >= A.heroAffinity));
811
+ let t = fillText(T.lines.raid, { who, target: groupName(alvo, alvo), loot: Math.round(loot) });
812
+ const ch = [];
813
+ const people = [v];
814
+ if (hero && R() < A.heroChance) {
815
+ t += fillText(T.lines.raid_hero, { hero: nm(hero), heroBase: groupName(S[hero].base, S[hero].base), victim: nm(v), o: o(v) });
816
+ set(v, "hurt", T.notes.hurt_raid);
817
+ W.timers[v] = { ...W.timers[v], heal: d + A.healDays };
818
+ addAff(hero, v, A.heroBond);
819
+ ch.push([v, "hurt", T.notes.raid]);
820
+ people.push(hero);
821
+ } else if (R() < A.deadChance && !anchor(v)) {
822
+ t += fillText(T.lines.raid_dead, { loss: kill(v, fillText(T.notes.dead_raid, { from: groupName(src, src) })), victim: nm(v) });
823
+ ch.push([v, "dead", T.notes.raid]);
824
+ } else if (!anchor(v)) {
825
+ set(v, "hurt", T.notes.hurt_raid);
826
+ W.timers[v] = { ...W.timers[v], heal: d + A.healDays };
827
+ t += fillText(T.lines.raid_grazed, { victim: nm(v) });
828
+ ch.push([v, "hurt", T.notes.raid]);
829
+ }
830
+ say("raid", t, ch, alvo, people);
831
+ }
832
+ const V = N.arrivals;
833
+ const pool = Object.values(host.people).filter((x) => !x.extra && typeof x.age === "number" && x.age >= V.bondAge);
834
+ if (O.arrivals !== false && d % V.every === 0 && pool.length > 0) {
835
+ const origins = O.arrivals?.origins?.length ? O.arrivals.origins : [""];
836
+ let n = R() < (d > V.lateAfter ? V.lateChance : V.chance) ? 1 + (R() < V.secondChance ? 1 : 0) : 0;
837
+ while (n-- > 0 && pop() < V.cap) {
838
+ const f = R() < 0.5;
839
+ let nome;
840
+ do
841
+ nome = `${pick(f ? host.names.female : host.names.male)} ${pick(host.names.surnames)}`;
842
+ while (W.usedNames.has(nome.split(" ")[0]) && W.usedNames.size < V.uniqueNames);
843
+ const first = nome.split(" ")[0];
844
+ W.usedNames.add(first);
845
+ const origem = pick(origins);
846
+ const bond = pick(pool).id;
847
+ const rel = pick(host.names.kinship[f ? "female" : "male"]);
848
+ const st = S[bond].state;
849
+ const live = alive(bond) || st === "party";
850
+ const bondBase = S[bond].base;
851
+ const b = live && bondBase !== playerBase && bondBase !== nomad && !H[bondBase] && cnt(bondBase) < V.room ? bondBase : bestBase();
852
+ const Q = N.personality;
853
+ const id = newPerson({
854
+ name: nome,
855
+ age: V.ageBase + Math.floor(R() * V.ageSpread),
856
+ base: b,
857
+ female: f,
858
+ personality: [Q.base + R() * Q.spread, Q.base + R() * Q.spread, Q.base + R() * Q.spread, Q.greedBase + R() * Q.spread, Q.base + R() * Q.spread].map(Math.round),
859
+ bond,
860
+ relation: rel
861
+ });
862
+ W.stats.arrivals++;
863
+ const way = origem ? ` ${origem}` : "";
864
+ const note = (O.arrivals ? O.arrivals.notes ?? [] : []).filter((x) => x.origin === origem && holds(x.when, conditionContext())).map((x) => x.text).join("");
865
+ let t = fillText(T.lines.arrival, { first, relation: rel, bond: nm(bond), origin: way, note });
866
+ if (!live) {
867
+ const fate = T.fates[st] ?? T.fates.other;
868
+ t += fillText(T.lines.arrival_gone, { bond: nm(bond), fate, base: groupName(b, b) });
869
+ } else {
870
+ t += st === "party" ? fillText(T.lines.arrival_party, { bond: nm(bond), base: groupName(b, b) }) : b === S[bond].base ? fillText(T.lines.arrival_found, { bond: nm(bond), base: groupName(b, b) }) : fillText(T.lines.arrival_elsewhere, { bond: nm(bond), where: groupName(S[bond].base, T.places.nowhere), base: groupName(b, b) });
871
+ W.affinity[key(id, bond)] = V.bondAffinity;
872
+ }
873
+ for (const a of O.arrivals ? O.arrivals.announcements ?? [] : []) if (holds(a.when, conditionContext())) t += a.text;
874
+ const arrived = fillText(T.notes.arrived, { origin: way });
875
+ const ch = [[id, "ok", arrived, b]];
876
+ const people = [id, bond];
877
+ if (R() < V.pairChance && pop() < V.cap) {
878
+ const pf = !f;
879
+ let n2;
880
+ do
881
+ n2 = pick(pf ? host.names.female : host.names.male);
882
+ while (W.usedNames.has(n2) && W.usedNames.size < V.uniqueNames);
883
+ W.usedNames.add(n2);
884
+ const surname = nome.split(" ").slice(-1)[0];
885
+ const id2 = newPerson({
886
+ name: `${n2} ${surname}`,
887
+ age: Math.max(V.ageBase, W.made[id].age + Math.floor(R() * V.pairAgeSpread) - V.pairAgeShift),
888
+ base: b,
889
+ female: pf,
890
+ personality: [1, 2, 3, 4, 5].map(() => Math.round(Q.base + R() * Q.spread)),
891
+ bond,
892
+ relation: fillText(T.notes.pair_of, { first })
893
+ });
894
+ W.stats.arrivals++;
895
+ W.partner[id] = id2;
896
+ W.partner[id2] = id;
897
+ ch.push([id2, "ok", arrived, b]);
898
+ people.push(id2);
899
+ const mae = pf ? id2 : id;
900
+ if (R() < V.pregnantChance && W.made[mae].age <= B.pregnancyAge) {
901
+ W.timers[mae] = { due: d + V.pregnantDueBase + Math.floor(R() * V.pregnantDueSpread), father: mae === id ? id2 : id };
902
+ W.stats.pregnancies++;
903
+ t += fillText(T.lines.arrival_pair_pregnant, { pair: n2, mother: nm(mae) });
904
+ } else t += fillText(T.lines.arrival_pair, { pair: n2 });
905
+ }
906
+ say("arrival", t, ch, null, people);
907
+ }
908
+ }
909
+ }
910
+ function fillWorld(W, host, target) {
911
+ const { states: S, random: R, numbers: N, options: O } = host;
912
+ const closed = /* @__PURE__ */ new Set([O.nomadBase ?? "nomad", ...O.closedBases ?? []]);
913
+ const bases = Object.keys(O.groups).filter((b) => !closed.has(b));
914
+ const living = () => Object.keys(S).filter((id) => N.livingStates.includes(S[id].state));
915
+ let i = 0;
916
+ let idle = 0;
917
+ while (living().length < target && bases.length > 0) {
918
+ const b = bases[i++ % bases.length];
919
+ if (living().filter((id2) => S[id2].base === b).length >= N.fill.perBase) {
920
+ if (++idle >= bases.length) break;
921
+ continue;
922
+ }
923
+ idle = 0;
924
+ const id = `g${++W.count}`;
925
+ const f = R() < 0.5;
926
+ const first = f ? host.names.female : host.names.male;
927
+ W.made[id] = {
928
+ id,
929
+ generated: true,
930
+ name: `${first[W.count % first.length]} ${host.names.surnames[W.count % host.names.surnames.length]}`,
931
+ age: N.fill.ageBase + Math.floor(R() * N.fill.ageSpread),
932
+ base: b,
933
+ female: f,
934
+ personality: [1, 2, 3, 4, 5].map(() => Math.round(N.personality.base + R() * N.personality.spread))
935
+ };
936
+ S[id] = { state: "ok", note: "", base: b };
937
+ }
938
+ }
939
+
940
+ // src/story/beat.ts
941
+ var resolveChanges = (items, context, out) => {
942
+ for (const item of items) {
943
+ const here = { ...context, changes: out };
944
+ if ("when" in item) {
945
+ if (holds(item.when, here)) resolveChanges(item.changes, context, out);
946
+ continue;
947
+ }
948
+ if ("each" in item) {
949
+ const { roles, cast, where, except } = item.each;
950
+ const ids = roles ? roles.map((role) => context.cast(role)) : cast ? context.castList : [];
951
+ const skip = new Set((except ?? []).map((role) => context.cast(role)));
952
+ for (const id2 of ids) {
953
+ if (skip.has(id2) || !context.states[id2]) continue;
954
+ if (where && !holds(where, { ...here, self: id2, cast: (role) => role === "self" ? id2 : context.cast(role) })) continue;
955
+ out.push({ id: id2, state: item.change.state, note: item.change.note, ...item.change.base ? { base: item.change.base } : {} });
956
+ }
957
+ continue;
958
+ }
959
+ const id = context.cast(item.role);
960
+ const base = item.base === void 0 ? void 0 : typeof item.base === "string" ? item.base : context.states[context.cast(item.base.baseOf)]?.base;
961
+ out.push({ id, state: item.state, note: item.note, ...base ? { base } : {} });
962
+ }
963
+ };
964
+ var renderText = (parts, context, changes, join) => {
965
+ const piece = (part) => {
966
+ if (typeof part === "string") return part;
967
+ if ("when" in part) {
968
+ const chosen = holds(part.when, { ...context, changes }) ? part.text : part.else;
969
+ if (chosen === void 0) return "";
970
+ return typeof chosen === "string" ? chosen : renderText(chosen, context, changes, void 0);
971
+ }
972
+ if ("value" in part) return context.values?.[part.value] ?? "";
973
+ if ("count" in part) return String(changes.length);
974
+ return context.nameOf(context.cast(part.name));
975
+ };
976
+ const pieces = parts.map(piece);
977
+ return join === void 0 ? pieces.join("") : pieces.filter((text) => text !== "").join(join);
978
+ };
979
+ function runBeat(beat, context) {
980
+ if (beat.run) {
981
+ const code = context.codeBeats?.[beat.run];
982
+ if (!code) throw new Error(`game-stories: code beat "${beat.run}" of "${beat.id}" is not registered`);
983
+ return code({ states: context.states, actions: context.actions, flags: context.flags, cast: context.cast });
984
+ }
985
+ const values = {};
986
+ for (const [name, options] of Object.entries(beat.values ?? {})) values[name] = options.find((option) => !option.when || holds(option.when, context))?.value ?? null;
987
+ const here = { ...context, values };
988
+ const variant = (beat.variants ?? []).find((v) => !v.when || holds(v.when, here));
989
+ if (!variant || variant.none) return null;
990
+ const changes = [];
991
+ resolveChanges(variant.changes ?? [], here, changes);
992
+ if (variant.needsChanges && changes.length === 0) return null;
993
+ return {
994
+ text: renderText(variant.text ?? [], here, changes, variant.join),
995
+ ...variant.alt ? { alt: true } : {},
996
+ ...variant.changes ? { changes } : {},
997
+ ...variant.flags ? { flags: variant.flags } : {}
998
+ };
999
+ }
1000
+
1001
+ // ../game-characters-people/dist/index.js
1002
+ var names_default = {
1003
+ sources: {
1004
+ file: "specs/001-migracao-entregas-dos-times/sources/sobreviventes__os-vivos-do-vale.js (site/pages/sobreviventes/os-vivos-do-vale.html)",
1005
+ pools: "NM (lines 610-611): m, f, s, bm, bf",
1006
+ kinship: "RELW (line 612)"
1007
+ },
1008
+ "pt-BR": {
1009
+ male: [
1010
+ "Jo\xE3o",
1011
+ "Paulo",
1012
+ "Marcos",
1013
+ "Andr\xE9",
1014
+ "Rafael",
1015
+ "Thiago",
1016
+ "Bruno",
1017
+ "Luiz",
1018
+ "S\xE9rgio",
1019
+ "Wagner",
1020
+ "C\xEDcero",
1021
+ "Edson",
1022
+ "Igor",
1023
+ "Mateus",
1024
+ "Renan",
1025
+ "Hugo"
1026
+ ],
1027
+ female: [
1028
+ "Ana",
1029
+ "Patr\xEDcia",
1030
+ "Juliana",
1031
+ "Camila",
1032
+ "Sandra",
1033
+ "Luana",
1034
+ "M\xE1rcia",
1035
+ "Fernanda",
1036
+ "Aline",
1037
+ "Rita",
1038
+ "Jaqueline",
1039
+ "Simone",
1040
+ "Larissa",
1041
+ "Bruna",
1042
+ "Tereza",
1043
+ "Vanessa"
1044
+ ],
1045
+ surnames: [
1046
+ "Silva",
1047
+ "Santos",
1048
+ "Oliveira",
1049
+ "Souza",
1050
+ "Pereira",
1051
+ "Lima",
1052
+ "Carvalho",
1053
+ "Gomes",
1054
+ "Ribeiro",
1055
+ "Martins",
1056
+ "Rocha",
1057
+ "Almeida",
1058
+ "Barbosa",
1059
+ "Teixeira",
1060
+ "Moraes",
1061
+ "Cunha"
1062
+ ],
1063
+ babyMale: [
1064
+ "Davi",
1065
+ "Miguel",
1066
+ "Gael",
1067
+ "Bento",
1068
+ "Noah",
1069
+ "Theo"
1070
+ ],
1071
+ babyFemale: [
1072
+ "Esperan\xE7a",
1073
+ "Aurora",
1074
+ "Cec\xEDlia",
1075
+ "Luna",
1076
+ "Maria Luz",
1077
+ "Alice"
1078
+ ],
1079
+ kinship: {
1080
+ male: [
1081
+ "irm\xE3o",
1082
+ "primo",
1083
+ "ex-aluno",
1084
+ "vizinho",
1085
+ "colega de trabalho",
1086
+ "afilhado",
1087
+ "filho"
1088
+ ],
1089
+ female: [
1090
+ "irm\xE3",
1091
+ "prima",
1092
+ "ex-aluna",
1093
+ "vizinha",
1094
+ "colega de trabalho",
1095
+ "afilhada",
1096
+ "filha"
1097
+ ]
1098
+ }
1099
+ }
1100
+ };
1101
+ var professions_default = [
1102
+ {
1103
+ id: "nurse",
1104
+ name: {
1105
+ en: "Nurse",
1106
+ "pt-BR": "Enfermeira(o)"
1107
+ },
1108
+ female: true,
1109
+ points: -2,
1110
+ protection: [],
1111
+ look: {
1112
+ skin: "8D5A3B",
1113
+ hair: {
1114
+ style: "bun",
1115
+ color: "1A1310"
1116
+ },
1117
+ top: {
1118
+ color: "3F8F8A",
1119
+ sleeve: "short"
1120
+ },
1121
+ pants: {
1122
+ color: "3F8F8A"
1123
+ },
1124
+ shoes: "D8D8D0"
1125
+ },
1126
+ accessories: [
1127
+ {
1128
+ id: "steth"
1129
+ },
1130
+ {
1131
+ id: "mask_chin",
1132
+ color: "AED0DE"
1133
+ }
1134
+ ],
1135
+ hand: "medbag",
1136
+ carries: {
1137
+ en: "First-aid kit, gloves",
1138
+ "pt-BR": "kit de primeiros socorros, luvas"
1139
+ }
1140
+ },
1141
+ {
1142
+ id: "police_officer",
1143
+ name: {
1144
+ en: "Police officer",
1145
+ "pt-BR": "Policial militar"
1146
+ },
1147
+ points: -4,
1148
+ protection: [
1149
+ "vest_police"
1150
+ ],
1151
+ look: {
1152
+ skin: "C49A74",
1153
+ hair: {
1154
+ style: "buzz",
1155
+ color: "2A1D14"
1156
+ },
1157
+ hat: {
1158
+ kind: "beret",
1159
+ color: "23272C"
1160
+ },
1161
+ top: {
1162
+ color: "5D6E7A",
1163
+ sleeve: "long"
1164
+ },
1165
+ pants: {
1166
+ color: "2A3038"
1167
+ },
1168
+ shoes: "111111"
1169
+ },
1170
+ accessories: [
1171
+ {
1172
+ id: "vest",
1173
+ color: "1B1D20"
1174
+ },
1175
+ {
1176
+ id: "belt",
1177
+ color: "15161A"
1178
+ },
1179
+ {
1180
+ id: "holster"
1181
+ },
1182
+ {
1183
+ id: "radio"
1184
+ }
1185
+ ],
1186
+ hand: "baton",
1187
+ carries: {
1188
+ en: ".38 revolver with 6 rounds, baton",
1189
+ "pt-BR": "rev\xF3lver .38 com 6 balas, cassetete"
1190
+ }
1191
+ },
1192
+ {
1193
+ id: "firefighter",
1194
+ name: {
1195
+ en: "Firefighter",
1196
+ "pt-BR": "Bombeiro(a)"
1197
+ },
1198
+ points: -3,
1199
+ protection: [
1200
+ "turnout_gear"
1201
+ ],
1202
+ look: {
1203
+ skin: "6B4430",
1204
+ hair: {
1205
+ style: "buzz",
1206
+ color: "111111"
1207
+ },
1208
+ hat: {
1209
+ kind: "fire",
1210
+ color: "9A2A20"
1211
+ },
1212
+ top: {
1213
+ color: "8A7A52",
1214
+ sleeve: "long"
1215
+ },
1216
+ pants: {
1217
+ color: "8A7A52"
1218
+ },
1219
+ shoes: "111111"
1220
+ },
1221
+ accessories: [
1222
+ {
1223
+ id: "stripes",
1224
+ color: "D8E04A"
1225
+ },
1226
+ {
1227
+ id: "legstripes",
1228
+ color: "D8E04A"
1229
+ }
1230
+ ],
1231
+ hand: "axe",
1232
+ carries: {
1233
+ en: "Fire axe",
1234
+ "pt-BR": "machado de bombeiro"
1235
+ }
1236
+ },
1237
+ {
1238
+ id: "mechanic",
1239
+ name: {
1240
+ en: "Mechanic",
1241
+ "pt-BR": "Mec\xE2nico(a)"
1242
+ },
1243
+ points: -1,
1244
+ protection: [],
1245
+ look: {
1246
+ skin: "A8754F",
1247
+ hair: {
1248
+ style: "short",
1249
+ color: "1D140E"
1250
+ },
1251
+ hat: {
1252
+ kind: "cap",
1253
+ color: "2D3A4F"
1254
+ },
1255
+ top: {
1256
+ color: "2D3A4F",
1257
+ sleeve: "long"
1258
+ },
1259
+ pants: {
1260
+ color: "2D3A4F"
1261
+ },
1262
+ shoes: "2A2018"
1263
+ },
1264
+ accessories: [
1265
+ {
1266
+ id: "stains"
1267
+ },
1268
+ {
1269
+ id: "rag",
1270
+ color: "B33A2A"
1271
+ }
1272
+ ],
1273
+ hand: "wrench",
1274
+ leftHand: "toolbox",
1275
+ carries: {
1276
+ en: "Toolbox",
1277
+ "pt-BR": "caixa de ferramentas"
1278
+ }
1279
+ },
1280
+ {
1281
+ id: "builder",
1282
+ name: {
1283
+ en: "Construction worker",
1284
+ "pt-BR": "Pedreiro / obras"
1285
+ },
1286
+ points: -1,
1287
+ protection: [],
1288
+ look: {
1289
+ skin: "7A4C33",
1290
+ hair: {
1291
+ style: "buzz",
1292
+ color: "111111"
1293
+ },
1294
+ hat: {
1295
+ kind: "hardhat",
1296
+ color: "E0B020"
1297
+ },
1298
+ top: {
1299
+ color: "C8662A",
1300
+ sleeve: "short"
1301
+ },
1302
+ pants: {
1303
+ color: "3B4A63"
1304
+ },
1305
+ shoes: "4A3524"
1306
+ },
1307
+ accessories: [
1308
+ {
1309
+ id: "belt",
1310
+ color: "5A4026"
1311
+ },
1312
+ {
1313
+ id: "pouches",
1314
+ color: "5A4026"
1315
+ }
1316
+ ],
1317
+ hand: "hammer",
1318
+ carries: {
1319
+ en: "Hammer, nails",
1320
+ "pt-BR": "martelo, pregos"
1321
+ }
1322
+ },
1323
+ {
1324
+ id: "cook",
1325
+ name: {
1326
+ en: "Cook",
1327
+ "pt-BR": "Cozinheiro(a)"
1328
+ },
1329
+ points: 0,
1330
+ protection: [],
1331
+ look: {
1332
+ skin: "D2A885",
1333
+ hair: {
1334
+ style: "short",
1335
+ color: "5A3A22"
1336
+ },
1337
+ hat: {
1338
+ kind: "bandana",
1339
+ color: "2B2B2B"
1340
+ },
1341
+ top: {
1342
+ color: "E6E2D6",
1343
+ sleeve: "long"
1344
+ },
1345
+ pants: {
1346
+ color: "4A4A4A"
1347
+ },
1348
+ shoes: "111111"
1349
+ },
1350
+ accessories: [
1351
+ {
1352
+ id: "apron",
1353
+ color: "3A3A3A"
1354
+ },
1355
+ {
1356
+ id: "buttons"
1357
+ }
1358
+ ],
1359
+ hand: "knife",
1360
+ carries: {
1361
+ en: "Chef knife",
1362
+ "pt-BR": "faca de chef"
1363
+ }
1364
+ },
1365
+ {
1366
+ id: "sport_shooter",
1367
+ name: {
1368
+ en: "Sport shooter",
1369
+ "pt-BR": "Atirador esportivo"
1370
+ },
1371
+ points: -4,
1372
+ protection: [],
1373
+ look: {
1374
+ skin: "C9A07C",
1375
+ hair: {
1376
+ style: "short",
1377
+ color: "3B2A1C"
1378
+ },
1379
+ hat: {
1380
+ kind: "cap",
1381
+ color: "4B5236"
1382
+ },
1383
+ face: [
1384
+ "glasses_y"
1385
+ ],
1386
+ top: {
1387
+ color: "4B5236",
1388
+ sleeve: "long"
1389
+ },
1390
+ pants: {
1391
+ color: "5F5A4A"
1392
+ },
1393
+ shoes: "3A2E22"
1394
+ },
1395
+ accessories: [
1396
+ {
1397
+ id: "earpro",
1398
+ color: "2A2A2A"
1399
+ },
1400
+ {
1401
+ id: "holster"
1402
+ },
1403
+ {
1404
+ id: "belt",
1405
+ color: "2A2A2A"
1406
+ }
1407
+ ],
1408
+ hand: "pistol",
1409
+ carries: {
1410
+ en: "9 mm pistol, no ammunition",
1411
+ "pt-BR": "pistola 9 mm sem muni\xE7\xE3o"
1412
+ }
1413
+ },
1414
+ {
1415
+ id: "farmer",
1416
+ name: {
1417
+ en: "Farmer",
1418
+ "pt-BR": "Agricultor(a)"
1419
+ },
1420
+ points: 0,
1421
+ protection: [],
1422
+ look: {
1423
+ skin: "9A6644",
1424
+ hair: {
1425
+ style: "short",
1426
+ color: "2A2018"
1427
+ },
1428
+ hat: {
1429
+ kind: "straw",
1430
+ color: "C9B06A"
1431
+ },
1432
+ top: {
1433
+ color: "8A2F2A",
1434
+ sleeve: "long",
1435
+ plaid: "3A1612"
1436
+ },
1437
+ pants: {
1438
+ color: "3B4A63"
1439
+ },
1440
+ shoes: "4A3524"
1441
+ },
1442
+ accessories: [],
1443
+ hand: "hoe",
1444
+ carries: {
1445
+ en: "Seeds, hoe",
1446
+ "pt-BR": "sementes, enxada"
1447
+ }
1448
+ },
1449
+ {
1450
+ id: "electrician",
1451
+ name: {
1452
+ en: "Electrician",
1453
+ "pt-BR": "Eletricista"
1454
+ },
1455
+ points: -1,
1456
+ protection: [],
1457
+ look: {
1458
+ skin: "B7835C",
1459
+ hair: {
1460
+ style: "short",
1461
+ color: "111111"
1462
+ },
1463
+ hat: {
1464
+ kind: "hardhat",
1465
+ color: "E8E8E2"
1466
+ },
1467
+ top: {
1468
+ color: "6F7377",
1469
+ sleeve: "long"
1470
+ },
1471
+ pants: {
1472
+ color: "2A3038"
1473
+ },
1474
+ shoes: "111111"
1475
+ },
1476
+ accessories: [
1477
+ {
1478
+ id: "belt",
1479
+ color: "5A4026"
1480
+ },
1481
+ {
1482
+ id: "pouches",
1483
+ color: "5A4026"
1484
+ },
1485
+ {
1486
+ id: "coil",
1487
+ color: "C06A2A"
1488
+ }
1489
+ ],
1490
+ hand: "pliers",
1491
+ carries: {
1492
+ en: "Pliers, insulating tape",
1493
+ "pt-BR": "alicate, fita isolante"
1494
+ }
1495
+ },
1496
+ {
1497
+ id: "courier",
1498
+ name: {
1499
+ en: "Courier",
1500
+ "pt-BR": "Motoboy"
1501
+ },
1502
+ points: 0,
1503
+ protection: [],
1504
+ look: {
1505
+ skin: "8D5A3B",
1506
+ hair: {
1507
+ style: "buzz",
1508
+ color: "111111"
1509
+ },
1510
+ hat: {
1511
+ kind: "moto",
1512
+ color: "1D1F22"
1513
+ },
1514
+ top: {
1515
+ color: "2A2C30",
1516
+ sleeve: "long"
1517
+ },
1518
+ pants: {
1519
+ color: "3B4A63"
1520
+ },
1521
+ shoes: "E0E0E0"
1522
+ },
1523
+ accessories: [
1524
+ {
1525
+ id: "stripes",
1526
+ color: "9FD0A0"
1527
+ },
1528
+ {
1529
+ id: "deliverybox",
1530
+ color: "B8322A"
1531
+ }
1532
+ ],
1533
+ carries: {
1534
+ en: "Helmet, motorbike with no fuel",
1535
+ "pt-BR": "capacete, moto sem gasolina"
1536
+ }
1537
+ },
1538
+ {
1539
+ id: "teacher",
1540
+ name: {
1541
+ en: "Teacher",
1542
+ "pt-BR": "Professor(a)"
1543
+ },
1544
+ points: 2,
1545
+ protection: [],
1546
+ look: {
1547
+ skin: "D2A885",
1548
+ hair: {
1549
+ style: "bald",
1550
+ color: "6A6A66"
1551
+ },
1552
+ face: [
1553
+ "glasses",
1554
+ "beard_g"
1555
+ ],
1556
+ top: {
1557
+ color: "7A6A4E",
1558
+ sleeve: "long"
1559
+ },
1560
+ pants: {
1561
+ color: "4A443C"
1562
+ },
1563
+ shoes: "3A2A1E"
1564
+ },
1565
+ accessories: [
1566
+ {
1567
+ id: "sweatervest",
1568
+ color: "4A5A3E"
1569
+ },
1570
+ {
1571
+ id: "collar",
1572
+ color: "D8D4C8"
1573
+ },
1574
+ {
1575
+ id: "satchel",
1576
+ color: "6A4A2A"
1577
+ }
1578
+ ],
1579
+ leftHand: "book",
1580
+ carries: {
1581
+ en: "Chemistry book, volume 1",
1582
+ "pt-BR": "livro de qu\xEDmica vol. 1"
1583
+ }
1584
+ },
1585
+ {
1586
+ id: "unemployed",
1587
+ name: {
1588
+ en: "Unemployed",
1589
+ "pt-BR": "Desempregado(a)"
1590
+ },
1591
+ points: 8,
1592
+ protection: [],
1593
+ look: {
1594
+ skin: "C49A74",
1595
+ hair: {
1596
+ style: "short",
1597
+ color: "2A1D14"
1598
+ },
1599
+ hat: {
1600
+ kind: "capback",
1601
+ color: "8A2A2A"
1602
+ },
1603
+ top: {
1604
+ color: "6A6E70",
1605
+ sleeve: "long"
1606
+ },
1607
+ pants: {
1608
+ color: "3B4A63"
1609
+ },
1610
+ shoes: "2A2A2A"
1611
+ },
1612
+ accessories: [
1613
+ {
1614
+ id: "hood",
1615
+ color: "6A6E70"
1616
+ },
1617
+ {
1618
+ id: "pocket",
1619
+ color: "5C6062"
1620
+ }
1621
+ ],
1622
+ carries: {
1623
+ en: "Nothing",
1624
+ "pt-BR": "nada"
1625
+ }
1626
+ }
1627
+ ];
1628
+ var sheet_default2 = {
1629
+ sources: {
1630
+ file: "specs/001-migracao-entregas-dos-times/sources/zumbis-personagens__personagens.js (site/pages/zumbis-personagens/personagens.html)",
1631
+ attributes: "ATTRS (lines 6-12)",
1632
+ skills: 'SKILLS (lines 14-44), milestones at levels 3, 6 and 9 (the page table "Nv 3 / Nv 6 / Nv 9")',
1633
+ professions: "PROFS (lines 45-57)",
1634
+ traits: "TRAITS (lines 59-77)",
1635
+ pointBuy: "S, BASE_POOL 14, MAXC 8, MINC 1, attributes start at 3 (lines 137-145)",
1636
+ derived: "derived() (lines 178-190)"
1637
+ },
1638
+ pointBuy: {
1639
+ start: 3,
1640
+ pool: 14,
1641
+ min: 1,
1642
+ max: 8
1643
+ },
1644
+ milestoneLevels: [
1645
+ 3,
1646
+ 6,
1647
+ 9
1648
+ ],
1649
+ attributes: [
1650
+ {
1651
+ id: "strength",
1652
+ key: "for",
1653
+ name: {
1654
+ "pt-BR": "For\xE7a",
1655
+ en: "Strength"
1656
+ },
1657
+ description: {
1658
+ "pt-BR": "Dano corpo a corpo, peso carregado, arrombar portas",
1659
+ en: "Melee damage, carried weight, forcing doors"
1660
+ }
1661
+ },
1662
+ {
1663
+ id: "vigor",
1664
+ key: "vig",
1665
+ name: {
1666
+ "pt-BR": "Vigor",
1667
+ en: "Vigor"
1668
+ },
1669
+ description: {
1670
+ "pt-BR": "Vida, f\xF4lego, resist\xEAncia a doen\xE7a e fome",
1671
+ en: "Health, stamina, resistance to disease and hunger"
1672
+ }
1673
+ },
1674
+ {
1675
+ id: "agility",
1676
+ key: "agi",
1677
+ name: {
1678
+ "pt-BR": "Agilidade",
1679
+ en: "Agility"
1680
+ },
1681
+ description: {
1682
+ "pt-BR": "Velocidade, esquiva, passos silenciosos",
1683
+ en: "Speed, dodging, quiet steps"
1684
+ }
1685
+ },
1686
+ {
1687
+ id: "perception",
1688
+ key: "per",
1689
+ name: {
1690
+ "pt-BR": "Percep\xE7\xE3o",
1691
+ en: "Perception"
1692
+ },
1693
+ description: {
1694
+ "pt-BR": "Raio de detec\xE7\xE3o, achar loot, precis\xE3o",
1695
+ en: "Detection radius, finding loot, accuracy"
1696
+ }
1697
+ },
1698
+ {
1699
+ id: "intellect",
1700
+ key: "int",
1701
+ name: {
1702
+ "pt-BR": "Intelecto",
1703
+ en: "Intellect"
1704
+ },
1705
+ description: {
1706
+ "pt-BR": "Velocidade de aprendizado, crafting, medicina",
1707
+ en: "Learning speed, crafting, medicine"
1708
+ }
1709
+ },
1710
+ {
1711
+ id: "presence",
1712
+ key: "pre",
1713
+ name: {
1714
+ "pt-BR": "Presen\xE7a",
1715
+ en: "Presence"
1716
+ },
1717
+ description: {
1718
+ "pt-BR": "Recrutar NPCs, negociar, acalmar o grupo",
1719
+ en: "Recruiting people, bargaining, calming the group"
1720
+ }
1721
+ }
1722
+ ],
1723
+ skillGroups: [
1724
+ {
1725
+ id: "combat",
1726
+ name: {
1727
+ "pt-BR": "Combate",
1728
+ en: "Combat"
1729
+ },
1730
+ skills: [
1731
+ "pistols",
1732
+ "rifles",
1733
+ "shotguns",
1734
+ "blunt",
1735
+ "blades",
1736
+ "throwing"
1737
+ ]
1738
+ },
1739
+ {
1740
+ id: "survival",
1741
+ name: {
1742
+ "pt-BR": "Sobreviv\xEAncia",
1743
+ en: "Survival"
1744
+ },
1745
+ skills: [
1746
+ "first_aid",
1747
+ "cooking",
1748
+ "farming",
1749
+ "hunting",
1750
+ "scavenging",
1751
+ "fishing"
1752
+ ]
1753
+ },
1754
+ {
1755
+ id: "technical",
1756
+ name: {
1757
+ "pt-BR": "T\xE9cnico",
1758
+ en: "Technical"
1759
+ },
1760
+ skills: [
1761
+ "mechanics",
1762
+ "electrics",
1763
+ "carpentry",
1764
+ "metalwork",
1765
+ "gunsmithing",
1766
+ "chemistry"
1767
+ ]
1768
+ },
1769
+ {
1770
+ id: "physical",
1771
+ name: {
1772
+ "pt-BR": "F\xEDsico",
1773
+ en: "Physical"
1774
+ },
1775
+ skills: [
1776
+ "running",
1777
+ "stealth",
1778
+ "climbing",
1779
+ "conditioning"
1780
+ ]
1781
+ },
1782
+ {
1783
+ id: "social",
1784
+ name: {
1785
+ "pt-BR": "Social",
1786
+ en: "Social"
1787
+ },
1788
+ skills: [
1789
+ "leadership",
1790
+ "negotiation"
1791
+ ]
1792
+ }
1793
+ ],
1794
+ skills: [
1795
+ {
1796
+ id: "pistols",
1797
+ group: "combat",
1798
+ name: {
1799
+ "pt-BR": "Pistolas e rev\xF3lveres",
1800
+ en: "Pistols and revolvers"
1801
+ },
1802
+ milestones: [
1803
+ {
1804
+ "pt-BR": "Troca r\xE1pida de carregador",
1805
+ en: "Fast magazine change"
1806
+ },
1807
+ {
1808
+ "pt-BR": "Tiro duplo controlado",
1809
+ en: "Controlled double tap"
1810
+ },
1811
+ {
1812
+ "pt-BR": "Mira de reflexo sem desacelerar",
1813
+ en: "Reflex aim without slowing down"
1814
+ }
1815
+ ]
1816
+ },
1817
+ {
1818
+ id: "rifles",
1819
+ group: "combat",
1820
+ name: {
1821
+ "pt-BR": "Fuzis e carabinas",
1822
+ en: "Rifles and carbines"
1823
+ },
1824
+ milestones: [
1825
+ {
1826
+ "pt-BR": "Respira\xE7\xE3o presa (mira est\xE1vel 3 s)",
1827
+ en: "Held breath (steady aim for 3 s)"
1828
+ },
1829
+ {
1830
+ "pt-BR": "Tiro na cabe\xE7a a 150 m sem luneta",
1831
+ en: "Headshot at 150 m without a scope"
1832
+ },
1833
+ {
1834
+ "pt-BR": "Recarga t\xE1tica mantendo cano no alvo",
1835
+ en: "Tactical reload keeping the muzzle on target"
1836
+ }
1837
+ ]
1838
+ },
1839
+ {
1840
+ id: "shotguns",
1841
+ group: "combat",
1842
+ name: {
1843
+ "pt-BR": "Espingardas",
1844
+ en: "Shotguns"
1845
+ },
1846
+ milestones: [
1847
+ {
1848
+ "pt-BR": "Recarga em movimento",
1849
+ en: "Reload on the move"
1850
+ },
1851
+ {
1852
+ "pt-BR": "Empurr\xE3o com a coronha",
1853
+ en: "Butt-stock shove"
1854
+ },
1855
+ {
1856
+ "pt-BR": "Tiro de dispers\xE3o controlada",
1857
+ en: "Controlled spread shot"
1858
+ }
1859
+ ]
1860
+ },
1861
+ {
1862
+ id: "blunt",
1863
+ group: "combat",
1864
+ name: {
1865
+ "pt-BR": "Armas contundentes",
1866
+ en: "Blunt weapons"
1867
+ },
1868
+ milestones: [
1869
+ {
1870
+ "pt-BR": "Golpe de atordoamento",
1871
+ en: "Stunning blow"
1872
+ },
1873
+ {
1874
+ "pt-BR": "Derrubar dois de uma vez",
1875
+ en: "Knock down two at once"
1876
+ },
1877
+ {
1878
+ "pt-BR": "Golpe fatal no cr\xE2nio ca\xEDdo",
1879
+ en: "Killing blow to a fallen skull"
1880
+ }
1881
+ ]
1882
+ },
1883
+ {
1884
+ id: "blades",
1885
+ group: "combat",
1886
+ name: {
1887
+ "pt-BR": "Armas cortantes",
1888
+ en: "Bladed weapons"
1889
+ },
1890
+ milestones: [
1891
+ {
1892
+ "pt-BR": "Decapitar com fac\xE3o",
1893
+ en: "Behead with a machete"
1894
+ },
1895
+ {
1896
+ "pt-BR": "Esfaqueamento silencioso por tr\xE1s",
1897
+ en: "Silent stab from behind"
1898
+ },
1899
+ {
1900
+ "pt-BR": "Combo de tr\xEAs golpes sem perder f\xF4lego",
1901
+ en: "Three-hit combo without losing breath"
1902
+ }
1903
+ ]
1904
+ },
1905
+ {
1906
+ id: "throwing",
1907
+ group: "combat",
1908
+ name: {
1909
+ "pt-BR": "Arremesso",
1910
+ en: "Throwing"
1911
+ },
1912
+ milestones: [
1913
+ {
1914
+ "pt-BR": "Garrafa para distrair horda",
1915
+ en: "Bottle to distract a horde"
1916
+ },
1917
+ {
1918
+ "pt-BR": "Molotov com arco maior",
1919
+ en: "Molotov with a longer arc"
1920
+ },
1921
+ {
1922
+ "pt-BR": "Faca de arremesso letal",
1923
+ en: "Lethal throwing knife"
1924
+ }
1925
+ ]
1926
+ },
1927
+ {
1928
+ id: "first_aid",
1929
+ group: "survival",
1930
+ name: {
1931
+ "pt-BR": "Primeiros socorros",
1932
+ en: "First aid"
1933
+ },
1934
+ milestones: [
1935
+ {
1936
+ "pt-BR": "Sutura",
1937
+ en: "Stitches"
1938
+ },
1939
+ {
1940
+ "pt-BR": "Remover proj\xE9til",
1941
+ en: "Remove a bullet"
1942
+ },
1943
+ {
1944
+ "pt-BR": "Amputa\xE7\xE3o com chance de sobreviver +10%",
1945
+ en: "Amputation with +10% chance to survive"
1946
+ }
1947
+ ]
1948
+ },
1949
+ {
1950
+ id: "cooking",
1951
+ group: "survival",
1952
+ name: {
1953
+ "pt-BR": "Culin\xE1ria",
1954
+ en: "Cooking"
1955
+ },
1956
+ milestones: [
1957
+ {
1958
+ "pt-BR": "Conservas que duram meses",
1959
+ en: "Preserves that last months"
1960
+ },
1961
+ {
1962
+ "pt-BR": "Pratos que d\xE3o moral",
1963
+ en: "Dishes that lift morale"
1964
+ },
1965
+ {
1966
+ "pt-BR": "Cozinhar sem fuma\xE7a vis\xEDvel",
1967
+ en: "Cook without visible smoke"
1968
+ }
1969
+ ]
1970
+ },
1971
+ {
1972
+ id: "farming",
1973
+ group: "survival",
1974
+ name: {
1975
+ "pt-BR": "Agricultura",
1976
+ en: "Farming"
1977
+ },
1978
+ milestones: [
1979
+ {
1980
+ "pt-BR": "Compostagem",
1981
+ en: "Composting"
1982
+ },
1983
+ {
1984
+ "pt-BR": "Estufa no inverno",
1985
+ en: "Winter greenhouse"
1986
+ },
1987
+ {
1988
+ "pt-BR": "Colheita dupla",
1989
+ en: "Double harvest"
1990
+ }
1991
+ ]
1992
+ },
1993
+ {
1994
+ id: "hunting",
1995
+ group: "survival",
1996
+ name: {
1997
+ "pt-BR": "Ca\xE7a e armadilhas",
1998
+ en: "Hunting and traps"
1999
+ },
2000
+ milestones: [
2001
+ {
2002
+ "pt-BR": "Armadilha de la\xE7o",
2003
+ en: "Snare trap"
2004
+ },
2005
+ {
2006
+ "pt-BR": "Ler rastros de animais e zumbis",
2007
+ en: "Read animal and zombie tracks"
2008
+ },
2009
+ {
2010
+ "pt-BR": "Armadilha de estacas para hordas",
2011
+ en: "Stake trap for hordes"
2012
+ }
2013
+ ]
2014
+ },
2015
+ {
2016
+ id: "scavenging",
2017
+ group: "survival",
2018
+ name: {
2019
+ "pt-BR": "Busca",
2020
+ en: "Scavenging"
2021
+ },
2022
+ milestones: [
2023
+ {
2024
+ "pt-BR": "Achar compartimentos escondidos",
2025
+ en: "Find hidden compartments"
2026
+ },
2027
+ {
2028
+ "pt-BR": "Saber o que tem dentro sem abrir",
2029
+ en: "Know what is inside without opening"
2030
+ },
2031
+ {
2032
+ "pt-BR": "Loot raro +15%",
2033
+ en: "Rare loot +15%"
2034
+ }
2035
+ ]
2036
+ },
2037
+ {
2038
+ id: "fishing",
2039
+ group: "survival",
2040
+ name: {
2041
+ "pt-BR": "Pesca",
2042
+ en: "Fishing"
2043
+ },
2044
+ milestones: [
2045
+ {
2046
+ "pt-BR": "Linha de espera",
2047
+ en: "Set line"
2048
+ },
2049
+ {
2050
+ "pt-BR": "Rede",
2051
+ en: "Net"
2052
+ },
2053
+ {
2054
+ "pt-BR": "Peixe grande",
2055
+ en: "Big fish"
2056
+ }
2057
+ ]
2058
+ },
2059
+ {
2060
+ id: "mechanics",
2061
+ group: "technical",
2062
+ name: {
2063
+ "pt-BR": "Mec\xE2nica",
2064
+ en: "Mechanics"
2065
+ },
2066
+ milestones: [
2067
+ {
2068
+ "pt-BR": "Liga\xE7\xE3o direta",
2069
+ en: "Hot-wiring"
2070
+ },
2071
+ {
2072
+ "pt-BR": "Trocar motor",
2073
+ en: "Engine swap"
2074
+ },
2075
+ {
2076
+ "pt-BR": "Carro blindado artesanal",
2077
+ en: "Home-made armoured car"
2078
+ }
2079
+ ]
2080
+ },
2081
+ {
2082
+ id: "electrics",
2083
+ group: "technical",
2084
+ name: {
2085
+ "pt-BR": "El\xE9trica",
2086
+ en: "Electrics"
2087
+ },
2088
+ milestones: [
2089
+ {
2090
+ "pt-BR": "Ligar gerador na casa",
2091
+ en: "Hook a generator to the house"
2092
+ },
2093
+ {
2094
+ "pt-BR": "R\xE1dio caseiro",
2095
+ en: "Home-made radio"
2096
+ },
2097
+ {
2098
+ "pt-BR": "Cerca el\xE9trica",
2099
+ en: "Electric fence"
2100
+ }
2101
+ ]
2102
+ },
2103
+ {
2104
+ id: "carpentry",
2105
+ group: "technical",
2106
+ name: {
2107
+ "pt-BR": "Carpintaria",
2108
+ en: "Carpentry"
2109
+ },
2110
+ milestones: [
2111
+ {
2112
+ "pt-BR": "T\xE1buas nas janelas",
2113
+ en: "Boards on the windows"
2114
+ },
2115
+ {
2116
+ "pt-BR": "Muro de madeira",
2117
+ en: "Wooden wall"
2118
+ },
2119
+ {
2120
+ "pt-BR": "Torre de vigia",
2121
+ en: "Watchtower"
2122
+ }
2123
+ ]
2124
+ },
2125
+ {
2126
+ id: "metalwork",
2127
+ group: "technical",
2128
+ name: {
2129
+ "pt-BR": "Metalurgia",
2130
+ en: "Metalwork"
2131
+ },
2132
+ milestones: [
2133
+ {
2134
+ "pt-BR": "Solda em chapa",
2135
+ en: "Sheet welding"
2136
+ },
2137
+ {
2138
+ "pt-BR": "Port\xE3o de a\xE7o",
2139
+ en: "Steel gate"
2140
+ },
2141
+ {
2142
+ "pt-BR": "Blindagem de ve\xEDculo",
2143
+ en: "Vehicle armour"
2144
+ }
2145
+ ]
2146
+ },
2147
+ {
2148
+ id: "gunsmithing",
2149
+ group: "technical",
2150
+ name: {
2151
+ "pt-BR": "Armeiro",
2152
+ en: "Gunsmithing"
2153
+ },
2154
+ milestones: [
2155
+ {
2156
+ "pt-BR": "Limpar e evitar pane",
2157
+ en: "Clean and prevent jams"
2158
+ },
2159
+ {
2160
+ "pt-BR": "Recarregar muni\xE7\xE3o (estojo, espoleta, p\xF3lvora)",
2161
+ en: "Reload ammunition (case, primer, powder)"
2162
+ },
2163
+ {
2164
+ "pt-BR": "Adaptar pe\xE7as entre modelos",
2165
+ en: "Adapt parts between models"
2166
+ }
2167
+ ]
2168
+ },
2169
+ {
2170
+ id: "chemistry",
2171
+ group: "technical",
2172
+ name: {
2173
+ "pt-BR": "Qu\xEDmica",
2174
+ en: "Chemistry"
2175
+ },
2176
+ milestones: [
2177
+ {
2178
+ "pt-BR": "P\xF3lvora negra",
2179
+ en: "Black powder"
2180
+ },
2181
+ {
2182
+ "pt-BR": "Antibi\xF3tico caseiro",
2183
+ en: "Home-made antibiotic"
2184
+ },
2185
+ {
2186
+ "pt-BR": "Explosivo improvisado",
2187
+ en: "Improvised explosive"
2188
+ }
2189
+ ]
2190
+ },
2191
+ {
2192
+ id: "running",
2193
+ group: "physical",
2194
+ name: {
2195
+ "pt-BR": "Corrida",
2196
+ en: "Running"
2197
+ },
2198
+ milestones: [
2199
+ {
2200
+ "pt-BR": "Arrancada curta sem gastar f\xF4lego",
2201
+ en: "Short dash without spending stamina"
2202
+ },
2203
+ {
2204
+ "pt-BR": "Pular cerca sem parar",
2205
+ en: "Jump a fence without stopping"
2206
+ },
2207
+ {
2208
+ "pt-BR": "Corrida longa: f\xF4lego \u221230%",
2209
+ en: "Long run: stamina \u221230%"
2210
+ }
2211
+ ]
2212
+ },
2213
+ {
2214
+ id: "stealth",
2215
+ group: "physical",
2216
+ name: {
2217
+ "pt-BR": "Furtividade",
2218
+ en: "Stealth"
2219
+ },
2220
+ milestones: [
2221
+ {
2222
+ "pt-BR": "Agachado mais r\xE1pido",
2223
+ en: "Faster crouching"
2224
+ },
2225
+ {
2226
+ "pt-BR": "Passar por zumbi parado",
2227
+ en: "Walk past a standing zombie"
2228
+ },
2229
+ {
2230
+ "pt-BR": "Invis\xEDvel no escuro a 3 m",
2231
+ en: "Invisible in the dark at 3 m"
2232
+ }
2233
+ ]
2234
+ },
2235
+ {
2236
+ id: "climbing",
2237
+ group: "physical",
2238
+ name: {
2239
+ "pt-BR": "Escalada",
2240
+ en: "Climbing"
2241
+ },
2242
+ milestones: [
2243
+ {
2244
+ "pt-BR": "Subir em muro",
2245
+ en: "Climb a wall"
2246
+ },
2247
+ {
2248
+ "pt-BR": "Telhados conectados",
2249
+ en: "Connected rooftops"
2250
+ },
2251
+ {
2252
+ "pt-BR": "Descer de corda",
2253
+ en: "Rope descent"
2254
+ }
2255
+ ]
2256
+ },
2257
+ {
2258
+ id: "conditioning",
2259
+ group: "physical",
2260
+ name: {
2261
+ "pt-BR": "Condicionamento",
2262
+ en: "Conditioning"
2263
+ },
2264
+ milestones: [
2265
+ {
2266
+ "pt-BR": "Carga +10%",
2267
+ en: "Load +10%"
2268
+ },
2269
+ {
2270
+ "pt-BR": "Dano recebido \u221210%",
2271
+ en: "Damage taken \u221210%"
2272
+ },
2273
+ {
2274
+ "pt-BR": "Ignora 1 n\xEDvel de dor",
2275
+ en: "Ignore 1 level of pain"
2276
+ }
2277
+ ]
2278
+ },
2279
+ {
2280
+ id: "leadership",
2281
+ group: "social",
2282
+ name: {
2283
+ "pt-BR": "Lideran\xE7a",
2284
+ en: "Leadership"
2285
+ },
2286
+ milestones: [
2287
+ {
2288
+ "pt-BR": "Dar ordens simples ao grupo",
2289
+ en: "Give the group simple orders"
2290
+ },
2291
+ {
2292
+ "pt-BR": "Grupo n\xE3o entra em p\xE2nico perto de voc\xEA",
2293
+ en: "The group does not panic near you"
2294
+ },
2295
+ {
2296
+ "pt-BR": "Recrutar NPC hostil",
2297
+ en: "Recruit a hostile person"
2298
+ }
2299
+ ]
2300
+ },
2301
+ {
2302
+ id: "negotiation",
2303
+ group: "social",
2304
+ name: {
2305
+ "pt-BR": "Negocia\xE7\xE3o",
2306
+ en: "Negotiation"
2307
+ },
2308
+ milestones: [
2309
+ {
2310
+ "pt-BR": "Ver o pre\xE7o real das coisas",
2311
+ en: "See the real price of things"
2312
+ },
2313
+ {
2314
+ "pt-BR": "Trocas a cr\xE9dito",
2315
+ en: "Trade on credit"
2316
+ },
2317
+ {
2318
+ "pt-BR": "Convencer grupo rival a recuar",
2319
+ en: "Convince a rival group to back off"
2320
+ }
2321
+ ]
2322
+ }
2323
+ ],
2324
+ professions: [
2325
+ {
2326
+ id: "nurse",
2327
+ key: "enf",
2328
+ points: -2,
2329
+ skills: {
2330
+ first_aid: 3,
2331
+ chemistry: 1
2332
+ },
2333
+ bonus: {
2334
+ "pt-BR": "V\xEA a gravidade exata de ferimentos e sabe se um ferimento infeccionou",
2335
+ en: "Sees the exact severity of wounds and knows whether a wound got infected"
2336
+ },
2337
+ kit: "Kit de primeiros socorros, luvas"
2338
+ },
2339
+ {
2340
+ id: "police_officer",
2341
+ key: "pm",
2342
+ points: -4,
2343
+ skills: {
2344
+ pistols: 3,
2345
+ rifles: 1,
2346
+ blunt: 1
2347
+ },
2348
+ bonus: {
2349
+ "pt-BR": "P\xE2nico \u221225% em combate; abre viaturas e delegacias sem chave",
2350
+ en: "Panic \u221225% in combat; opens police cars and stations without a key"
2351
+ },
2352
+ kit: "Rev\xF3lver .38 com 6 balas, cassetete"
2353
+ },
2354
+ {
2355
+ id: "firefighter",
2356
+ key: "bomb",
2357
+ points: -3,
2358
+ skills: {
2359
+ first_aid: 2,
2360
+ conditioning: 2,
2361
+ blades: 1
2362
+ },
2363
+ bonus: {
2364
+ "pt-BR": "Imune a fuma\xE7a leve; arromba portas com machado 2\xD7 mais r\xE1pido",
2365
+ en: "Immune to light smoke; breaks doors with an axe 2\xD7 faster"
2366
+ },
2367
+ kit: "Machado de bombeiro"
2368
+ },
2369
+ {
2370
+ id: "mechanic",
2371
+ key: "mec",
2372
+ points: -1,
2373
+ skills: {
2374
+ mechanics: 3,
2375
+ metalwork: 1
2376
+ },
2377
+ bonus: {
2378
+ "pt-BR": "Sabe o estado de qualquer ve\xEDculo s\xF3 de olhar",
2379
+ en: "Knows the state of any vehicle at a glance"
2380
+ },
2381
+ kit: "Caixa de ferramentas"
2382
+ },
2383
+ {
2384
+ id: "builder",
2385
+ key: "ped",
2386
+ points: -1,
2387
+ skills: {
2388
+ carpentry: 2,
2389
+ metalwork: 1,
2390
+ conditioning: 1
2391
+ },
2392
+ bonus: {
2393
+ "pt-BR": "Barricadas com +30% de resist\xEAncia",
2394
+ en: "Barricades with +30% strength"
2395
+ },
2396
+ kit: "Martelo, pregos"
2397
+ },
2398
+ {
2399
+ id: "cook",
2400
+ key: "coz",
2401
+ points: 0,
2402
+ skills: {
2403
+ cooking: 3,
2404
+ blades: 1
2405
+ },
2406
+ bonus: {
2407
+ "pt-BR": "Comida estraga 40% mais devagar sob seu cuidado",
2408
+ en: "Food spoils 40% slower in their care"
2409
+ },
2410
+ kit: "Faca de chef"
2411
+ },
2412
+ {
2413
+ id: "sport_shooter",
2414
+ key: "cac",
2415
+ points: -4,
2416
+ skills: {
2417
+ rifles: 2,
2418
+ pistols: 2,
2419
+ gunsmithing: 1
2420
+ },
2421
+ bonus: {
2422
+ "pt-BR": "Reconhece calibre e muni\xE7\xE3o compat\xEDvel na hora; pane \u221230%",
2423
+ en: "Recognises calibre and compatible ammunition at once; jams \u221230%"
2424
+ },
2425
+ kit: "Pistola 9 mm sem muni\xE7\xE3o"
2426
+ },
2427
+ {
2428
+ id: "farmer",
2429
+ key: "agr",
2430
+ points: 0,
2431
+ skills: {
2432
+ farming: 3,
2433
+ hunting: 1
2434
+ },
2435
+ bonus: {
2436
+ "pt-BR": "Planta sem livro; sabe a \xE9poca de cada cultura",
2437
+ en: "Plants without a book; knows the season of each crop"
2438
+ },
2439
+ kit: "Sementes, enxada"
2440
+ },
2441
+ {
2442
+ id: "electrician",
2443
+ key: "ele",
2444
+ points: -1,
2445
+ skills: {
2446
+ electrics: 3,
2447
+ climbing: 1
2448
+ },
2449
+ bonus: {
2450
+ "pt-BR": "Liga gerador sem risco de choque",
2451
+ en: "Hooks up a generator with no risk of a shock"
2452
+ },
2453
+ kit: "Alicate, fita isolante"
2454
+ },
2455
+ {
2456
+ id: "courier",
2457
+ key: "moto",
2458
+ points: 0,
2459
+ skills: {
2460
+ running: 1,
2461
+ mechanics: 1,
2462
+ climbing: 1
2463
+ },
2464
+ bonus: {
2465
+ "pt-BR": "Conhece as ruas: o mapa da cidade j\xE1 come\xE7a revelado",
2466
+ en: "Knows the streets: the city map starts revealed"
2467
+ },
2468
+ kit: "Capacete (protege cabe\xE7a), moto sem gasolina"
2469
+ },
2470
+ {
2471
+ id: "teacher",
2472
+ key: "prof",
2473
+ points: 2,
2474
+ skills: {
2475
+ leadership: 1,
2476
+ chemistry: 1
2477
+ },
2478
+ bonus: {
2479
+ "pt-BR": "Ensina outros com XP \xD72,5 em vez de \xD72",
2480
+ en: "Teaches others at XP \xD72.5 instead of \xD72"
2481
+ },
2482
+ kit: "Livro de qu\xEDmica vol. 1"
2483
+ },
2484
+ {
2485
+ id: "unemployed",
2486
+ key: "des",
2487
+ points: 8,
2488
+ skills: {},
2489
+ bonus: {
2490
+ "pt-BR": "Nenhum, mas muito mais pontos livres",
2491
+ en: "None, but many more free points"
2492
+ },
2493
+ kit: "Nada"
2494
+ }
2495
+ ],
2496
+ traits: [
2497
+ {
2498
+ id: "athlete",
2499
+ key: "atleta",
2500
+ cost: -4,
2501
+ excludes: [
2502
+ "sedentary",
2503
+ "asthmatic"
2504
+ ],
2505
+ name: {
2506
+ "pt-BR": "Atleta",
2507
+ en: "Athlete"
2508
+ },
2509
+ effect: {
2510
+ "pt-BR": "F\xF4lego +25%, corrida +0,3 m/s",
2511
+ en: "Stamina +25%, running +0.3 m/s"
2512
+ }
2513
+ },
2514
+ {
2515
+ id: "eagle_eyes",
2516
+ key: "olhos",
2517
+ cost: -3,
2518
+ excludes: [
2519
+ "short_sighted"
2520
+ ],
2521
+ name: {
2522
+ "pt-BR": "Olhos de \xE1guia",
2523
+ en: "Eagle eyes"
2524
+ },
2525
+ effect: {
2526
+ "pt-BR": "Raio de detec\xE7\xE3o +25%",
2527
+ en: "Detection radius +25%"
2528
+ }
2529
+ },
2530
+ {
2531
+ id: "cold_blood",
2532
+ key: "frio",
2533
+ cost: -4,
2534
+ excludes: [
2535
+ "fearful"
2536
+ ],
2537
+ name: {
2538
+ "pt-BR": "Sangue frio",
2539
+ en: "Cold blood"
2540
+ },
2541
+ effect: {
2542
+ "pt-BR": "Compostura +40%",
2543
+ en: "Composure +40%"
2544
+ }
2545
+ },
2546
+ {
2547
+ id: "fast_learner",
2548
+ key: "rapido",
2549
+ cost: -5,
2550
+ excludes: [
2551
+ "slow_learner"
2552
+ ],
2553
+ name: {
2554
+ "pt-BR": "Aprende r\xE1pido",
2555
+ en: "Fast learner"
2556
+ },
2557
+ effect: {
2558
+ "pt-BR": "XP +30% em tudo",
2559
+ en: "XP +30% in everything"
2560
+ }
2561
+ },
2562
+ {
2563
+ id: "light_feet",
2564
+ key: "silen",
2565
+ cost: -3,
2566
+ excludes: [
2567
+ "clumsy"
2568
+ ],
2569
+ name: {
2570
+ "pt-BR": "P\xE9s leves",
2571
+ en: "Light feet"
2572
+ },
2573
+ effect: {
2574
+ "pt-BR": "Ru\xEDdo dos passos \u221230%",
2575
+ en: "Footstep noise \u221230%"
2576
+ }
2577
+ },
2578
+ {
2579
+ id: "organized",
2580
+ key: "org",
2581
+ cost: -3,
2582
+ excludes: [],
2583
+ name: {
2584
+ "pt-BR": "Organizado",
2585
+ en: "Organized"
2586
+ },
2587
+ effect: {
2588
+ "pt-BR": "Carga +20%",
2589
+ en: "Load +20%"
2590
+ }
2591
+ },
2592
+ {
2593
+ id: "tough_body",
2594
+ key: "touro",
2595
+ cost: -4,
2596
+ excludes: [
2597
+ "bleeds_easily"
2598
+ ],
2599
+ name: {
2600
+ "pt-BR": "Corpo resistente",
2601
+ en: "Tough body"
2602
+ },
2603
+ effect: {
2604
+ "pt-BR": "Vida +15, sangra 25% menos",
2605
+ en: "Health +15, bleeds 25% less"
2606
+ }
2607
+ },
2608
+ {
2609
+ id: "light_sleeper",
2610
+ key: "sono",
2611
+ cost: -2,
2612
+ excludes: [
2613
+ "heavy_sleeper"
2614
+ ],
2615
+ name: {
2616
+ "pt-BR": "Sono leve",
2617
+ en: "Light sleeper"
2618
+ },
2619
+ effect: {
2620
+ "pt-BR": "Acorda com ru\xEDdo a 15 m",
2621
+ en: "Wakes to noise at 15 m"
2622
+ }
2623
+ },
2624
+ {
2625
+ id: "smoker",
2626
+ key: "fumante",
2627
+ cost: 3,
2628
+ excludes: [],
2629
+ name: {
2630
+ "pt-BR": "Fumante",
2631
+ en: "Smoker"
2632
+ },
2633
+ effect: {
2634
+ "pt-BR": "Estresse sobe sem cigarro; pode largar em 20 dias",
2635
+ en: "Stress rises without cigarettes; can quit in 20 days"
2636
+ }
2637
+ },
2638
+ {
2639
+ id: "asthmatic",
2640
+ key: "asma",
2641
+ cost: 4,
2642
+ excludes: [
2643
+ "athlete"
2644
+ ],
2645
+ name: {
2646
+ "pt-BR": "Asm\xE1tico",
2647
+ en: "Asthmatic"
2648
+ },
2649
+ effect: {
2650
+ "pt-BR": "F\xF4lego \u221230%",
2651
+ en: "Stamina \u221230%"
2652
+ }
2653
+ },
2654
+ {
2655
+ id: "short_sighted",
2656
+ key: "miope",
2657
+ cost: 3,
2658
+ excludes: [
2659
+ "eagle_eyes"
2660
+ ],
2661
+ name: {
2662
+ "pt-BR": "M\xEDope",
2663
+ en: "Short-sighted"
2664
+ },
2665
+ effect: {
2666
+ "pt-BR": "Raio \u221230% at\xE9 achar \xF3culos",
2667
+ en: "Radius \u221230% until glasses are found"
2668
+ }
2669
+ },
2670
+ {
2671
+ id: "clumsy",
2672
+ key: "desaj",
2673
+ cost: 3,
2674
+ excludes: [
2675
+ "light_feet"
2676
+ ],
2677
+ name: {
2678
+ "pt-BR": "Desajeitado",
2679
+ en: "Clumsy"
2680
+ },
2681
+ effect: {
2682
+ "pt-BR": "Ru\xEDdo +30%, derruba coisas",
2683
+ en: "Noise +30%, drops things"
2684
+ }
2685
+ },
2686
+ {
2687
+ id: "fearful",
2688
+ key: "medroso",
2689
+ cost: 4,
2690
+ excludes: [
2691
+ "cold_blood"
2692
+ ],
2693
+ name: {
2694
+ "pt-BR": "Medroso",
2695
+ en: "Fearful"
2696
+ },
2697
+ effect: {
2698
+ "pt-BR": "Compostura \u221240%",
2699
+ en: "Composure \u221240%"
2700
+ }
2701
+ },
2702
+ {
2703
+ id: "sedentary",
2704
+ key: "sedent",
2705
+ cost: 3,
2706
+ excludes: [
2707
+ "athlete"
2708
+ ],
2709
+ name: {
2710
+ "pt-BR": "Sedent\xE1rio",
2711
+ en: "Sedentary"
2712
+ },
2713
+ effect: {
2714
+ "pt-BR": "F\xF4lego \u221220%, corrida \u22120,3 m/s",
2715
+ en: "Stamina \u221220%, running \u22120.3 m/s"
2716
+ }
2717
+ },
2718
+ {
2719
+ id: "bleeds_easily",
2720
+ key: "hemo",
2721
+ cost: 5,
2722
+ excludes: [
2723
+ "tough_body"
2724
+ ],
2725
+ name: {
2726
+ "pt-BR": "Sangra f\xE1cil",
2727
+ en: "Bleeds easily"
2728
+ },
2729
+ effect: {
2730
+ "pt-BR": "Sangramento \xD72",
2731
+ en: "Bleeding \xD72"
2732
+ }
2733
+ },
2734
+ {
2735
+ id: "slow_learner",
2736
+ key: "lento",
2737
+ cost: 4,
2738
+ excludes: [
2739
+ "fast_learner"
2740
+ ],
2741
+ name: {
2742
+ "pt-BR": "Aprende devagar",
2743
+ en: "Slow learner"
2744
+ },
2745
+ effect: {
2746
+ "pt-BR": "XP \u221230%",
2747
+ en: "XP \u221230%"
2748
+ }
2749
+ },
2750
+ {
2751
+ id: "heavy_sleeper",
2752
+ key: "pesado",
2753
+ cost: 2,
2754
+ excludes: [
2755
+ "light_sleeper"
2756
+ ],
2757
+ name: {
2758
+ "pt-BR": "Sono pesado",
2759
+ en: "Heavy sleeper"
2760
+ },
2761
+ effect: {
2762
+ "pt-BR": "N\xE3o acorda com barulho",
2763
+ en: "Does not wake to noise"
2764
+ }
2765
+ },
2766
+ {
2767
+ id: "big_eater",
2768
+ key: "glutao",
2769
+ cost: 2,
2770
+ excludes: [],
2771
+ name: {
2772
+ "pt-BR": "Come muito",
2773
+ en: "Big eater"
2774
+ },
2775
+ effect: {
2776
+ "pt-BR": "Fome +30%",
2777
+ en: "Hunger +30%"
2778
+ }
2779
+ }
2780
+ ],
2781
+ derived: [
2782
+ {
2783
+ id: "health",
2784
+ base: 70,
2785
+ per: {
2786
+ vigor: 6
2787
+ },
2788
+ traits: [
2789
+ {
2790
+ trait: "tough_body",
2791
+ add: 15
2792
+ }
2793
+ ],
2794
+ source: 'derived(): vida=70+6*a.vig+(has("touro")?15:0)',
2795
+ name: {
2796
+ "pt-BR": "Vida m\xE1xima",
2797
+ en: "Max health"
2798
+ }
2799
+ },
2800
+ {
2801
+ id: "stamina",
2802
+ base: 40,
2803
+ per: {
2804
+ vigor: 6,
2805
+ agility: 4
2806
+ },
2807
+ traits: [
2808
+ {
2809
+ trait: "athlete",
2810
+ mul: 1.25
2811
+ },
2812
+ {
2813
+ trait: "asthmatic",
2814
+ mul: 0.7
2815
+ },
2816
+ {
2817
+ trait: "sedentary",
2818
+ mul: 0.8
2819
+ }
2820
+ ],
2821
+ source: "derived(): folego=40+6*a.vig+4*a.agi; \xD71.25 atleta, \xD7.7 asma, \xD7.8 sedent",
2822
+ name: {
2823
+ "pt-BR": "F\xF4lego",
2824
+ en: "Stamina"
2825
+ }
2826
+ },
2827
+ {
2828
+ id: "carry",
2829
+ base: 8,
2830
+ per: {
2831
+ strength: 2.5
2832
+ },
2833
+ traits: [
2834
+ {
2835
+ trait: "organized",
2836
+ mul: 1.2
2837
+ }
2838
+ ],
2839
+ unit: "kg",
2840
+ source: "derived(): carga=8+2.5*a.for; \xD71.2 org",
2841
+ name: {
2842
+ "pt-BR": "Carga m\xE1xima",
2843
+ en: "Max load"
2844
+ }
2845
+ },
2846
+ {
2847
+ id: "run_speed",
2848
+ base: 5.2,
2849
+ per: {
2850
+ agility: 0.12
2851
+ },
2852
+ traits: [
2853
+ {
2854
+ trait: "athlete",
2855
+ add: 0.3
2856
+ },
2857
+ {
2858
+ trait: "sedentary",
2859
+ add: -0.3
2860
+ }
2861
+ ],
2862
+ unit: "m/s",
2863
+ source: "derived(): corrida=5.2+0.12*a.agi+(atleta?.3:0)-(sedent?.3:0)",
2864
+ name: {
2865
+ "pt-BR": "Velocidade de corrida",
2866
+ en: "Running speed"
2867
+ }
2868
+ },
2869
+ {
2870
+ id: "detection",
2871
+ base: 12,
2872
+ per: {
2873
+ perception: 2
2874
+ },
2875
+ traits: [
2876
+ {
2877
+ trait: "eagle_eyes",
2878
+ mul: 1.25
2879
+ },
2880
+ {
2881
+ trait: "short_sighted",
2882
+ mul: 0.7
2883
+ }
2884
+ ],
2885
+ unit: "m",
2886
+ source: "derived(): raio=12+2*a.per; \xD71.25 olhos, \xD7.7 miope",
2887
+ name: {
2888
+ "pt-BR": "Raio de detec\xE7\xE3o",
2889
+ en: "Detection radius"
2890
+ }
2891
+ },
2892
+ {
2893
+ id: "noise",
2894
+ base: 100,
2895
+ per: {
2896
+ agility: -4
2897
+ },
2898
+ traits: [
2899
+ {
2900
+ trait: "light_feet",
2901
+ mul: 0.7
2902
+ },
2903
+ {
2904
+ trait: "clumsy",
2905
+ mul: 1.3
2906
+ }
2907
+ ],
2908
+ unit: "%",
2909
+ source: "derived(): ruido=100-a.agi*4; \xD7.7 silen, \xD71.3 desaj",
2910
+ name: {
2911
+ "pt-BR": "Ru\xEDdo dos passos",
2912
+ en: "Footstep noise"
2913
+ }
2914
+ },
2915
+ {
2916
+ id: "learning",
2917
+ base: 70,
2918
+ per: {
2919
+ intellect: 6
2920
+ },
2921
+ traits: [
2922
+ {
2923
+ trait: "fast_learner",
2924
+ mul: 1.3
2925
+ },
2926
+ {
2927
+ trait: "slow_learner",
2928
+ mul: 0.7
2929
+ }
2930
+ ],
2931
+ unit: "%",
2932
+ source: "derived(): xp=70+6*a.int; \xD71.3 rapido, \xD7.7 lento",
2933
+ name: {
2934
+ "pt-BR": "Velocidade de aprendizado",
2935
+ en: "Learning speed"
2936
+ }
2937
+ },
2938
+ {
2939
+ id: "composure",
2940
+ base: 20,
2941
+ per: {
2942
+ vigor: 3,
2943
+ presence: 4
2944
+ },
2945
+ traits: [
2946
+ {
2947
+ trait: "cold_blood",
2948
+ mul: 1.4
2949
+ },
2950
+ {
2951
+ trait: "fearful",
2952
+ mul: 0.6
2953
+ }
2954
+ ],
2955
+ source: "derived(): comp=20+3*a.vig+4*a.pre; \xD71.4 frio, \xD7.6 medroso",
2956
+ name: {
2957
+ "pt-BR": "Compostura",
2958
+ en: "Composure"
2959
+ }
2960
+ },
2961
+ {
2962
+ id: "melee",
2963
+ base: -5,
2964
+ per: {
2965
+ strength: 1
2966
+ },
2967
+ scale: 6,
2968
+ traits: [],
2969
+ unit: "%",
2970
+ source: "derived(): dano=(a.for-5)*6",
2971
+ name: {
2972
+ "pt-BR": "Dano corpo a corpo",
2973
+ en: "Melee damage"
2974
+ }
2975
+ }
2976
+ ]
2977
+ };
2978
+ var PROFESSIONS = professions_default;
2979
+ var NAME_POOLS = { "pt-BR": names_default["pt-BR"] };
2980
+ var getNamePools = (locale = "pt-BR") => NAME_POOLS[locale] ?? NAME_POOLS["pt-BR"];
2981
+ var SHEET_RULESET = sheet_default2;
2982
+ var getProfession = (id) => PROFESSIONS.find((profession) => profession.id === id);
2983
+
2984
+ // src/sheet/sheet.ts
2985
+ function createSheet(options = {}) {
2986
+ const rules = options.ruleset ?? SHEET_RULESET;
2987
+ const N = { ...sheet_default, ...options.numbers };
2988
+ const professionId = options.profession ?? rules.professions[rules.professions.length - 1].id;
2989
+ const profession = rules.professions.find((p) => p.id === professionId);
2990
+ if (!profession) throw new Error(`game-stories: unknown profession "${professionId}"`);
2991
+ const attributes = Object.fromEntries(rules.attributes.map((a) => [a.id, options.attributes?.[a.id] ?? rules.pointBuy.start]));
2992
+ const traits = [...options.traits ?? []];
2993
+ for (const id of traits) if (!rules.traits.some((t) => t.id === id)) throw new Error(`game-stories: unknown trait "${id}"`);
2994
+ const skills = { ...profession.skills };
2995
+ for (const [id, level] of Object.entries(options.skills ?? {})) skills[id] = (skills[id] ?? 0) + level;
2996
+ const memories = [];
2997
+ const opinions = {};
2998
+ const history = [];
2999
+ const has = (id) => traits.includes(id);
3000
+ const traitPoints = () => traits.reduce((sum, id) => sum + rules.traits.find((t) => t.id === id).cost, 0);
3001
+ const spent = () => Object.values(attributes).reduce((sum, v) => sum + (v - rules.pointBuy.start), 0);
3002
+ const clampOpinion = (v) => Math.max(N.opinion.min, Math.min(N.opinion.max, v));
3003
+ return {
3004
+ profession: professionId,
3005
+ attributes,
3006
+ traits,
3007
+ skills,
3008
+ milestones: (skill) => rules.milestoneLevels.map((level, index) => (skills[skill] ?? 0) >= level ? index : -1).filter((index) => index >= 0),
3009
+ pool: () => rules.pointBuy.pool + profession.points + traitPoints() - spent(),
3010
+ valid() {
3011
+ const inRange = Object.values(attributes).every((v) => v >= rules.pointBuy.min && v <= rules.pointBuy.max);
3012
+ const excluded = traits.some((id) => rules.traits.find((t) => t.id === id).excludes.some(has));
3013
+ return inRange && !excluded && this.pool() >= 0;
3014
+ },
3015
+ derived() {
3016
+ const out = {};
3017
+ for (const rule of rules.derived) {
3018
+ let value = rule.base;
3019
+ for (const [attribute, factor] of Object.entries(rule.per)) value += factor * attributes[attribute];
3020
+ for (const t of rule.traits) if (has(t.trait) && t.add !== void 0) value += t.add;
3021
+ for (const t of rule.traits) if (has(t.trait) && t.mul !== void 0) value *= t.mul;
3022
+ out[rule.id] = rule.scale === void 0 ? value : value * rule.scale;
3023
+ }
3024
+ return out;
3025
+ },
3026
+ personality: options.personality ?? [50, 50, 50, 50, 50],
3027
+ memories,
3028
+ opinions,
3029
+ history,
3030
+ record(entry) {
3031
+ history.push(entry);
3032
+ if (entry.weight === void 0) return;
3033
+ memories.push({ id: entry.id, weight: entry.weight, day: entry.day, ...entry.about ? { about: entry.about } : {} });
3034
+ while (memories.length > N.memories.max) {
3035
+ let drop = 0;
3036
+ for (let i = 1; i < memories.length; i++) if (Math.abs(memories[i].weight) < Math.abs(memories[drop].weight)) drop = i;
3037
+ memories.splice(drop, 1);
3038
+ }
3039
+ },
3040
+ remembers: (id) => history.some((entry) => entry.id === id),
3041
+ opine: (about, delta) => opinions[about] = clampOpinion((opinions[about] ?? 0) + delta),
3042
+ decay(days) {
3043
+ for (const [about, value] of Object.entries(opinions)) {
3044
+ const step = N.opinion.decayPerDay * days;
3045
+ opinions[about] = value > 0 ? Math.max(0, value - step) : Math.min(0, value + step);
3046
+ }
3047
+ },
3048
+ stance(about) {
3049
+ const value = opinions[about] ?? 0;
3050
+ return value >= N.opinion.joins ? "joins" : value < N.opinion.hostile ? "hostile" : "neutral";
3051
+ }
3052
+ };
3053
+ }
3054
+ function drawSheet(random, input = {}) {
3055
+ const rules = input.ruleset ?? SHEET_RULESET;
3056
+ const profession = input.profession && rules.professions.some((p) => p.id === input.profession) ? input.profession : rules.professions[Math.floor(random() * rules.professions.length)].id;
3057
+ const sheet = createSheet({ ...input, profession });
3058
+ const attributes = sheet.attributes;
3059
+ let left = sheet.pool();
3060
+ while (left > 0) {
3061
+ const open = rules.attributes.filter((a) => attributes[a.id] < rules.pointBuy.max);
3062
+ if (open.length === 0) break;
3063
+ attributes[open[Math.floor(random() * open.length)].id] += 1;
3064
+ left -= 1;
3065
+ }
3066
+ return sheet;
3067
+ }
3068
+
3069
+ // ../game-contracts/dist/index.js
3070
+ var MISSING_BITS = {
3071
+ head: 1,
3072
+ armL: 2,
3073
+ armR: 4,
3074
+ legL: 8,
3075
+ legR: 16,
3076
+ armLBroken: 32,
3077
+ armRBroken: 64,
3078
+ legLBroken: 128,
3079
+ legRBroken: 256,
3080
+ jaw: 512
3081
+ };
3082
+
3083
+ // ../game-characters/dist/index.js
3084
+ var __defProp2 = Object.defineProperty;
3085
+ var __export2 = (target, all) => {
3086
+ for (var name in all)
3087
+ __defProp2(target, name, { get: all[name], enumerable: true });
3088
+ };
3089
+ var body_default = {
3090
+ guide: {
3091
+ heightMetres: 1.8,
3092
+ shoulderWidthMetres: 0.61,
3093
+ shoulderWidthWomanMetres: 0.57,
3094
+ shoulderHeightMetres: 1.475,
3095
+ headToHeight: 0.1667
3096
+ },
3097
+ teamGame: {
3098
+ source: "G 65336 J (shoulders +-0.24 m)",
3099
+ joints: {
3100
+ quadril: [
3101
+ 0,
3102
+ 0.95,
3103
+ 0
3104
+ ],
3105
+ pescoco: [
3106
+ 0,
3107
+ 1.52,
3108
+ 0
3109
+ ],
3110
+ ombroE: [
3111
+ -0.24,
3112
+ 1.44,
3113
+ 0
3114
+ ],
3115
+ ombroD: [
3116
+ 0.24,
3117
+ 1.44,
3118
+ 0
3119
+ ],
3120
+ cotoveloE: [
3121
+ -0.27,
3122
+ 1.13,
3123
+ 0
3124
+ ],
3125
+ cotoveloD: [
3126
+ 0.27,
3127
+ 1.13,
3128
+ 0
3129
+ ],
3130
+ coxaE: [
3131
+ -0.1,
3132
+ 0.92,
3133
+ 0
3134
+ ],
3135
+ coxaD: [
3136
+ 0.1,
3137
+ 0.92,
3138
+ 0
3139
+ ],
3140
+ joelhoE: [
3141
+ -0.1,
3142
+ 0.5,
3143
+ 0
3144
+ ],
3145
+ joelhoD: [
3146
+ 0.1,
3147
+ 0.5,
3148
+ 0
3149
+ ]
3150
+ },
3151
+ heightMetres: 1.8
3152
+ },
3153
+ lodDistancesMetres: [
3154
+ 18,
3155
+ 55
3156
+ ],
3157
+ triangleBudget: {
3158
+ lod0: 2500,
3159
+ lod1: 800,
3160
+ lod2: 250
3161
+ }
3162
+ };
3163
+ var SEGMENT_RADIUS = {
3164
+ head: 0.1,
3165
+ jaw: 0.05,
3166
+ neck: 0.06,
3167
+ torso: 0.17,
3168
+ pelvis: 0.16,
3169
+ upperArm: 0.085,
3170
+ forearm: 0.065,
3171
+ hand: 0.05,
3172
+ thigh: 0.1,
3173
+ shin: 0.08,
3174
+ foot: 0.05
3175
+ };
3176
+ var { guide } = body_default;
3177
+ var shoulderX = guide.shoulderWidthMetres / 2;
3178
+ var shoulderY = guide.shoulderHeightMetres;
3179
+ var GUIDE = {
3180
+ id: "guide",
3181
+ heightMetres: guide.heightMetres,
3182
+ joints: {
3183
+ hip: [0, 0.92, 0],
3184
+ spine: [0, 1.05, 0],
3185
+ neck: [0, 1.56, 0],
3186
+ head: [0, 1.64, 0],
3187
+ jaw: [0, 1.66, 0.06],
3188
+ shoulderL: [shoulderX, shoulderY, 0],
3189
+ shoulderR: [-shoulderX, shoulderY, 0],
3190
+ elbowL: [shoulderX, shoulderY - 0.31, 0],
3191
+ elbowR: [-shoulderX, shoulderY - 0.31, 0],
3192
+ wristL: [shoulderX, shoulderY - 0.61, 0],
3193
+ wristR: [-shoulderX, shoulderY - 0.61, 0],
3194
+ hipL: [0.105, 0.9, 0],
3195
+ hipR: [-0.105, 0.9, 0],
3196
+ kneeL: [0.105, 0.48, 0],
3197
+ kneeR: [-0.105, 0.48, 0],
3198
+ ankleL: [0.105, 0.1, 0],
3199
+ ankleR: [-0.105, 0.1, 0]
3200
+ },
3201
+ segmentRadius: SEGMENT_RADIUS
3202
+ };
3203
+ var J = body_default.teamGame.joints;
3204
+ var below = (point, drop) => [point[0], point[1] - drop, point[2]];
3205
+ var TEAM_GAME = {
3206
+ id: "team-game",
3207
+ heightMetres: body_default.teamGame.heightMetres,
3208
+ joints: {
3209
+ hip: J.quadril,
3210
+ spine: J.quadril,
3211
+ neck: J.pescoco,
3212
+ head: J.pescoco,
3213
+ jaw: [0, J.pescoco[1] + 0.08, 0.06],
3214
+ shoulderL: J.ombroE,
3215
+ shoulderR: J.ombroD,
3216
+ elbowL: J.cotoveloE,
3217
+ elbowR: J.cotoveloD,
3218
+ wristL: below(J.cotoveloE, 0.3),
3219
+ wristR: below(J.cotoveloD, 0.3),
3220
+ hipL: J.coxaE,
3221
+ hipR: J.coxaD,
3222
+ kneeL: J.joelhoE,
3223
+ kneeR: J.joelhoD,
3224
+ ankleL: below(J.joelhoE, 0.42),
3225
+ ankleR: below(J.joelhoD, 0.42)
3226
+ },
3227
+ segmentRadius: SEGMENT_RADIUS
3228
+ };
3229
+ var team_game_default = {
3230
+ id: "team-game",
3231
+ segments: {
3232
+ head: 40,
3233
+ arm: 60,
3234
+ leg: 80
3235
+ },
3236
+ calibers: {
3237
+ "7_62x51": {
3238
+ gameKey: "308",
3239
+ name: "7,62\xD751 (.308)",
3240
+ points: 75,
3241
+ penetration: 4,
3242
+ effectiveRangeMetres: 600,
3243
+ noiseMetres: 130,
3244
+ skullRange: 1,
3245
+ class: "rifle"
3246
+ },
3247
+ "9x19": {
3248
+ gameKey: "9x19",
3249
+ name: "9\xD719 mm",
3250
+ points: 35,
3251
+ penetration: 1.2,
3252
+ effectiveRangeMetres: 35,
3253
+ noiseMetres: 60,
3254
+ skullRange: 1,
3255
+ class: "pistol"
3256
+ },
3257
+ "12ga_00buck": {
3258
+ gameKey: "12ga",
3259
+ name: "12 ga 00",
3260
+ points: 12,
3261
+ pellets: 9,
3262
+ penetration: 0.5,
3263
+ effectiveRangeMetres: 15,
3264
+ noiseMetres: 90,
3265
+ skullRange: 8,
3266
+ class: "pistol"
3267
+ },
3268
+ "7_62x39": {
3269
+ gameKey: "762x39",
3270
+ name: "7,62\xD739 mm",
3271
+ points: 55,
3272
+ penetration: 3,
3273
+ effectiveRangeMetres: 250,
3274
+ noiseMetres: 110,
3275
+ skullRange: 1,
3276
+ class: "rifle"
3277
+ }
3278
+ },
3279
+ regionCost: {
3280
+ head: 0.7,
3281
+ torso: 1,
3282
+ bruteTorso: 2,
3283
+ limb: 0.5
3284
+ },
3285
+ damageKeptPerBody: 0.75,
3286
+ falloff: {
3287
+ fullAtRange: 1,
3288
+ atTwiceRange: 0.5
3289
+ },
3290
+ helmetAbsorbs: "pistol",
3291
+ regions: [
3292
+ "head",
3293
+ "torso",
3294
+ "armL",
3295
+ "armR",
3296
+ "legL",
3297
+ "legR"
3298
+ ]
3299
+ };
3300
+ var REGION_HIT_POINTS = {
3301
+ head: team_game_default.segments.head,
3302
+ armL: team_game_default.segments.arm,
3303
+ armR: team_game_default.segments.arm,
3304
+ legL: team_game_default.segments.leg,
3305
+ legR: team_game_default.segments.leg
3306
+ };
3307
+ var segments_default = {
3308
+ segments: [
3309
+ {
3310
+ id: "head",
3311
+ region: "head",
3312
+ hitPoints: 40,
3313
+ multiplier: 3,
3314
+ bleed: 40
3315
+ },
3316
+ {
3317
+ id: "jaw",
3318
+ region: "head",
3319
+ hitPoints: 12,
3320
+ multiplier: 2,
3321
+ bleed: 8
3322
+ },
3323
+ {
3324
+ id: "neck",
3325
+ region: "head",
3326
+ hitPoints: 25,
3327
+ multiplier: 2,
3328
+ bleed: 46
3329
+ },
3330
+ {
3331
+ id: "torso",
3332
+ region: "torso",
3333
+ hitPoints: 150,
3334
+ multiplier: 1,
3335
+ bleed: 10
3336
+ },
3337
+ {
3338
+ id: "pelvis",
3339
+ region: "torso",
3340
+ hitPoints: 90,
3341
+ multiplier: 1,
3342
+ bleed: 12
3343
+ },
3344
+ {
3345
+ id: "upperArm",
3346
+ region: "arm",
3347
+ hitPoints: 45,
3348
+ multiplier: 0.6,
3349
+ bleed: 15
3350
+ },
3351
+ {
3352
+ id: "forearm",
3353
+ region: "arm",
3354
+ hitPoints: 30,
3355
+ multiplier: 0.6,
3356
+ bleed: 8
3357
+ },
3358
+ {
3359
+ id: "hand",
3360
+ region: "arm",
3361
+ hitPoints: 12,
3362
+ multiplier: 0.6,
3363
+ bleed: 3
3364
+ },
3365
+ {
3366
+ id: "thigh",
3367
+ region: "leg",
3368
+ hitPoints: 55,
3369
+ multiplier: 0.7,
3370
+ bleed: 19
3371
+ },
3372
+ {
3373
+ id: "shin",
3374
+ region: "leg",
3375
+ hitPoints: 40,
3376
+ multiplier: 0.7,
3377
+ bleed: 9
3378
+ },
3379
+ {
3380
+ id: "foot",
3381
+ region: "leg",
3382
+ hitPoints: 15,
3383
+ multiplier: 0.7,
3384
+ bleed: 3
3385
+ }
3386
+ ],
3387
+ regions: {
3388
+ head: {
3389
+ hitPoints: 40,
3390
+ segmentSum: 77,
3391
+ scale: 0.5195
3392
+ },
3393
+ arm: {
3394
+ hitPoints: 60,
3395
+ segmentSum: 87,
3396
+ scale: 0.6897
3397
+ },
3398
+ leg: {
3399
+ hitPoints: 80,
3400
+ segmentSum: 110,
3401
+ scale: 0.7273
3402
+ }
3403
+ },
3404
+ arsenalWeapons: {
3405
+ pistola: {
3406
+ n: "Pistola 9\xD719",
3407
+ dmg: 35,
3408
+ pel: 1
3409
+ },
3410
+ fuzil: {
3411
+ n: "Fuzil 7,62\xD739",
3412
+ dmg: 55,
3413
+ pel: 1
3414
+ },
3415
+ escopeta: {
3416
+ n: "Escopeta 12 ga",
3417
+ dmg: 12,
3418
+ pel: 9
3419
+ },
3420
+ cinquenta: {
3421
+ n: ".50 BMG",
3422
+ dmg: 180,
3423
+ pel: 1
3424
+ },
3425
+ granada: {
3426
+ n: "Granada",
3427
+ dmg: 300,
3428
+ pel: 0
3429
+ }
3430
+ },
3431
+ elenco: {
3432
+ segments: {
3433
+ head: {
3434
+ hp: 40,
3435
+ radius: 0.07
3436
+ },
3437
+ torso: {
3438
+ hp: 120
3439
+ },
3440
+ pelvis: {
3441
+ hp: 90
3442
+ },
3443
+ upperArm: {
3444
+ hp: 60,
3445
+ radius: 0.085
3446
+ },
3447
+ forearm: {
3448
+ hp: 45,
3449
+ radius: 0.065
3450
+ },
3451
+ hand: {
3452
+ hp: 25,
3453
+ radius: 0.05
3454
+ },
3455
+ thigh: {
3456
+ hp: 80,
3457
+ radius: 0.1
3458
+ },
3459
+ shin: {
3460
+ hp: 55,
3461
+ radius: 0.08
3462
+ }
3463
+ },
3464
+ below: {
3465
+ upperArm: [
3466
+ "forearm",
3467
+ "hand"
3468
+ ],
3469
+ forearm: [
3470
+ "hand"
3471
+ ],
3472
+ thigh: [
3473
+ "shin"
3474
+ ]
3475
+ },
3476
+ falls: {
3477
+ upperArm: "o bra\xE7o inteiro",
3478
+ forearm: "o antebra\xE7o com a m\xE3o",
3479
+ hand: "a m\xE3o",
3480
+ thigh: "a perna inteira",
3481
+ shin: "a canela com o p\xE9",
3482
+ head: "a cabe\xE7a"
3483
+ },
3484
+ cutWeapons: [
3485
+ {
3486
+ id: "faca",
3487
+ n: "Faca",
3488
+ dmg: 22,
3489
+ cut: 0.7
3490
+ },
3491
+ {
3492
+ id: "facao",
3493
+ n: "Fac\xE3o",
3494
+ dmg: 55,
3495
+ cut: 1
3496
+ },
3497
+ {
3498
+ id: "machado",
3499
+ n: "Machado",
3500
+ dmg: 85,
3501
+ cut: 1
3502
+ },
3503
+ {
3504
+ id: "p9",
3505
+ n: "Pistola 9 mm",
3506
+ dmg: 30,
3507
+ cut: 0.15
3508
+ },
3509
+ {
3510
+ id: "e12",
3511
+ n: "Espingarda 12",
3512
+ dmg: 95,
3513
+ cut: 0.75
3514
+ },
3515
+ {
3516
+ id: "f762",
3517
+ n: "Fuzil 7,62",
3518
+ dmg: 65,
3519
+ cut: 0.45
3520
+ }
3521
+ ]
3522
+ },
3523
+ severity: {
3524
+ severAtCut: 0.5,
3525
+ severAtDamageFraction: 0.9,
3526
+ destroyAtDamageMultiple: 2,
3527
+ limpAtHpFraction: 0.5,
3528
+ splitAtDamage: 150,
3529
+ headKillDamage: 20,
3530
+ jawChance: 0.3,
3531
+ jawDamage: 25,
3532
+ headBitDamage: 60,
3533
+ headBitShotgunMetres: 5
3534
+ },
3535
+ defaultCut: {
3536
+ slash: 0.85,
3537
+ blunt: 0,
3538
+ explosive: 1,
3539
+ ballistic: {
3540
+ pistol: 0.15,
3541
+ buckshot: 0.75,
3542
+ rifle: 0.45
3543
+ }
3544
+ },
3545
+ deathRule: {
3546
+ default: "vitality-or-head",
3547
+ options: [
3548
+ "vitality-or-head",
3549
+ "head-only"
3550
+ ]
3551
+ }
3552
+ };
3553
+ var limb = (kind, side, joint, parent, slots) => {
3554
+ const isArm2 = kind === "upperArm" || kind === "forearm" || kind === "hand";
3555
+ const region = isArm2 ? side === "L" ? "armL" : "armR" : side === "L" ? "legL" : "legR";
3556
+ return { id: `${kind}${side}`, kind, side, joint, region, slots, ...parent ? { parent } : {} };
3557
+ };
3558
+ var sideOf = (side) => [
3559
+ limb("upperArm", side, `shoulder${side}`, "torso", ["arms"]),
3560
+ limb("forearm", side, `elbow${side}`, `upperArm${side}`, ["arms"]),
3561
+ limb("hand", side, `wrist${side}`, `forearm${side}`, ["hands"]),
3562
+ limb("thigh", side, `hip${side}`, "pelvis", ["legs"]),
3563
+ limb("shin", side, `knee${side}`, `thigh${side}`, ["legs"]),
3564
+ limb("foot", side, `ankle${side}`, `shin${side}`, ["feet"])
3565
+ ];
3566
+ var SEGMENT_TREE = [
3567
+ { id: "pelvis", kind: "pelvis", joint: "hip", region: "torso", slots: ["torso"] },
3568
+ { id: "torso", kind: "torso", joint: "spine", parent: "pelvis", region: "torso", slots: ["torso"] },
3569
+ { id: "neck", kind: "neck", joint: "neck", parent: "torso", region: "head", slots: [] },
3570
+ { id: "head", kind: "head", joint: "head", parent: "neck", region: "head", slots: ["head", "eyes"] },
3571
+ { id: "jaw", kind: "jaw", joint: "jaw", parent: "head", region: "head", slots: [] },
3572
+ ...sideOf("L"),
3573
+ ...sideOf("R")
3574
+ ];
3575
+ var SEGMENT_IDS = SEGMENT_TREE.map((node) => node.id);
3576
+ var NODES = new Map(SEGMENT_TREE.map((node) => [node.id, node]));
3577
+ var SEGMENT_NUMBERS = Object.fromEntries(
3578
+ segments_default.segments.map((entry) => [entry.id, { hitPoints: entry.hitPoints, multiplier: entry.multiplier, bleed: entry.bleed }])
3579
+ );
3580
+ var LIMB_MASK = [
3581
+ { ids: ["upperArmL", "forearmL"], lost: MISSING_BITS.armL, broken: MISSING_BITS.armLBroken },
3582
+ { ids: ["upperArmR", "forearmR"], lost: MISSING_BITS.armR, broken: MISSING_BITS.armRBroken },
3583
+ { ids: ["thighL", "shinL"], lost: MISSING_BITS.legL, broken: MISSING_BITS.legLBroken },
3584
+ { ids: ["thighR", "shinR"], lost: MISSING_BITS.legR, broken: MISSING_BITS.legRBroken }
3585
+ ];
3586
+ var physical_default = {
3587
+ id: "physical",
3588
+ tissuePerCentimetre: {
3589
+ flesh: 0.25,
3590
+ bone: 1
3591
+ },
3592
+ torsoPath: {
3593
+ fleshCentimetres: 22,
3594
+ boneCentimetres: 2
3595
+ },
3596
+ regionFactor: {
3597
+ head: 0.7,
3598
+ limb: 0.5,
3599
+ torso: 1
3600
+ },
3601
+ bruteTorsoFactor: 2,
3602
+ energyKept: "P / (P + R)",
3603
+ points: {
3604
+ coefficient: 3.5,
3605
+ exponent: 0.37
3606
+ },
3607
+ tolerance: {
3608
+ bodies: 0,
3609
+ energyKept: 0.05
3610
+ },
3611
+ calibration: [
3612
+ {
3613
+ caliber: "9x19",
3614
+ force: 7.67,
3615
+ bodies: 2,
3616
+ energyKeptFirstBody: 0.51,
3617
+ expectedBodies: 2,
3618
+ bestiaryEnergyKept: 0.5
3619
+ },
3620
+ {
3621
+ caliber: "5_56x45",
3622
+ force: 16.56,
3623
+ bodies: 3,
3624
+ energyKeptFirstBody: 0.69,
3625
+ expectedBodies: 3,
3626
+ bestiaryEnergyKept: 0.65
3627
+ },
3628
+ {
3629
+ caliber: "7_62x51",
3630
+ force: 26.67,
3631
+ bodies: 4,
3632
+ energyKeptFirstBody: 0.78,
3633
+ expectedBodies: 4,
3634
+ bestiaryEnergyKept: 0.75
3635
+ },
3636
+ {
3637
+ caliber: "7_62x39",
3638
+ force: 18.52,
3639
+ bodies: 3,
3640
+ energyKeptFirstBody: 0.71,
3641
+ expectedBodies: 3
3642
+ },
3643
+ {
3644
+ caliber: "12ga_00buck",
3645
+ force: 4.35,
3646
+ bodies: 1,
3647
+ energyKeptFirstBody: 0.37,
3648
+ expectedBodies: 1
3649
+ }
3650
+ ],
3651
+ torsoResistance: 7.5
3652
+ };
3653
+ var { severity } = segments_default;
3654
+ var DEFAULT_DAMAGE_RULES = {
3655
+ deathRule: "vitality-or-head",
3656
+ segmentRule: "kit",
3657
+ severity: { ...severity, stunAtPoints: 35, limbVitalityShare: 0.3, regionLimbVitalityShare: 0.5 },
3658
+ blast: { minimumPoints: 8, headDestroyPoints: 40, bigPoints: 120 },
3659
+ defaultCut: segments_default.defaultCut,
3660
+ points: physical_default.points,
3661
+ spread: 0,
3662
+ segments: SEGMENT_NUMBERS,
3663
+ burnPointsPerSecond: 3
3664
+ };
3665
+ var ARSENAL_RULES = { ...DEFAULT_DAMAGE_RULES, segmentRule: "arsenal" };
3666
+ var ELENCO_HIT_POINTS = segments_default.elenco.segments;
3667
+ var ELENCO_RULES = {
3668
+ ...DEFAULT_DAMAGE_RULES,
3669
+ segmentRule: "elenco",
3670
+ spread: 0.15,
3671
+ segments: Object.fromEntries(
3672
+ Object.entries(SEGMENT_NUMBERS).map(([kind, numbers]) => [kind, { ...numbers, multiplier: 1, hitPoints: ELENCO_HIT_POINTS[kind]?.hp ?? numbers.hitPoints }])
3673
+ )
3674
+ };
3675
+ var CLASS_OF = { pistol: "pistol", rifle: "rifle" };
3676
+ var TEAM_GAME_CALIBERS = Object.entries(team_game_default.calibers).map(([id, entry]) => ({
3677
+ id,
3678
+ points: entry.points,
3679
+ penetration: entry.penetration,
3680
+ class: CLASS_OF[entry.class] ?? "pistol",
3681
+ skullRange: entry.skullRange,
3682
+ pellets: "pellets" in entry ? entry.pellets : 1,
3683
+ effectiveRangeMetres: entry.effectiveRangeMetres
3684
+ }));
3685
+ var TEAM_GAME_CALIBER_KEYS = Object.fromEntries(Object.entries(team_game_default.calibers).map(([id, entry]) => [entry.gameKey, id]));
3686
+ var TEAM_GAME_RULES = { regionCost: team_game_default.regionCost, damageKeptPerBody: team_game_default.damageKeptPerBody, falloff: team_game_default.falloff };
3687
+ var PHYSICAL_RULES = physical_default;
3688
+ var { tissuePerCentimetre, torsoPath, regionFactor } = PHYSICAL_RULES;
3689
+ var TORSO_RESISTANCE = torsoPath.fleshCentimetres * tissuePerCentimetre.flesh + torsoPath.boneCentimetres * tissuePerCentimetre.bone;
3690
+ var HEAD_KINDS = /* @__PURE__ */ new Set(["head", "jaw", "neck"]);
3691
+ var TORSO_KINDS = /* @__PURE__ */ new Set(["torso", "pelvis"]);
3692
+ var segmentResistance = ({ kind, bodyCount, protection }) => {
3693
+ if (TORSO_KINDS.has(kind)) return TORSO_RESISTANCE * regionFactor.torso * bodyCount + protection;
3694
+ if (HEAD_KINDS.has(kind)) return TORSO_RESISTANCE * regionFactor.head + protection;
3695
+ return TORSO_RESISTANCE * regionFactor.limb + protection;
3696
+ };
3697
+ var createRng = (seed) => {
3698
+ let state = seed >>> 0;
3699
+ return () => {
3700
+ state = state + 1831565813 >>> 0;
3701
+ let mixed = Math.imul(state ^ state >>> 15, state | 1);
3702
+ mixed ^= mixed + Math.imul(mixed ^ mixed >>> 7, mixed | 61);
3703
+ return ((mixed ^ mixed >>> 14) >>> 0) / 4294967296;
3704
+ };
3705
+ };
3706
+ var LIMB_BITS = {
3707
+ armL: { severed: MISSING_BITS.armL, broken: MISSING_BITS.armLBroken },
3708
+ armR: { severed: MISSING_BITS.armR, broken: MISSING_BITS.armRBroken },
3709
+ legL: { severed: MISSING_BITS.legL, broken: MISSING_BITS.legLBroken },
3710
+ legR: { severed: MISSING_BITS.legR, broken: MISSING_BITS.legRBroken }
3711
+ };
3712
+ var HEAD_RESISTANCE = segmentResistance({ kind: "head", bodyCount: 1, protection: 0 });
3713
+ var gait_default = {
3714
+ cascade: {
3715
+ hip: 16,
3716
+ spine: 11,
3717
+ shoulder: 9,
3718
+ elbow: 7,
3719
+ leg: 14,
3720
+ knee: 11,
3721
+ head: 6
3722
+ },
3723
+ moves: {
3724
+ idle: {
3725
+ gait: 0,
3726
+ freq: 0,
3727
+ amp: 0,
3728
+ knee: 0,
3729
+ bob: 0,
3730
+ lean: 0,
3731
+ arm: 0,
3732
+ elb: 0.12,
3733
+ crouch: 0,
3734
+ aim: 0,
3735
+ turn: 0,
3736
+ grip: 0.35
3737
+ },
3738
+ walk: {
3739
+ gait: 1,
3740
+ freq: 5.6,
3741
+ amp: 0.42,
3742
+ knee: 0.85,
3743
+ bob: 0.035,
3744
+ lean: 0.05,
3745
+ arm: 0.42,
3746
+ elb: 0.3,
3747
+ crouch: 0,
3748
+ aim: 0,
3749
+ turn: 0,
3750
+ grip: 0.4
3751
+ },
3752
+ run: {
3753
+ gait: 1,
3754
+ freq: 9.2,
3755
+ amp: 0.72,
3756
+ knee: 1.5,
3757
+ bob: 0.06,
3758
+ lean: 0.3,
3759
+ arm: 0.85,
3760
+ elb: 1.35,
3761
+ crouch: 0,
3762
+ aim: 0,
3763
+ turn: 0,
3764
+ grip: 1.1
3765
+ },
3766
+ crouch: {
3767
+ gait: 1,
3768
+ freq: 3.8,
3769
+ amp: 0.3,
3770
+ knee: 0.6,
3771
+ bob: 0.02,
3772
+ lean: 0.42,
3773
+ arm: 0.15,
3774
+ elb: 0.7,
3775
+ crouch: 1,
3776
+ aim: 0,
3777
+ turn: 0,
3778
+ grip: 0.8
3779
+ },
3780
+ aim: {
3781
+ gait: 0,
3782
+ freq: 0,
3783
+ amp: 0,
3784
+ knee: 0,
3785
+ bob: 0,
3786
+ lean: 0.06,
3787
+ arm: 0,
3788
+ elb: 0.2,
3789
+ crouch: 0.25,
3790
+ aim: 1,
3791
+ turn: 0,
3792
+ grip: 1.2
3793
+ },
3794
+ turn: {
3795
+ gait: 0.5,
3796
+ freq: 4.2,
3797
+ amp: 0.14,
3798
+ knee: 0.7,
3799
+ bob: 0.012,
3800
+ lean: 0.03,
3801
+ arm: 0.12,
3802
+ elb: 0.25,
3803
+ crouch: 0,
3804
+ aim: 0,
3805
+ turn: 1,
3806
+ grip: 0.4
3807
+ }
3808
+ },
3809
+ poses: [
3810
+ {
3811
+ index: 0,
3812
+ id: "walk",
3813
+ name: {
3814
+ "pt-BR": "anda"
3815
+ }
3816
+ },
3817
+ {
3818
+ index: 1,
3819
+ id: "crawl",
3820
+ name: {
3821
+ "pt-BR": "rasteja"
3822
+ }
3823
+ },
3824
+ {
3825
+ index: 2,
3826
+ id: "fallen",
3827
+ name: {
3828
+ "pt-BR": "ca\xEDdo"
3829
+ }
3830
+ },
3831
+ {
3832
+ index: 3,
3833
+ id: "attack",
3834
+ name: {
3835
+ "pt-BR": "ataca"
3836
+ }
3837
+ },
3838
+ {
3839
+ index: 4,
3840
+ id: "scream",
3841
+ name: {
3842
+ "pt-BR": "grito"
3843
+ }
3844
+ },
3845
+ {
3846
+ index: 5,
3847
+ id: "crouch",
3848
+ name: {
3849
+ "pt-BR": "agachado"
3850
+ }
3851
+ },
3852
+ {
3853
+ index: 6,
3854
+ id: "leap",
3855
+ name: {
3856
+ "pt-BR": "salto"
3857
+ }
3858
+ },
3859
+ {
3860
+ index: 7,
3861
+ id: "eat",
3862
+ name: {
3863
+ "pt-BR": "comendo"
3864
+ }
3865
+ }
3866
+ ],
3867
+ walk: {
3868
+ cadenceBase: 0.5,
3869
+ cadencePerSpeed: 0.3,
3870
+ stopBelowSpeed: 0.05,
3871
+ maxSine: 0.9,
3872
+ strideFactor: 4,
3873
+ legLengthFactor: 0.9
3874
+ }
3875
+ };
3876
+ var WALK = gait_default.walk;
3877
+ var CASCADE = gait_default.cascade;
3878
+ var TWO_PI = Math.PI * 2;
3879
+ var MOVES = gait_default.moves;
3880
+ var appearance_default = {
3881
+ variance: {
3882
+ height: {
3883
+ gaussianSpread: 0.09,
3884
+ min: 0.9,
3885
+ max: 1.1
3886
+ },
3887
+ buildBands: [
3888
+ {
3889
+ below: 0.3,
3890
+ base: 0.85,
3891
+ range: 0.1
3892
+ },
3893
+ {
3894
+ below: 0.8,
3895
+ base: 0.95,
3896
+ range: 0.13
3897
+ },
3898
+ {
3899
+ base: 1.08,
3900
+ range: 0.17
3901
+ }
3902
+ ],
3903
+ buildSpeed: {
3904
+ lean: 1.05,
3905
+ heavy: 0.9
3906
+ },
3907
+ buildHealth: {
3908
+ lean: 0.9,
3909
+ heavy: 1.15
3910
+ },
3911
+ speedJitter: {
3912
+ base: 0.92,
3913
+ range: 0.16
3914
+ },
3915
+ wanderJitter: {
3916
+ base: 0.8,
3917
+ range: 0.4
3918
+ },
3919
+ runnerAgeClamp: 1,
3920
+ variantCount: {
3921
+ walker: 4,
3922
+ runner: 3,
3923
+ crawler: 2,
3924
+ brute: 2
3925
+ },
3926
+ variantSeedBase: 7e3
3927
+ },
3928
+ civilianColors: [
3929
+ "6D5A4A",
3930
+ "4D5D6B",
3931
+ "7A3B33",
3932
+ "5B6B4A",
3933
+ "8A7A5A",
3934
+ "3E4A5C",
3935
+ "6A4A6A",
3936
+ "9A8A70",
3937
+ "40403A"
3938
+ ],
3939
+ corpses: {
3940
+ max: 400,
3941
+ lifeSeconds: 900
3942
+ }
3943
+ };
3944
+ var { variance } = appearance_default;
3945
+ var palette_exports = {};
3946
+ __export2(palette_exports, {
3947
+ BAG_COLORS: () => BAG_COLORS,
3948
+ BLOOD_COLORS: () => BLOOD_COLORS,
3949
+ CAP_COLORS: () => CAP_COLORS,
3950
+ DECAY_SKIN: () => DECAY_SKIN,
3951
+ GRAY: () => GRAY,
3952
+ HAIR_COLORS: () => HAIR_COLORS,
3953
+ HAIR_STYLES_FEMALE: () => HAIR_STYLES_FEMALE,
3954
+ HAIR_STYLES_MALE: () => HAIR_STYLES_MALE,
3955
+ OLD_HAIR: () => OLD_HAIR,
3956
+ PANTS_COLORS: () => PANTS_COLORS,
3957
+ RED: () => RED,
3958
+ SHOE_COLORS: () => SHOE_COLORS,
3959
+ SKIN_DECAY_BLEND: () => SKIN_DECAY_BLEND,
3960
+ SKIN_TONES: () => SKIN_TONES,
3961
+ SLEEVES: () => SLEEVES,
3962
+ TOP_COLORS: () => TOP_COLORS,
3963
+ UNIFORM_PANTS: () => UNIFORM_PANTS,
3964
+ UNIFORM_SHIRTS: () => UNIFORM_SHIRTS,
3965
+ UNIFORM_SHOES: () => UNIFORM_SHOES
3966
+ });
3967
+ var SKIN_TONES = [15845797, 14724224, 13012066, 11039050, 9067066, 6175526];
3968
+ var TOP_COLORS = [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162, 12104354, 2894896, 10111530, 3104099, 13216074, 8007242, 14210248, 3820138];
3969
+ var PANTS_COLORS = [2896704, 2040360, 4866096, 5922662, 3820138, 6969920, 2767402];
3970
+ var SLEEVES = ["short", "short", "long", "tank"];
3971
+ var HAIR_STYLES_FEMALE = ["long", "long", "bun", "short", "afro", "bald"];
3972
+ var HAIR_STYLES_MALE = ["short", "short", "buzz", "bald", "afro", "long"];
3973
+ var CAP_COLORS = [9321275, 2894896, 3820138, 13216074];
3974
+ var BAG_COLORS = [3820090, 5909028, 2896704];
3975
+ var HAIR_COLORS = [1709072, 2760214, 3877920, 6965802, 11569744, 8006170];
3976
+ var SHOE_COLORS = [1908514, 3811866, 12104872, 4856346];
3977
+ var DECAY_SKIN = [13482920, 9740420, 8090698, 9206878];
3978
+ var UNIFORM_SHIRTS = [4878230, 9321275, 6113850, 5208643, 7040888, 4153146, 5917294, 9071162];
3979
+ var UNIFORM_PANTS = 2896704;
3980
+ var UNIFORM_SHOES = 1908514;
3981
+ var SKIN_DECAY_BLEND = [0.45, 0.65, 0.8, 0.88];
3982
+ var GRAY = 4868678;
3983
+ var RED = 8000012;
3984
+ var BLOOD_COLORS = [10361618, 7214096, 4860440, 1840658];
3985
+ var OLD_HAIR = 9078656;
3986
+
3987
+ // src/world.ts
3988
+ var hashId = (text) => {
3989
+ let hash = 2166136261;
3990
+ for (let i = 0; i < text.length; i++) hash = Math.imul(hash ^ text.charCodeAt(i), 16777619);
3991
+ return hash >>> 0;
3992
+ };
3993
+ var lifeTextOf = (locale, patch) => {
3994
+ const table = life_text_default[locale] ?? life_text_default["pt-BR"];
3995
+ if (!patch) return table;
3996
+ const out = { ...table };
3997
+ for (const [key, value] of Object.entries(patch)) out[key] = Array.isArray(value) ? value : { ...table[key], ...value };
3998
+ return out;
3999
+ };
4000
+ var mergeNumbers = (base, patch) => {
4001
+ if (!patch) return base;
4002
+ const out = { ...base };
4003
+ for (const [key, value] of Object.entries(patch)) {
4004
+ out[key] = value !== null && typeof value === "object" && !Array.isArray(value) ? mergeNumbers(base[key], value) : value;
4005
+ }
4006
+ return out;
4007
+ };
4008
+ var lifeNumbersOf = (patch) => {
4009
+ const { sources: _sources, ...defaults } = life_default;
4010
+ void _sources;
4011
+ return mergeNumbers(defaults, patch);
4012
+ };
4013
+ function createStoryWorld(options = {}) {
4014
+ const seed = options.seed ?? 1;
4015
+ const locale = options.locale ?? "pt-BR";
4016
+ const names = options.names ?? getNamePools(locale);
4017
+ const stories = options.stories ?? [];
4018
+ const castRandom = createRng((seed ^ 2654435769) >>> 0);
4019
+ const casting = castStories({ stories, ...options.people ? { people: options.people } : {}, names, random: castRandom, ...options.life?.nomadBase ? { defaultBase: options.life.nomadBase } : {} });
4020
+ const given = Object.fromEntries(casting.people.map((person2) => [person2.id, person2]));
4021
+ const numbers = lifeNumbersOf(options.life?.numbers);
4022
+ const text = lifeTextOf(locale, options.life?.text);
4023
+ const states = {};
4024
+ for (const person2 of casting.people) states[person2.id] = { state: person2.start?.state ?? "ok", note: person2.start?.note ?? "", base: person2.base };
4025
+ const flags = {};
4026
+ const actions = { ...options.actions };
4027
+ const chronicle = createChronicle();
4028
+ const cast = (role) => casting.roles[role] ?? role;
4029
+ const beats = stories.flatMap((story) => story.beats).map((beat, order) => ({ beat, order })).sort((a, b) => a.beat.day - b.beat.day || a.order - b.order).map(({ beat }) => beat);
4030
+ const beatDays = /* @__PURE__ */ new Map();
4031
+ for (const beat of beats) for (const role of beat.roles) (beatDays.get(cast(role)) ?? beatDays.set(cast(role), []).get(cast(role))).push(beat.day);
4032
+ const random = options.random ?? createRng(seed);
4033
+ const life = options.life ? createLifeWorld(options.life, numbers) : null;
4034
+ const sheets = /* @__PURE__ */ new Map();
4035
+ const castList = casting.people.filter((person2) => !person2.extra).map((person2) => person2.id);
4036
+ const person = (id) => given[id] ?? life?.made[id];
4037
+ const nameOf = (id) => {
4038
+ const p = person(id);
4039
+ return p ? p.nick ?? shortName(p.name, text.honorifics) : id;
4040
+ };
4041
+ const sheet = (id) => {
4042
+ let made = sheets.get(id);
4043
+ if (!made) {
4044
+ const p = person(id);
4045
+ made = drawSheet(createRng((seed ^ hashId(id)) >>> 0), { ...p?.profession ? { profession: p.profession } : {}, ...p?.personality ? { personality: p.personality } : {} });
4046
+ sheets.set(id, made);
4047
+ }
4048
+ return made;
4049
+ };
4050
+ const living = numbers.livingStates;
4051
+ const playerBase = options.playerBase ?? options.life?.playerBase ?? "player";
4052
+ let day = 0;
4053
+ let next = 0;
4054
+ if (life && options.fill) fillWorld(life, { states, random, names, numbers, options: options.life }, options.fill);
4055
+ const log = (entry) => {
4056
+ chronicle.add(entry);
4057
+ for (const id of entry.people) if (person(id)) sheet(id).record({ kind: entry.kind === "story" ? "story" : "event", id: entry.id, day: entry.day });
4058
+ };
4059
+ const world = {
4060
+ states,
4061
+ flags,
4062
+ actions,
4063
+ casting,
4064
+ chronicle,
4065
+ life,
4066
+ get day() {
4067
+ return day;
4068
+ },
4069
+ person,
4070
+ people: () => [...casting.people, ...Object.values(life?.made ?? {})],
4071
+ nameOf,
4072
+ cast,
4073
+ act(id) {
4074
+ actions[id] = 1;
4075
+ },
4076
+ advance() {
4077
+ day += 1;
4078
+ const d = day;
4079
+ while (next < beats.length && beats[next].day === d) {
4080
+ const beat = beats[next++];
4081
+ const outcome = runBeat(beat, { states, flags, actions, cast, living, castList, nameOf, remembers: (id, entry) => sheet(id).remembers(entry), ...options.codeBeats ? { codeBeats: options.codeBeats } : {} });
4082
+ if (!outcome) continue;
4083
+ applyChanges(states, outcome.changes, playerBase);
4084
+ Object.assign(flags, outcome.flags ?? {});
4085
+ log({ day: d, kind: "story", id: beat.id, title: beat.title, text: outcome.text, ...outcome.alt ? { alt: true } : {}, changes: outcome.changes ?? [], people: beat.roles.map(cast) });
4086
+ }
4087
+ while (next < beats.length && beats[next].day < d) next++;
4088
+ if (life) lifeDay(d, life, { states, flags, actions, people: given, beatDays: (id) => beatDays.get(id) ?? [], random, names, numbers, text, options: options.life, log });
4089
+ },
4090
+ runTo(target) {
4091
+ while (day < target) world.advance();
4092
+ },
4093
+ sheet
4094
+ };
4095
+ return world;
4096
+ }
4097
+ function simulateStories(days, options = {}) {
4098
+ const world = createStoryWorld(options);
4099
+ world.runTo(days);
4100
+ return world;
4101
+ }
4102
+
4103
+ // src/bench/example.ts
4104
+ var content = (t) => ({
4105
+ example: true,
4106
+ days: 30,
4107
+ actions: [
4108
+ { id: "welcome", name: t.welcome },
4109
+ { id: "fix_well", name: t.fixWell }
4110
+ ],
4111
+ stories: [
4112
+ {
4113
+ id: "stranger",
4114
+ example: true,
4115
+ roles: [
4116
+ { id: "host", needs: { age: [30, 70], base: "town" }, create: true },
4117
+ { id: "newcomer", create: true, bond: { role: "host", relation: t.cousin, sameSurname: false, sameBase: true } }
4118
+ ],
4119
+ beats: [
4120
+ { id: "stranger_arrives", day: 3, title: t.stranger, roles: ["newcomer", "host"], variants: [{ text: [{ name: "newcomer" }, t.arrives[0], { name: "host" }, t.arrives[1]] }] },
4121
+ {
4122
+ id: "stranger_stays",
4123
+ day: 7,
4124
+ title: t.stay,
4125
+ roles: ["newcomer", "host"],
4126
+ variants: [
4127
+ { when: { action: "welcome" }, alt: true, text: [{ name: "newcomer" }, t.staysText[0]], changes: [{ role: "newcomer", state: "ok", note: t.stays }] },
4128
+ { text: [{ name: "newcomer" }, t.goesText[0]], changes: [{ role: "newcomer", state: "moved", note: t.goes, base: "nomad" }] }
4129
+ ]
4130
+ }
4131
+ ]
4132
+ },
4133
+ {
4134
+ id: "well",
4135
+ example: true,
4136
+ crosses: ["stranger"],
4137
+ roles: [{ id: "keeper", needs: { base: "farm" }, create: true }],
4138
+ beats: [
4139
+ { id: "well_dry", day: 10, title: t.well, roles: ["keeper"], variants: [{ text: [{ name: "keeper" }, t.dryText[0]], flags: { well_dry: 1 } }] },
4140
+ {
4141
+ id: "well_after",
4142
+ day: 14,
4143
+ title: t.wellDry,
4144
+ roles: ["keeper", "newcomer"],
4145
+ variants: [
4146
+ { when: { all: [{ action: "fix_well" }, { living: "newcomer" }, { state: "newcomer", is: "ok" }] }, alt: true, text: [{ name: "newcomer" }, t.fixedText[0], { name: "keeper" }, t.fixedText[1]], changes: [] },
4147
+ { text: [{ name: "keeper" }, t.carriedText[0]], changes: [{ role: "keeper", state: "hurt", note: t.carried }] }
4148
+ ]
4149
+ }
4150
+ ]
4151
+ }
4152
+ ],
4153
+ life: {
4154
+ groups: { farm: { name: t.farm, defence: 0.5, food: 20 }, town: { name: t.town, defence: 0.4, food: 10 }, nomad: { name: t.nomads, defence: 0, food: 0 } },
4155
+ nomadBase: "nomad"
4156
+ },
4157
+ fill: 14
4158
+ });
4159
+ var STORIES_EXAMPLE = {
4160
+ "pt-BR": content({
4161
+ farm: "S\xEDtio (exemplo)",
4162
+ town: "Vila (exemplo)",
4163
+ nomads: "Estrada",
4164
+ stranger: "Um estranho no port\xE3o",
4165
+ stay: "Fica ou vai",
4166
+ stays: "ficou",
4167
+ goes: "seguiu pela estrada",
4168
+ well: "O po\xE7o secou",
4169
+ wellDry: "Depois do po\xE7o",
4170
+ fixed: "consertou o po\xE7o",
4171
+ carried: "carregou \xE1gua de longe",
4172
+ welcome: "Acolher o estranho",
4173
+ fixWell: "Consertar o po\xE7o",
4174
+ cousin: "primo",
4175
+ arrives: [" bate no port\xE3o de ", " pedindo abrigo por uma noite.", ""],
4176
+ staysText: [" fica e passa a ajudar na horta.", ""],
4177
+ goesText: [" junta as coisas e segue pela estrada.", ""],
4178
+ dryText: [" acorda e o balde volta vazio do po\xE7o.", ""],
4179
+ fixedText: [" desce no po\xE7o com uma corda e destrava a bomba. ", " agradece com um prato de comida.", ""],
4180
+ carriedText: [" passa a semana carregando \xE1gua do rio e machuca as costas.", ""]
4181
+ }),
4182
+ en: content({
4183
+ farm: "Farm (example)",
4184
+ town: "Town (example)",
4185
+ nomads: "Road",
4186
+ stranger: "A stranger at the gate",
4187
+ stay: "Stay or go",
4188
+ stays: "stayed",
4189
+ goes: "went down the road",
4190
+ well: "The well ran dry",
4191
+ wellDry: "After the well",
4192
+ fixed: "fixed the well",
4193
+ carried: "carried water from afar",
4194
+ welcome: "Welcome the stranger",
4195
+ fixWell: "Fix the well",
4196
+ cousin: "cousin",
4197
+ arrives: [" knocks at the gate of ", " asking for a night of shelter.", ""],
4198
+ staysText: [" stays and starts helping in the garden.", ""],
4199
+ goesText: [" packs up and goes down the road.", ""],
4200
+ dryText: [" wakes up and the bucket comes back empty from the well.", ""],
4201
+ fixedText: [" goes down the well on a rope and frees the pump. ", " says thanks with a plate of food.", ""],
4202
+ carriedText: [" spends the week carrying water from the river and hurts their back.", ""]
4203
+ })
4204
+ };
4205
+
4206
+ // src/bench/locale.ts
4207
+ var EN = {
4208
+ group: "Stories",
4209
+ name: "Stories area",
4210
+ controls: { seed: "Seed", day: "Day", life: "Life simulation", infection: "Bites and the turned", person: "Sheet of", next_day: "Next day", reset: "Start again" },
4211
+ hints: {
4212
+ seed: "The world drawn again: who plays each role, their names and every chance of the life simulation.",
4213
+ day: "The world runs to this day; the sheet and the chronicle show it.",
4214
+ life: "Off, only the stories happen; on, people also get hurt, fall in love, have children, fight, betray, trade and arrive.",
4215
+ infection: "The apocalypse layer of the life simulation: a run can end in a bite, and a bite turns in two days. Off by default: a normal world.",
4216
+ person: "Whose sheet the panel shows.",
4217
+ action: "A player action: the stories read it from its day on."
4218
+ },
4219
+ metrics: { day: "Day", people: "People", living: "Living", dead: "Dead", births: "Births", arrivals: "Arrivals", lines: "Chronicle lines", last: "Last line" },
4220
+ scenarios: {
4221
+ determinism: "The same seed, the same chronicle",
4222
+ casting: "Another seed casts other names",
4223
+ normal_world: "A normal world: nobody is bitten",
4224
+ sheet_keeps: "The sheet keeps the stories the person lived",
4225
+ point_buy: "Point buy: 14 points, the profession and traits",
4226
+ crossing: "An action in one story changes another"
4227
+ },
4228
+ sheet: { kicker: "Character sheet", state: "State", base: "Lives at", age: "Age", profession: "Profession", pool: "Points left", lived: "Lived", none: "Nothing yet", day: "Day" },
4229
+ states: { ok: "well", hurt: "hurt", bitten: "bitten", undead: "turned", dead: "dead", missing: "missing", party: "with the player", hostile: "hostile", moved: "moved", left: "left", unborn: "not born yet" },
4230
+ derived: { health: "Max health", stamina: "Stamina", carry: "Max load (kg)", run_speed: "Running speed (m/s)", detection: "Detection radius (m)", noise: "Footstep noise (%)", learning: "Learning speed (%)", composure: "Composure", melee: "Melee damage (%)" }
4231
+ };
4232
+ var PT_BR = {
4233
+ group: "Hist\xF3rias",
4234
+ name: "\xC1rea das hist\xF3rias",
4235
+ controls: { seed: "Semente", day: "Dia", life: "Vida sorteada", infection: "Mordidas e reanimados", person: "Ficha de", next_day: "Pr\xF3ximo dia", reset: "Recome\xE7ar" },
4236
+ hints: {
4237
+ seed: "O mundo sorteado de novo: quem faz cada papel, os nomes e cada chance da vida sorteada.",
4238
+ day: "O mundo corre at\xE9 esse dia; a ficha e a cr\xF4nica mostram.",
4239
+ life: "Desligada, s\xF3 as hist\xF3rias acontecem; ligada, as pessoas tamb\xE9m se ferem, se apaixonam, t\xEAm filhos, brigam, traem, trocam e chegam.",
4240
+ infection: "A camada do apocalipse na vida sorteada: uma sa\xEDda pode acabar em mordida, e a mordida reanima em dois dias. Desligada por padr\xE3o: mundo normal.",
4241
+ person: "De quem \xE9 a ficha no painel.",
4242
+ action: "Uma a\xE7\xE3o do jogador: as hist\xF3rias leem a partir do dia dela."
4243
+ },
4244
+ metrics: { day: "Dia", people: "Pessoas", living: "Vivos", dead: "Mortos", births: "Nascimentos", arrivals: "Chegadas", lines: "Linhas da cr\xF4nica", last: "\xDAltima linha" },
4245
+ scenarios: {
4246
+ determinism: "A mesma semente, a mesma cr\xF4nica",
4247
+ casting: "Outra semente escala outros nomes",
4248
+ normal_world: "Mundo normal: ningu\xE9m \xE9 mordido",
4249
+ sheet_keeps: "A ficha guarda as hist\xF3rias que a pessoa viveu",
4250
+ point_buy: "Compra de pontos: 14 pontos, a profiss\xE3o e os tra\xE7os",
4251
+ crossing: "Uma a\xE7\xE3o numa hist\xF3ria muda outra"
4252
+ },
4253
+ sheet: { kicker: "Ficha do personagem", state: "Estado", base: "Mora em", age: "Idade", profession: "Profiss\xE3o", pool: "Pontos livres", lived: "Viveu", none: "Nada ainda", day: "Dia" },
4254
+ states: { ok: "vivo", hurt: "ferido", bitten: "mordido", undead: "zumbi nomeado", dead: "morto", missing: "sumido", party: "com o jogador", hostile: "hostil", moved: "mudou de base", left: "saiu do vale", unborn: "ainda n\xE3o nasceu" },
4255
+ derived: { health: "Vida m\xE1xima", stamina: "F\xF4lego", carry: "Carga m\xE1xima (kg)", run_speed: "Velocidade de corrida (m/s)", detection: "Raio de detec\xE7\xE3o (m)", noise: "Ru\xEDdo dos passos (%)", learning: "Velocidade de aprendizado (%)", composure: "Compostura", melee: "Dano corpo a corpo (%)" }
4256
+ };
4257
+ var TEXT = { en: EN, "pt-BR": PT_BR };
4258
+ var storiesBenchTextOf = (locale) => TEXT[locale] ?? PT_BR;
4259
+
4260
+ // src/bench/module.ts
4261
+ var STORIES_AREA_ID = "stories_area";
4262
+ var MAX_DAYS = 365;
4263
+ var actionControl = (id) => `act_${id.toLowerCase().replace(/[^a-z0-9]+/g, "_")}`;
4264
+ function createStoriesTestModule(options = {}) {
4265
+ const locale = options.locale ?? "pt-BR";
4266
+ const text = storiesBenchTextOf(locale);
4267
+ let content2 = options.content ?? STORIES_EXAMPLE[locale === "en" ? "en" : "pt-BR"];
4268
+ const settings = { seed: options.seed ?? 1, day: Math.min(30, content2.days ?? 90), life: true, infection: false, person: "" };
4269
+ let actions = {};
4270
+ let host = null;
4271
+ const worldOptions = (seed, withLife, infection, acts) => {
4272
+ const life = withLife && content2.life ? { ...content2.life, numbers: { ...content2.life.numbers, infection } } : void 0;
4273
+ return {
4274
+ stories: content2.stories,
4275
+ ...content2.people ? { people: content2.people } : {},
4276
+ seed,
4277
+ ...life ? { life } : {},
4278
+ ...life && content2.fill ? { fill: content2.fill } : {},
4279
+ ...content2.playerBase ? { playerBase: content2.playerBase } : {},
4280
+ actions: acts,
4281
+ locale
4282
+ };
4283
+ };
4284
+ const run = (seed, day, withLife = settings.life, infection = settings.infection, acts = actions) => {
4285
+ const made = createStoryWorld(worldOptions(seed, withLife, infection, acts));
4286
+ made.runTo(day);
4287
+ return made;
4288
+ };
4289
+ let world = run(settings.seed, settings.day);
4290
+ const personOptions = () => world.people().filter((p) => !p.extra || world.states[p.id]?.state !== "unborn").map((p) => ({ id: p.id, name: p.name }));
4291
+ const groupName = (base) => content2.life?.groups[base]?.name ?? base;
4292
+ const showSheet = () => {
4293
+ if (!host?.bench?.sheet) return;
4294
+ const id = settings.person && world.person(settings.person) ? settings.person : personOptions()[0]?.id ?? "";
4295
+ const person = world.person(id);
4296
+ if (!person) {
4297
+ host.bench.sheet(null);
4298
+ return;
4299
+ }
4300
+ const sheet = world.sheet(id);
4301
+ const derived = sheet.derived();
4302
+ const state = world.states[id];
4303
+ const lived = world.chronicle.about(id);
4304
+ const card = {
4305
+ kicker: `${text.sheet.kicker} \xB7 ${text.sheet.day} ${world.day}`,
4306
+ title: person.name,
4307
+ ...state?.note ? { body: state.note } : {},
4308
+ facts: [
4309
+ { label: text.sheet.state, value: state ? text.states[state.state] : "" },
4310
+ { label: text.sheet.base, value: state ? groupName(state.base) : "" },
4311
+ ...person.age !== void 0 ? [{ label: text.sheet.age, value: String(person.age) }] : [],
4312
+ { label: text.sheet.profession, value: getProfession(sheet.profession)?.name[locale === "en" ? "en" : "pt-BR"] ?? sheet.profession },
4313
+ { label: text.sheet.pool, value: String(sheet.pool()) },
4314
+ ...Object.entries(derived).map(([key, value]) => ({ label: text.derived[key] ?? key, value: String(Math.round(value * 100) / 100) }))
4315
+ ],
4316
+ steps: lived.length > 0 ? lived.slice(-8).map((entry) => ({ at: `${text.sheet.day} ${entry.day}`, text: `${entry.title}: ${entry.text}` })) : [{ at: "", text: text.sheet.none }]
4317
+ };
4318
+ host.bench.sheet(card);
4319
+ };
4320
+ const rebuild = () => {
4321
+ world = run(settings.seed, settings.day);
4322
+ host?.bench?.patch("person", { options: personOptions(), ...settings.person ? { value: settings.person } : {} });
4323
+ showSheet();
4324
+ };
4325
+ const onChange = (change) => {
4326
+ if (change.kind === "action") {
4327
+ if (change.id === "next_day") {
4328
+ settings.day = Math.min(MAX_DAYS, settings.day + 1);
4329
+ host?.bench?.patch("day", { value: settings.day });
4330
+ } else if (change.id === "reset") actions = {};
4331
+ rebuild();
4332
+ return;
4333
+ }
4334
+ if (change.kind !== "control") return;
4335
+ const { id, value } = change;
4336
+ if (id === "seed" && typeof value === "number") settings.seed = value;
4337
+ else if (id === "day" && typeof value === "number") settings.day = value;
4338
+ else if (id === "life" && typeof value === "boolean") settings.life = value;
4339
+ else if (id === "infection" && typeof value === "boolean") settings.infection = value;
4340
+ else if (id === "person" && typeof value === "string") {
4341
+ settings.person = value;
4342
+ showSheet();
4343
+ return;
4344
+ } else {
4345
+ const act = (content2.actions ?? []).find((a) => actionControl(a.id) === id);
4346
+ if (!act || typeof value !== "boolean") return;
4347
+ if (value) actions = { ...actions, [act.id]: 1 };
4348
+ else {
4349
+ const { [act.id]: _off, ...rest } = actions;
4350
+ void _off;
4351
+ actions = rest;
4352
+ }
4353
+ }
4354
+ rebuild();
4355
+ };
4356
+ const controls = () => {
4357
+ const people = personOptions();
4358
+ return [
4359
+ { kind: "number", id: "seed", name: text.controls.seed, hint: text.hints.seed, min: 1, max: 9999, step: 1, default: settings.seed },
4360
+ { kind: "number", id: "day", name: text.controls.day, hint: text.hints.day, min: 0, max: MAX_DAYS, step: 1, default: settings.day },
4361
+ { kind: "boolean", id: "life", name: text.controls.life, hint: text.hints.life, default: settings.life },
4362
+ { kind: "boolean", id: "infection", name: text.controls.infection, hint: text.hints.infection, default: settings.infection },
4363
+ { kind: "choice", id: "person", name: text.controls.person, hint: text.hints.person, options: people.length > 0 ? people : [{ id: "", name: "-" }], default: people[0]?.id ?? "" },
4364
+ ...(content2.actions ?? []).map((a) => ({ kind: "boolean", id: actionControl(a.id), name: a.name, hint: text.hints.action, default: false })),
4365
+ { kind: "action", id: "next_day", name: text.controls.next_day },
4366
+ { kind: "action", id: "reset", name: text.controls.reset }
4367
+ ];
4368
+ };
4369
+ const count = (states) => Object.values(world.states).filter((s) => states.includes(s.state)).length;
4370
+ const metrics = () => [
4371
+ { id: "current_day", name: text.metrics.day, digits: 0, read: () => world.day },
4372
+ { id: "people", name: text.metrics.people, digits: 0, read: () => world.people().length },
4373
+ { id: "living", name: text.metrics.living, digits: 0, read: () => count(["ok", "hurt", "party", "hostile", "moved"]) },
4374
+ { id: "dead", name: text.metrics.dead, digits: 0, read: () => count(["dead"]) },
4375
+ { id: "births", name: text.metrics.births, digits: 0, read: () => world.life?.stats.births ?? 0 },
4376
+ { id: "arrivals", name: text.metrics.arrivals, digits: 0, read: () => world.life?.stats.arrivals ?? 0 },
4377
+ { id: "lines", name: text.metrics.lines, digits: 0, read: () => world.chronicle.entries.length },
4378
+ { id: "last", name: text.metrics.last, read: () => world.chronicle.entries.at(-1)?.title ?? "" }
4379
+ ];
4380
+ const days = () => content2.days ?? 90;
4381
+ const lines = (made) => made.chronicle.entries.map((e) => `${e.day}|${e.id}|${e.text}`).join("\n");
4382
+ const scenarios = () => [
4383
+ {
4384
+ id: "stories_determinism",
4385
+ name: text.scenarios.determinism,
4386
+ run: ({ expect }) => {
4387
+ const a = lines(run(7, days(), true, false, {}));
4388
+ expect(a === lines(run(7, days(), true, false, {})) && a.length > 0, "the same seed gives the same chronicle");
4389
+ }
4390
+ },
4391
+ {
4392
+ id: "stories_casting",
4393
+ name: text.scenarios.casting,
4394
+ run: ({ expect }) => {
4395
+ const names = (seed) => run(seed, 0, false, false, {}).people().map((p) => p.name).join(",");
4396
+ const drawn = new Set([1, 2, 3, 4, 5].map(names));
4397
+ expect(drawn.size > 1, "five seeds do not all cast the same names", { casts: drawn.size });
4398
+ }
4399
+ },
4400
+ {
4401
+ id: "stories_normal_world",
4402
+ name: text.scenarios.normal_world,
4403
+ run: ({ expect }) => {
4404
+ let bites = 0;
4405
+ for (let seed = 1; seed <= 10; seed++) bites += run(seed, days(), true, false, {}).chronicle.entries.filter((e) => e.kind === "life" && e.changes.some((c) => c.state === "bitten" || c.state === "undead")).length;
4406
+ expect(bites === 0, "ten worlds without the infection layer: no bite, nobody turns", { bites });
4407
+ }
4408
+ },
4409
+ {
4410
+ id: "stories_sheet_keeps",
4411
+ name: text.scenarios.sheet_keeps,
4412
+ run: ({ expect }) => {
4413
+ const made = run(3, days(), false, false, {});
4414
+ const entry = made.chronicle.entries.find((e) => e.kind === "story");
4415
+ const id = entry?.people[0];
4416
+ expect(Boolean(entry && id && made.sheet(id).remembers(entry.id)), "whoever a beat is about keeps it in their sheet", { beat: entry?.id, person: id });
4417
+ }
4418
+ },
4419
+ {
4420
+ id: "stories_point_buy",
4421
+ name: text.scenarios.point_buy,
4422
+ run: ({ expect }) => {
4423
+ const nurse = createSheet({ profession: "nurse" });
4424
+ expect(nurse.pool() === 12, "a nurse starts with 14 \u2212 2 = 12 points", { pool: nurse.pool() });
4425
+ const spent = createSheet({ profession: "nurse", attributes: { agility: 8 }, traits: ["athlete", "smoker"] });
4426
+ expect(spent.pool() === 6 && spent.valid(), "agility 8 (\u22125), athlete (\u22124), smoker (+3): 6 left", { pool: spent.pool() });
4427
+ const clash = createSheet({ traits: ["athlete", "asthmatic"] });
4428
+ expect(!clash.valid(), "athlete and asthmatic exclude each other");
4429
+ }
4430
+ },
4431
+ {
4432
+ id: "stories_crossing",
4433
+ name: text.scenarios.crossing,
4434
+ run: ({ expect }) => {
4435
+ const differ = (content2.actions ?? []).some((a) => lines(run(5, days(), false, false, {})) !== lines(run(5, days(), false, false, { [a.id]: 1 })));
4436
+ expect(differ, "some player action changes the chronicle");
4437
+ }
4438
+ }
4439
+ ];
4440
+ const module = {
4441
+ id: STORIES_AREA_ID,
4442
+ name: text.name,
4443
+ group: text.group,
4444
+ get world() {
4445
+ return world;
4446
+ },
4447
+ load(next) {
4448
+ content2 = next;
4449
+ actions = {};
4450
+ settings.person = "";
4451
+ rebuild();
4452
+ },
4453
+ items: () => [],
4454
+ controls: () => controls(),
4455
+ metrics: () => metrics(),
4456
+ scenarios: () => scenarios(),
4457
+ attach(lent) {
4458
+ host = lent;
4459
+ const off = lent.bench?.onChange(onChange);
4460
+ showSheet();
4461
+ return () => {
4462
+ off?.();
4463
+ host?.bench?.sheet?.(null);
4464
+ host = null;
4465
+ };
4466
+ }
4467
+ };
4468
+ return module;
4469
+ }
4470
+
4471
+ // src/index.ts
4472
+ var LIFE_DEFAULTS = life_default;
4473
+ var LIFE_TEXT = life_text_default;
4474
+ var SHEET_DEFAULTS = sheet_default;
4475
+ return __toCommonJS(index_exports);
4476
+ })();
4477
+ //# sourceMappingURL=index.global.js.map