Audit every remaining sheet and dialog; add the Culland Hall scene
Opened all of it this time, rather than driving the system through its API.
Two more defects of the same family as the NPC sheet, and in the same place —
the types added last:
- Talent and Art had no TYPES.Item.* label, so their sheets printed the raw
key "TYPES.Item.talent" under the name.
- Neither had a branch in item-sheet.hbs or an isTalent/isPower flag, so the
Detail tab rendered EMPTY over a document holding category, rule, uses per
case, Coherence cost, push cost and origin. Eleven of thirteen types were
handled, which is exactly the shape an eye skips.
Both now have a header statline and a full Detail section, and a guard walks
template.json: every declared type must have a label, a flag set in ringbrp.mjs,
and an {{#if isX}} section in the sheet.
One hardening fix: loseCoherence applies the cost and THEN announces it, so
anything that throws in between leaves the actor quietly poorer with no message.
localize() throws on a non-string, which an unrecognised reason key is.
Everything else came back clean, and it is worth recording what was checked so
the next audit knows what "checked" meant: 13 item types opened, all render; the
expedition sheet (4 tabs); all 7 chat cards driven through their real entry
points — roll, coherence, reaction via Combat.rollAll, hit location, weapon
damage, major wound, and contact through a case file that actually has a group;
all 3 dialogs opened by clicking the controls a player clicks. No errors, no raw
localisation keys anywhere.
Also: Culland Hall as a playable Scene. Drawn as SVG rather than generated,
because a room count, a door position and a label are all load-bearing here and
a generator cannot be trusted with any of them — three label-collision passes in
the browser before it was clean, which is the documented cost of doing it this
way. 2800x2000 at 100px to the square so the Foundry grid lands on the grid
drawn into the image; 5ft squares; no walls, because half-done walls are worse
than none and a GM who wants vision can add them in ten minutes.
Verified in Foundry: the scene imports, activates, and draws.
Version 1.0.2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
02ec47a351
commit
51bd8e618a
@@ -182,7 +182,7 @@ The build runs all three guards before it writes anything:
|
||||
```
|
||||
check-rules: OK — 87 rules, 21 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
|
||||
check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resolves, every posting can use what it carries, every loadout distinct
|
||||
check-lang: OK — en.json, 927 keys, no leaf/branch collisions
|
||||
check-behaviour: OK — 44 behavioural tests
|
||||
check-lang: OK — en.json, 945 keys, no leaf/branch collisions
|
||||
check-behaviour: OK — 45 behavioural tests
|
||||
```
|
||||
<!-- guards:end -->
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2800" height="2000" viewBox="0 0 2800 2000">
|
||||
<!-- CULLAND HALL — ground floor and grounds, for LAST ADMISSION.
|
||||
Drawn rather than generated: a generator cannot be trusted with a room count,
|
||||
a door position or a label, and all three are load-bearing here.
|
||||
2800x2000 at 100px to the square = 28 x 20 squares of 5ft. -->
|
||||
<defs>
|
||||
<style>
|
||||
.rm { fill:#16215f; stroke:#6674d8; stroke-width:6; }
|
||||
.rm2 { fill:#101a6e; stroke:#6674d8; stroke-width:6; }
|
||||
.out { fill:#0f1750; stroke:#4d5ac0; stroke-width:4; }
|
||||
.wall { stroke:#8f9ae6; stroke-width:10; stroke-linecap:square; }
|
||||
.door { stroke:#f2ea72; stroke-width:11; stroke-linecap:round; }
|
||||
.lbl { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
|
||||
font-size:34px; fill:#f2ea72; letter-spacing:2px; }
|
||||
.sub { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
|
||||
font-size:24px; fill:#a8b2e3; letter-spacing:1px; }
|
||||
.num { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
|
||||
font-size:44px; fill:#0c125b; }
|
||||
.ttl { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
|
||||
font-size:54px; fill:#ecefee; letter-spacing:5px; }
|
||||
.key { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-size:26px; fill:#ecefee; }
|
||||
.grid { stroke:#233073; stroke-width:1; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<rect width="2800" height="2000" fill="#0c125b"/>
|
||||
|
||||
<!-- one-square grid, so the Foundry grid lands on the drawn one -->
|
||||
<g class="grid">
|
||||
<path d="M100 0V2000M200 0V2000M300 0V2000M400 0V2000M500 0V2000M600 0V2000M700 0V2000M800 0V2000M900 0V2000M1000 0V2000M1100 0V2000M1200 0V2000M1300 0V2000M1400 0V2000M1500 0V2000M1600 0V2000M1700 0V2000M1800 0V2000M1900 0V2000M2000 0V2000M2100 0V2000M2200 0V2000M2300 0V2000M2400 0V2000M2500 0V2000M2600 0V2000M2700 0V2000"/>
|
||||
<path d="M0 100H2800M0 200H2800M0 300H2800M0 400H2800M0 500H2800M0 600H2800M0 700H2800M0 800H2800M0 900H2800M0 1000H2800M0 1100H2800M0 1200H2800M0 1300H2800M0 1400H2800M0 1500H2800M0 1600H2800M0 1700H2800M0 1800H2800M0 1900H2800"/>
|
||||
</g>
|
||||
|
||||
<text x="120" y="120" class="ttl">CULLAND HALL</text>
|
||||
<text x="120" y="160" class="sub">GROUND FLOOR · THE CULLAND HAUNTING · LAST ADMISSION 22:00</text>
|
||||
|
||||
<!-- ================= the house ================= -->
|
||||
<rect x="400" y="300" width="2000" height="1200" class="rm2"/>
|
||||
|
||||
<!-- 5 · Long Gallery -->
|
||||
<rect x="700" y="400" width="430" height="300" class="rm"/>
|
||||
<circle cx="760" cy="450" r="30" fill="#f2ea72"/><text x="746" y="465" class="num">5</text>
|
||||
<text x="730" y="560" class="lbl" font-size="30">LONG GALLERY</text>
|
||||
<text x="730" y="598" class="sub" font-size="21">strobe · mirrors · no cast at all</text>
|
||||
|
||||
<!-- 6 · Ballroom -->
|
||||
<rect x="700" y="720" width="430" height="360" class="rm"/>
|
||||
<circle cx="760" cy="770" r="30" fill="#f2ea72"/><text x="746" y="785" class="num">6</text>
|
||||
<text x="730" y="880" class="lbl" font-size="30">BALLROOM</text>
|
||||
<text x="730" y="918" class="sub" font-size="21">lights up · fire exit · "thanks for coming"</text>
|
||||
|
||||
<!-- 1 · Entrance Hall -->
|
||||
<rect x="1150" y="1100" width="500" height="400" class="rm"/>
|
||||
<circle cx="1190" cy="1140" r="30" fill="#f2ea72"/><text x="1176" y="1155" class="num">1</text>
|
||||
<text x="1188" y="1235" class="lbl" font-size="30">ENTRANCE HALL</text>
|
||||
<text x="1188" y="1275" class="sub" font-size="21">the WALL PANEL — four names</text>
|
||||
<rect x="1188" y="1300" width="200" height="26" fill="#f2ea72"/>
|
||||
<text x="1188" y="1372" class="sub" font-size="21">ticket desk · head count</text>
|
||||
|
||||
<!-- main stairs (Edith) -->
|
||||
<rect x="1660" y="1140" width="170" height="250" class="rm2"/>
|
||||
<g stroke="#6674d8" stroke-width="4">
|
||||
<path d="M1660 1180H1830M1660 1220H1830M1660 1260H1830M1660 1300H1830M1660 1340H1830"/>
|
||||
</g>
|
||||
<text x="1663" y="1168" class="sub" font-size="21">MAIN STAIRS</text>
|
||||
<circle cx="1745" cy="1260" r="26" fill="#ff7a5c"/>
|
||||
<path d="M1650 1265H1600" class="door"/>
|
||||
<text x="1870" y="1268" class="sub" fill="#ff7a5c">EDITH CRANE stands here</text>
|
||||
|
||||
<!-- 2 · The Ward, east corridor -->
|
||||
<rect x="1700" y="900" width="700" height="200" class="rm"/>
|
||||
<circle cx="1740" cy="940" r="30" fill="#f2ea72"/><text x="1726" y="955" class="num">2</text>
|
||||
<text x="1786" y="1005" class="lbl" font-size="30">THE WARD — EAST CORRIDOR</text>
|
||||
<text x="1786" y="1045" class="sub" font-size="21">Sister Enderby counts</text>
|
||||
<rect x="2320" y="905" width="16" height="190" fill="#ff7a5c"/>
|
||||
<text x="1990" y="1160" class="sub" fill="#ff7a5c" font-size="21">JOSH PARDOE is behind this wall →</text>
|
||||
|
||||
<!-- 3 · Nursery -->
|
||||
<rect x="1900" y="400" width="500" height="420" class="rm"/>
|
||||
<circle cx="1940" cy="440" r="30" fill="#f2ea72"/><text x="1926" y="455" class="num">3</text>
|
||||
<text x="1990" y="530" class="lbl">THE NURSERY</text>
|
||||
<text x="1990" y="570" class="sub">Sammy in the wall · hole at ankle height</text>
|
||||
<rect x="1905" y="700" width="60" height="24" fill="#f2ea72"/>
|
||||
|
||||
<!-- green room -->
|
||||
<rect x="450" y="400" width="220" height="300" class="rm"/>
|
||||
<text x="466" y="446" class="lbl" font-size="20">GREEN</text>
|
||||
<text x="466" y="472" class="lbl" font-size="20">ROOM</text>
|
||||
<text x="466" y="508" class="sub" font-size="19">call sheet</text>
|
||||
<text x="466" y="534" class="sub" font-size="19">costumes</text>
|
||||
<text x="466" y="560" class="sub" font-size="19">SET PLANS</text>
|
||||
<text x="466" y="586" class="sub" font-size="19">pinned up</text>
|
||||
|
||||
<!-- control room -->
|
||||
<rect x="450" y="720" width="220" height="180" class="rm"/>
|
||||
<text x="466" y="768" class="lbl" font-size="22">CONTROL</text>
|
||||
<text x="466" y="802" class="sub" font-size="19">Ferriby · comms</text>
|
||||
|
||||
<!-- kitchens -->
|
||||
<rect x="450" y="920" width="220" height="300" class="rm"/>
|
||||
<text x="466" y="966" class="lbl" font-size="22">KITCHENS</text>
|
||||
<text x="466" y="1000" class="sub" font-size="19">Sharrock's way</text>
|
||||
<text x="466" y="1026" class="sub" font-size="19">out — NOT on</text>
|
||||
<text x="466" y="1052" class="sub" font-size="19">the fire plan</text>
|
||||
|
||||
<!-- yard -->
|
||||
<rect x="200" y="980" width="230" height="240" class="out"/>
|
||||
<text x="220" y="1030" class="lbl" font-size="28">YARD</text>
|
||||
<text x="220" y="1070" class="sub">skip · taped hasp</text>
|
||||
|
||||
<!-- doors -->
|
||||
<g class="door">
|
||||
<path d="M1370 1500H1450"/> <!-- front door -->
|
||||
<path d="M1130 1180H1150"/> <!-- hall to ballroom side -->
|
||||
<path d="M915 1080V1100"/> <!-- ballroom to hall run -->
|
||||
<path d="M1650 1000H1700"/> <!-- hall to ward -->
|
||||
<path d="M2150 900V820"/> <!-- ward to nursery -->
|
||||
<path d="M670 560H700"/> <!-- green room to gallery -->
|
||||
<path d="M670 800H700"/> <!-- control to ballroom -->
|
||||
<path d="M430 1070H450"/> <!-- kitchens to yard -->
|
||||
<path d="M915 700V720"/> <!-- gallery to ballroom -->
|
||||
</g>
|
||||
|
||||
<!-- ================= grounds ================= -->
|
||||
<rect x="200" y="1560" width="2400" height="380" class="out"/>
|
||||
<text x="230" y="1610" class="sub">THE LAWN — ninety feet of it</text>
|
||||
|
||||
<!-- marquee -->
|
||||
<rect x="1150" y="1560" width="500" height="230" class="rm"/>
|
||||
<text x="1200" y="1650" class="lbl" font-size="30">MARQUEE</text>
|
||||
<text x="1200" y="1690" class="sub">queue · box office · muster point</text>
|
||||
|
||||
<!-- glasshouse -->
|
||||
<rect x="2000" y="1600" width="560" height="300" class="rm"/>
|
||||
<circle cx="2040" cy="1640" r="30" fill="#f2ea72"/><text x="2026" y="1655" class="num">4</text>
|
||||
<text x="2090" y="1720" class="lbl">THE GLASSHOUSE</text>
|
||||
<text x="2090" y="1760" class="sub">Old Marrow · the taped line</text>
|
||||
<path d="M2000 1830H2560" stroke="#f2ea72" stroke-width="6" stroke-dasharray="18 14"/>
|
||||
<text x="2090" y="1880" class="sub" fill="#f2ea72">he stops here — and tonight he does not</text>
|
||||
|
||||
<!-- car park -->
|
||||
<rect x="250" y="1700" width="620" height="220" class="out"/>
|
||||
<text x="280" y="1760" class="lbl" font-size="28">CAR PARK</text>
|
||||
<text x="280" y="1800" class="sub">the van · the tea urn · blue lights</text>
|
||||
|
||||
<!-- Marrow's ground: he will not come indoors -->
|
||||
<path d="M700 1545H2600" stroke="#ff7a5c" stroke-width="8" stroke-dasharray="26 18"/>
|
||||
<text x="700" y="1530" class="sub" fill="#ff7a5c">OLD MARROW WILL NOT CROSS THIS LINE — inside is a complete answer</text>
|
||||
|
||||
<!-- key -->
|
||||
<g transform="translate(2420,120)">
|
||||
<rect x="-20" y="-40" width="360" height="230" fill="#101a6e" stroke="#4d5ac0" stroke-width="4"/>
|
||||
<text x="0" y="0" class="key" fill="#f2ea72">KEY</text>
|
||||
<rect x="0" y="30" width="34" height="14" fill="#f2ea72"/><text x="50" y="44" class="key">door / opening</text>
|
||||
<circle cx="17" cy="85" r="14" fill="#ff7a5c"/><text x="50" y="94" class="key">a body, or a person</text>
|
||||
<path d="M0 135H34" stroke="#ff7a5c" stroke-width="7" stroke-dasharray="12 8"/>
|
||||
<text x="50" y="144" class="key">Marrow's limit</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
@@ -439,6 +439,7 @@ If there is time — and there usually is, because this ends early — she says
|
||||
| LA-ART-06 | The green room: mirrors, costumes, a clipboard on the door — used on the Act One page, where the call sheet is |
|
||||
| LA-ART-07 | Edith Crane on the stairs |
|
||||
| LA-ART-08 | The car park afterwards: a van, a tea urn, blue lights |
|
||||
| LA-MAP-01 | **Culland Hall floor plan** — drawn, not generated (`art/maps/culland-hall.svg`), and shipped as a playable **Scene** in the adventure: 2800×2000 at 100px to the square, 5ft grid, no walls (add them if you want line-of-sight). |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+19
-1
@@ -13,6 +13,8 @@
|
||||
"TYPES.Item.group": "Group",
|
||||
"TYPES.Item.vehicle": "Vehicle",
|
||||
"TYPES.Item.authority": "Authority",
|
||||
"TYPES.Item.talent": "Talent",
|
||||
"TYPES.Item.power": "Art",
|
||||
"RINGBRP.Tab.Access": "Access",
|
||||
"RINGBRP.New.authority": "New authority",
|
||||
"RINGBRP.Scope.nothing": "nothing at all",
|
||||
@@ -925,5 +927,21 @@
|
||||
"RINGBRP.Gen.RolledChar": "{ch}",
|
||||
"RINGBRP.Gen.TakeThem": "Keep these",
|
||||
"RINGBRP.Gen.Nth": "agent {n}",
|
||||
"RINGBRP.Dialog.Circumstance": "How are the circumstances?"
|
||||
"RINGBRP.Dialog.Circumstance": "How are the circumstances?",
|
||||
"RINGBRP.Item.TalentId": "Talent key",
|
||||
"RINGBRP.Item.TalentCategory": "Category",
|
||||
"RINGBRP.Item.TalentSummary": "In one line",
|
||||
"RINGBRP.Item.TalentRule": "What it does",
|
||||
"RINGBRP.Item.GrantsAction": "Grants action",
|
||||
"RINGBRP.Item.UsesPerCase": "Uses per case",
|
||||
"RINGBRP.Item.UsedThisCase": "Used this case",
|
||||
"RINGBRP.Item.SkillBonusFam": "Bonus to skill",
|
||||
"RINGBRP.Item.SkillBonusVal": "Bonus",
|
||||
"RINGBRP.Item.ArtId": "Art key",
|
||||
"RINGBRP.Item.ArtSummary": "In one line",
|
||||
"RINGBRP.Item.ArtRule": "What it does",
|
||||
"RINGBRP.Item.ArtRating": "Rating",
|
||||
"RINGBRP.Item.PushCost": "Push cost",
|
||||
"RINGBRP.Item.PushRule": "What pushing does",
|
||||
"RINGBRP.Item.Origin": "Where it came from"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.665018 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.231003 174603000 Delete type=3 #1
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.733783 172907000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.298648 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.666832 17391f000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.232848 174603000 Delete type=3 #1
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.717519 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.282484 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.701249 173113000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.266480 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.728905 173113000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.293373 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.686790 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.252536 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.725345 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.290196 172ddf000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.721830 17391f000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.286378 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.732314 17391f000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.297168 1735eb000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.659409 172907000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.224954 172ddf000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.727208 173113000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.291920 1735eb000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.711308 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.276268 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.705364 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.270666 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.662077 172907000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.227877 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.668653 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.234705 1735eb000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.663680 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.229633 174603000 Delete type=3 #1
|
||||
|
||||
+9
-1
@@ -1524,7 +1524,10 @@ export async function loseCoherence(actor, reasonKey, { amount = null, note = ""
|
||||
speaker: ChatMessage.getSpeaker({ actor }),
|
||||
content: await rbRenderTemplate("systems/ringbrp/templates/chat-coherence.hbs", {
|
||||
actorName: actor.name, before, after, cost,
|
||||
reason: game.i18n.localize(spec?.label ?? reasonKey), note,
|
||||
// The cost is already applied by this point, so anything that throws here loses
|
||||
// the announcement and leaves the actor quietly poorer. localize() throws on a
|
||||
// non-string, which an unrecognised reason will be.
|
||||
reason: game.i18n.localize(String(spec?.label ?? reasonKey ?? "")), note,
|
||||
band: bAfter, crossed: bBefore.id !== bAfter.id,
|
||||
pips: Array.from({ length: Number(actor.system.coherence?.max ?? 10) }, (_, i) => i < after)
|
||||
})
|
||||
@@ -5514,6 +5517,11 @@ class RingBRPItemSheet extends ItemSheet {
|
||||
ctx.isMaterial = this.item.type === "material";
|
||||
ctx.isVehicle = this.item.type === "vehicle";
|
||||
ctx.isAuthority = this.item.type === "authority";
|
||||
// Talents and Arts were added after this list and never joined it, so their sheets
|
||||
// rendered an empty Detail tab over real data — category, rule, uses and cost all
|
||||
// sat in the document with nothing on screen reading them.
|
||||
ctx.isTalent = this.item.type === "talent";
|
||||
ctx.isPower = this.item.type === "power";
|
||||
ctx.descriptionHTML = await rbEnrich(ctx.sys.description ?? "");
|
||||
|
||||
if (ctx.isGear) {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"id": "ringbrp",
|
||||
"title": "The Custodians",
|
||||
"description": "A Basic Roleplaying game of agents who cross the crossing to contain what should not be there — and who come back a little less from here each time.",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"compatibility": {
|
||||
"minimum": 13,
|
||||
"verified": "14.364"
|
||||
|
||||
@@ -171,6 +171,28 @@
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if isTalent}}
|
||||
<section class="rb-statline">
|
||||
<div class="rb-stat rb-stat-primary"><span class="rb-stat-label">{{localize "RINGBRP.Item.TalentCategory"}}</span>
|
||||
<span class="rb-stat-value rb-stat-small">{{sys.category}}</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Item.UsesPerCase"}}</span>
|
||||
<span class="rb-stat-value">{{#if sys.usesPerCase}}{{sys.usedThisCase}} / {{sys.usesPerCase}}{{else}}—{{/if}}</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Coh.Label"}}</span>
|
||||
<span class="rb-stat-value">{{#if sys.coherenceCost}}{{sys.coherenceCost}}{{else}}—{{/if}}</span></div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if isPower}}
|
||||
<section class="rb-statline">
|
||||
<div class="rb-stat rb-stat-primary"><span class="rb-stat-label">{{localize "RINGBRP.Item.ArtRating"}}</span>
|
||||
<span class="rb-stat-value">{{sys.value}}%</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Coh.Label"}}</span>
|
||||
<span class="rb-stat-value">{{sys.coherenceCost}}</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Item.PushCost"}}</span>
|
||||
<span class="rb-stat-value">{{sys.pushCost}}</span></div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<nav class="sheet-tabs rb-tabs" data-group="primary">
|
||||
<a class="item" data-tab="detail">{{localize "RINGBRP.Tab.Detail"}}</a>
|
||||
<a class="item" data-tab="desc">{{localize "RINGBRP.Tab.Description"}}</a>
|
||||
@@ -471,6 +493,42 @@
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Item.BlocksSensors"}}</h3>
|
||||
<p class="rb-tags">{{#each sys.blocksSensors as |t|}}<span class="rb-tag">{{t}}</span>{{else}}<span class="rb-dim">—</span>{{/each}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if isTalent}}
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Item.Identity"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Item.TalentId"}}<input type="text" name="system.talentId" value="{{sys.talentId}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.TalentCategory"}}<input type="text" name="system.category" value="{{sys.category}}"/></label>
|
||||
</div>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.TalentSummary"}}<input type="text" name="system.summary" value="{{sys.summary}}"/></label>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.TalentRule"}}<textarea name="system.rule" rows="3">{{sys.rule}}</textarea></label>
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Tab.Combat"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Item.UsesPerCase"}}<input type="number" name="system.usesPerCase" value="{{sys.usesPerCase}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.UsedThisCase"}}<input type="number" name="system.usedThisCase" value="{{sys.usedThisCase}}"/></label>
|
||||
<label>{{localize "RINGBRP.Coh.Label"}}<input type="number" name="system.coherenceCost" value="{{sys.coherenceCost}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.GrantsAction"}}<input type="text" name="system.grantsAction" value="{{sys.grantsAction}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.SkillBonusFam"}}<input type="text" name="system.skillBonus.skillFamilyId" value="{{sys.skillBonus.skillFamilyId}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.SkillBonusVal"}}<input type="number" name="system.skillBonus.value" value="{{sys.skillBonus.value}}"/></label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if isPower}}
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Item.Identity"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Item.ArtId"}}<input type="text" name="system.artId" value="{{sys.artId}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.ArtRating"}}<input type="number" name="system.value" value="{{sys.value}}"/></label>
|
||||
</div>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.ArtSummary"}}<input type="text" name="system.summary" value="{{sys.summary}}"/></label>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.ArtRule"}}<textarea name="system.rule" rows="3">{{sys.rule}}</textarea></label>
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Coh.Label"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Coh.Label"}}<input type="number" name="system.coherenceCost" value="{{sys.coherenceCost}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.PushCost"}}<input type="number" name="system.pushCost" value="{{sys.pushCost}}"/></label>
|
||||
</div>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.PushRule"}}<textarea name="system.pushRule" rows="2">{{sys.pushRule}}</textarea></label>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.Origin"}}<input type="text" name="system.origin" value="{{sys.origin}}"/></label>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="tab" data-group="primary" data-tab="desc">
|
||||
|
||||
+37
-1
@@ -793,6 +793,42 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
+ `${S.STARTER_PAGES.length} GM pages, ${S.STARTER_HANDOUTS.length} handouts`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Culland Hall as a playable Scene.
|
||||
*
|
||||
* The map is DRAWN (art/maps/culland-hall.svg) rather than generated, because a room
|
||||
* count, a door position and a label are all load-bearing in this scenario and a
|
||||
* generator cannot be trusted with any of them. 2800x2000 at 100px to the square, so
|
||||
* the Foundry grid lands exactly on the grid drawn into the image.
|
||||
*
|
||||
* No walls or lighting: the scenario is a conversation with a floor plan behind it, and
|
||||
* a GM who wants line-of-sight can add them in ten minutes. Shipping half-done walls
|
||||
* would be worse than shipping none.
|
||||
*/
|
||||
function cullandScene() {
|
||||
const id = idFor("scene:culland_hall");
|
||||
return {
|
||||
_id: id,
|
||||
name: "Culland Hall — ground floor and grounds",
|
||||
navigation: true, navName: "Culland Hall", active: false,
|
||||
background: { src: "systems/ringbrp/art/maps/culland-hall.webp" },
|
||||
foreground: null, thumb: null,
|
||||
width: 2800, height: 2000, padding: 0.1,
|
||||
initial: { x: 1400, y: 1000, scale: 0.4 },
|
||||
backgroundColor: "#0c125b",
|
||||
grid: { type: 1, size: 100, style: "solidLines", thickness: 1,
|
||||
color: "#6674d8", alpha: 0.12, distance: 5, units: "ft" },
|
||||
tokenVision: false, fog: { exploration: false, overlay: null },
|
||||
globalLight: true, darkness: 0,
|
||||
environment: { globalLight: { enabled: true }, darknessLevel: 0 },
|
||||
drawings: [], tokens: [], lights: [], sounds: [], regions: [], templates: [],
|
||||
tiles: [], walls: [], notes: [],
|
||||
playlist: null, playlistSound: null, journal: null, journalEntryPage: null,
|
||||
weather: "", flags: { ringbrp: { key: "culland_hall" } },
|
||||
...BASE()
|
||||
};
|
||||
}
|
||||
|
||||
// LAST ADMISSION — the two-hour prologue, and the one to run by default. Same shape as
|
||||
// OPEN DAY's adventure and for the same reason: journals and cast only, because the
|
||||
// player characters are the "(before)" pregens in the Pregens pack.
|
||||
@@ -837,7 +873,7 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
{ ...gmJournal.doc, pages: gmJournal.pages },
|
||||
{ ...handouts.doc, pages: handouts.pages }
|
||||
],
|
||||
scenes: [], combats: [], tables: [], macros: [], cards: [], playlists: [], folders: [],
|
||||
scenes: [cullandScene()], combats: [], tables: [], macros: [], cards: [], playlists: [], folders: [],
|
||||
sort: 0, ...BASE()
|
||||
};
|
||||
|
||||
|
||||
@@ -398,6 +398,34 @@ test("every compendium pack is declared in exactly one packFolders folder", asyn
|
||||
assert.equal(dupes.length, 0, `declared in more than one folder: ${dupes.join(", ")}`);
|
||||
});
|
||||
|
||||
// Talent and Art were added to template.json and then forgotten twice over: no
|
||||
// TYPES.Item.* label, so the sheet printed the raw key under the name, and no branch in
|
||||
// item-sheet.hbs, so the Detail tab was blank over a document full of real data. Eleven
|
||||
// of thirteen types were handled, which is exactly the shape a human eye skips.
|
||||
test("every declared document type has a label and a sheet section", async () => {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const here = u => new URL(u, import.meta.url);
|
||||
const tmpl = JSON.parse(await readFile(here("../template.json"), "utf8"));
|
||||
const lang = JSON.parse(await readFile(here("../lang/en.json"), "utf8"));
|
||||
const itemSheet = await readFile(here("../templates/item-sheet.hbs"), "utf8");
|
||||
const system = await readFile(here("../ringbrp.mjs"), "utf8");
|
||||
|
||||
for (const doc of ["Actor", "Item"]) {
|
||||
for (const t of tmpl[doc].types) {
|
||||
assert.ok(lang[`TYPES.${doc}.${t}`],
|
||||
`no TYPES.${doc}.${t} in en.json — the sheet prints the raw key`);
|
||||
}
|
||||
}
|
||||
// Item types share one sheet, so each needs its own branch and the flag that drives it.
|
||||
for (const t of tmpl.Item.types) {
|
||||
const flag = `is${t[0].toUpperCase()}${t.slice(1)}`;
|
||||
assert.ok(system.includes(`ctx.${flag} =`),
|
||||
`the item sheet never sets ${flag}, so its section can never render`);
|
||||
assert.ok(itemSheet.includes(`{{#if ${flag}}}`),
|
||||
`item-sheet.hbs has no {{#if ${flag}}} section — Detail will be blank for ${t}`);
|
||||
}
|
||||
});
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
await runAll();
|
||||
|
||||
Reference in New Issue
Block a user