Remove the dice textures and the whole Dice So Nice path
Deleted: the seven PNGs in dice/, tools/make-dice-textures.mjs, the `npm run dice` script, the RB_DICE colourset and texture tables, diceSetFor, the diceSoNiceReady hook, and rbDressDice — which was tagging ten roll sites with coloursets that no longer existed. 99 lines of module, plus the assets. README and DEPLOY updated; they still advertised all of it. Recoverable with `git revert` if it is ever wanted back. This also explains the 01-that-was-12. The RESULT was never wrong: skillRoll rolls one 1d100 and grades on roll.total, and the chat card derives the tens and units it prints from that same total. Checked all 100 values of percentileFaces and 60 live rolls — the printed faces reconstruct the graded number every time, with no mismatches. What disagreed was the ANIMATION. The custom addDicePreset labels registered for d100 and d10 remapped which physical face means what, so Dice So Nice could land showing 01 while the roll behind it was 12. Nothing downstream ever read the animated faces, so no roll was ever adjudicated on the wrong number — but a table watching the dice had every reason to believe otherwise, which is its own kind of wrong. Disabling the skins in 1.0.6 already removed it; deleting them makes it permanent. Verified in a running Foundry after removal: skill roll, weapon damage, hit location, major wound and Coherence all resolve and post their cards, no errors, and game.ringbrp.diceSetFor is gone from the API. Version 1.0.7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@@ -28,7 +28,8 @@ git clone https://gitea.timevans.uk/slaguru666/RingBRP.git ringbrp
|
||||
```
|
||||
|
||||
Requires **Foundry v13+** (verified against v14.364). No modules are required. Dice So
|
||||
Nice is supported but optional — the system registers four of its own dice textures.
|
||||
Nice works if you have it, using your own dice — the system ships no dice skins and does
|
||||
not touch anybody's dice settings.
|
||||
|
||||
### Deploying to a remote Foundry server
|
||||
|
||||
@@ -47,7 +48,7 @@ packs or regenerate art on that machine:
|
||||
```bash
|
||||
npm install # pulls classic-level, used to write the LevelDB packs
|
||||
npm run build # rebuilds packs/ from tools/content.mjs
|
||||
npm run check # the three guards; the build refuses to run if they fail
|
||||
npm run check # the five guards; the build refuses to run if they fail
|
||||
```
|
||||
|
||||
To update a deployed server: `git pull` and restart Foundry. If the pull touches
|
||||
@@ -152,13 +153,12 @@ template.json data model for every Actor and Item type
|
||||
templates/ Handlebars sheets
|
||||
styles/ one stylesheet
|
||||
lang/en.json all user-facing text
|
||||
icons/ dice/ fonts/ generated art
|
||||
icons/ fonts/ art/ generated art
|
||||
packs/ built LevelDB compendia (committed — see Deploying)
|
||||
tools/
|
||||
content.mjs the catalogue: skills, weapons, armour, gear, vehicles, NPCs
|
||||
build-packs.mjs builds packs/ — runs the three guards first and refuses on failure
|
||||
build-packs.mjs builds packs/ — runs the five guards first and refuses on failure
|
||||
make-icons.mjs draws all 231 icons
|
||||
make-dice-textures.mjs
|
||||
rules-text.mjs generates the rules journal FROM rules.mjs
|
||||
mission.mjs the case generator
|
||||
mission-tables.mjs its content tables
|
||||
@@ -176,11 +176,11 @@ stops being identity-equal to what it aliases, if a constant is re-declared as a
|
||||
literal, or if an exported rule has no spot-check. The packs used to be built under
|
||||
one set of numbers and played under another; this makes that impossible to ship.
|
||||
|
||||
The build runs all three guards before it writes anything:
|
||||
The build runs all five guards before it writes anything:
|
||||
|
||||
<!-- guards:start -->
|
||||
```
|
||||
check-rules: OK — 87 rules, 21 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
|
||||
check-rules: OK — 87 rules, 20 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, 945 keys, no leaf/branch collisions
|
||||
check-behaviour: OK — 46 behavioural tests
|
||||
|
||||
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 785 B |
|
Before Width: | Height: | Size: 970 B |
|
Before Width: | Height: | Size: 826 B |
@@ -33,10 +33,9 @@ Only if you intend to edit the catalogue there:
|
||||
|
||||
```bash
|
||||
npm install # classic-level, for writing LevelDB
|
||||
npm run check # the three guards
|
||||
npm run check # the five guards
|
||||
npm run build # rebuilds packs/
|
||||
node tools/make-icons.mjs # redraws all icons
|
||||
node tools/make-dice-textures.mjs # regenerates the Dice So Nice textures
|
||||
```
|
||||
|
||||
`npm run build` runs the guards first and aborts if any fails, so a broken catalogue
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"scripts": {
|
||||
"build": "node tools/build-packs.mjs && node tools/update-readme.mjs",
|
||||
"icons": "node tools/make-icons.mjs",
|
||||
"dice": "node tools/make-dice-textures.mjs",
|
||||
"check": "node tools/check-rules.mjs && node tools/check-kits.mjs && node tools/check-lang.mjs && node tools/check-templates.mjs && node tools/check-behaviour.mjs",
|
||||
"test": "npm run check",
|
||||
"readme": "node tools/update-readme.mjs"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.258824 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.258964 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.259113 175abb000 Level-0 table #5: 4591 bytes OK
|
||||
2026/08/12-20:33:19.259390 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.259432 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.920895 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.921047 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.921223 172817000 Level-0 table #5: 4591 bytes OK
|
||||
2026/08/12-22:44:48.921517 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.921554 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.500947 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.287733 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.281748 1772df000 Recovering log #3
|
||||
2026/08/12-20:33:19.282130 1772df000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.282266 1772df000 Level-0 table #5: 4129 bytes OK
|
||||
2026/08/12-20:33:19.282510 1772df000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.282542 1772df000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.946376 17382f000 Recovering log #3
|
||||
2026/08/12-22:44:48.946637 17382f000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.946788 17382f000 Level-0 table #5: 4129 bytes OK
|
||||
2026/08/12-22:44:48.947029 17382f000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.947065 17382f000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.577064 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.362147 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.260549 1772df000 Recovering log #3
|
||||
2026/08/12-20:33:19.260715 1772df000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.261020 1772df000 Level-0 table #5: 31614 bytes OK
|
||||
2026/08/12-20:33:19.261257 1772df000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.261283 1772df000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.922471 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.923346 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.923621 173023000 Level-0 table #5: 31614 bytes OK
|
||||
2026/08/12-22:44:48.923809 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.923836 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.503203 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.290003 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.273026 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.273575 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.273963 175abb000 Level-0 table #5: 45532 bytes OK
|
||||
2026/08/12-20:33:19.274265 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.274316 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.936635 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.937301 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.937794 17200b000 Level-0 table #5: 45532 bytes OK
|
||||
2026/08/12-22:44:48.938219 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.938275 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.557918 172843000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.345401 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.245248 1762c7000 Recovering log #3
|
||||
2026/08/12-20:33:19.246727 1762c7000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.248205 1762c7000 Level-0 table #5: 225945 bytes OK
|
||||
2026/08/12-20:33:19.248498 1762c7000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.248546 1762c7000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.907196 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.908927 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.910392 173023000 Level-0 table #5: 225945 bytes OK
|
||||
2026/08/12-22:44:48.910874 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.910934 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.540045 172843000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.327015 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.255310 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.255554 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.255695 175abb000 Level-0 table #5: 3295 bytes OK
|
||||
2026/08/12-20:33:19.255978 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.256022 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.917345 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.917660 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.917794 172817000 Level-0 table #5: 3295 bytes OK
|
||||
2026/08/12-22:44:48.918046 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.918081 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.569801 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.356982 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.232393 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.235807 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.239193 175abb000 Level-0 table #5: 545293 bytes OK
|
||||
2026/08/12-20:33:19.239934 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.240034 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.896053 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.898901 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.902279 17200b000 Level-0 table #5: 545293 bytes OK
|
||||
2026/08/12-22:44:48.902701 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.902787 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.524869 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.311753 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.263420 1762c7000 Recovering log #3
|
||||
2026/08/12-20:33:19.263582 1762c7000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.263817 1762c7000 Level-0 table #5: 21613 bytes OK
|
||||
2026/08/12-20:33:19.264044 1762c7000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.264079 1762c7000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.925841 17382f000 Recovering log #3
|
||||
2026/08/12-22:44:48.926073 17382f000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.926304 17382f000 Level-0 table #5: 21613 bytes OK
|
||||
2026/08/12-22:44:48.926557 17382f000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.926598 17382f000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.565946 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.353318 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.275275 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.275814 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.276182 175abb000 Level-0 table #5: 43953 bytes OK
|
||||
2026/08/12-20:33:19.276489 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.276529 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.939398 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.940505 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.941454 17200b000 Level-0 table #5: 43953 bytes OK
|
||||
2026/08/12-22:44:48.942198 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.942249 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.562330 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.349699 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.264705 176ad3000 Recovering log #3
|
||||
2026/08/12-20:33:19.265215 176ad3000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.265531 176ad3000 Level-0 table #5: 33484 bytes OK
|
||||
2026/08/12-20:33:19.265770 176ad3000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.265802 176ad3000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.927292 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.927647 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.928175 172817000 Level-0 table #5: 33484 bytes OK
|
||||
2026/08/12-22:44:48.928522 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.928579 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.575038 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.360431 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.251838 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.252367 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.252611 175abb000 Level-0 table #5: 7590 bytes OK
|
||||
2026/08/12-20:33:19.252941 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.252982 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.913614 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.914288 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.914458 172817000 Level-0 table #5: 7590 bytes OK
|
||||
2026/08/12-22:44:48.914739 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.914773 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.494044 17304f000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.281949 16edcf000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.277727 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.278352 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.278612 175abb000 Level-0 table #5: 16902 bytes OK
|
||||
2026/08/12-20:33:19.278891 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.278925 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.943128 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.943292 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.943546 17200b000 Level-0 table #5: 16902 bytes OK
|
||||
2026/08/12-22:44:48.943901 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.943932 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.567928 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.355279 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.268400 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.269480 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.270081 175abb000 Level-0 table #5: 82299 bytes OK
|
||||
2026/08/12-20:33:19.270432 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.270471 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.931234 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.932452 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.933139 17200b000 Level-0 table #5: 82299 bytes OK
|
||||
2026/08/12-22:44:48.933493 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.933538 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.550640 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.337758 16f5db000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.280144 176ad3000 Recovering log #3
|
||||
2026/08/12-20:33:19.280500 176ad3000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.280605 176ad3000 Level-0 table #5: 3739 bytes OK
|
||||
2026/08/12-20:33:19.280818 176ad3000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.280857 176ad3000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.944808 172817000 Recovering log #3
|
||||
2026/08/12-22:44:48.944973 172817000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.945086 172817000 Level-0 table #5: 3739 bytes OK
|
||||
2026/08/12-22:44:48.945317 172817000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.945349 172817000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.544720 17385b000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.331276 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.253872 1762c7000 Recovering log #3
|
||||
2026/08/12-20:33:19.254006 1762c7000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.254155 1762c7000 Level-0 table #5: 5770 bytes OK
|
||||
2026/08/12-20:33:19.254377 1762c7000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.254415 1762c7000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.915783 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.915922 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.916069 173023000 Level-0 table #5: 5770 bytes OK
|
||||
2026/08/12-22:44:48.916313 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.916348 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.496700 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.284515 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.262214 175abb000 Recovering log #3
|
||||
2026/08/12-20:33:19.262355 175abb000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.262536 175abb000 Level-0 table #5: 6939 bytes OK
|
||||
2026/08/12-20:33:19.262774 175abb000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.262806 175abb000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.924550 17200b000 Recovering log #3
|
||||
2026/08/12-22:44:48.924684 17200b000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.924837 17200b000 Level-0 table #5: 6939 bytes OK
|
||||
2026/08/12-22:44:48.925120 17200b000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.925163 17200b000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.505366 174067000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.292038 16fde7000 Delete type=3 #1
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/08/12-20:33:19.257047 1772df000 Recovering log #3
|
||||
2026/08/12-20:33:19.257521 1772df000 Level-0 table #5: started
|
||||
2026/08/12-20:33:19.257788 1772df000 Level-0 table #5: 14767 bytes OK
|
||||
2026/08/12-20:33:19.258070 1772df000 Delete type=0 #3
|
||||
2026/08/12-20:33:19.258099 1772df000 Delete type=3 #2
|
||||
2026/08/12-22:44:48.918709 173023000 Recovering log #3
|
||||
2026/08/12-22:44:48.919008 173023000 Level-0 table #5: started
|
||||
2026/08/12-22:44:48.919255 173023000 Level-0 table #5: 14767 bytes OK
|
||||
2026/08/12-22:44:48.919627 173023000 Delete type=0 #3
|
||||
2026/08/12-22:44:48.919674 173023000 Delete type=3 #2
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-20:33:03.498976 172843000 Delete type=3 #1
|
||||
2026/08/12-22:44:33.286188 16e5c3000 Delete type=3 #1
|
||||
|
||||
@@ -1075,7 +1075,6 @@ export async function skillRoll({
|
||||
}
|
||||
);
|
||||
|
||||
rbDressDice(roll, actor);
|
||||
await roll.toMessage({
|
||||
speaker: speaker ?? ChatMessage.getSpeaker({ actor }),
|
||||
content,
|
||||
@@ -1108,7 +1107,7 @@ export async function majorWoundRoll({ actor, woundType = "blunt", monofilament
|
||||
console.error("Custodians | majorWoundRoll called without an actor");
|
||||
return null;
|
||||
}
|
||||
const r = rbDressDice(await new Roll("1d10").evaluate(), actor);
|
||||
const r = await new Roll("1d10").evaluate();
|
||||
let result = r.total;
|
||||
if (monofilament && result >= 4) result = 10; // spec §3.3 shift
|
||||
else if (blunt && result === 10) result = 9;
|
||||
@@ -1171,7 +1170,7 @@ export async function hitLocationRoll({ target, damage, mode = "ranged",
|
||||
}
|
||||
const sys = target.system;
|
||||
const speciesId = sys.speciesProfile || "baseline";
|
||||
const r = rbDressDice(await new Roll("1d20").evaluate(), target);
|
||||
const r = await new Roll("1d20").evaluate();
|
||||
const loc = locationFor(r.total, speciesId, mode);
|
||||
const live = (sys.locations ?? []).find(l => l.id === loc.id);
|
||||
const max = live?.max ?? locationMaxHp(sys.hp?.max ?? 1, loc.frac);
|
||||
@@ -1280,7 +1279,7 @@ export async function rollReaction(combatant) {
|
||||
: reactionPrintedFrom(rb.base, f.loadOverride);
|
||||
const mod = reactionModifier({ aim: f.aim ?? 0, edge: !!f.edge, drag: !!f.drag });
|
||||
|
||||
const roll = rbDressDice(await new Roll(`1d${RINGBRP.reaction.die}`).evaluate(), actor);
|
||||
const roll = await new Roll(`1d${RINGBRP.reaction.die}`).evaluate();
|
||||
const raw = roll.total;
|
||||
const coherence = actor.system?.coherence?.value ?? 10;
|
||||
const shown = fluxDie(raw, coherence);
|
||||
@@ -2384,7 +2383,7 @@ export async function fall(actor, metres, { soft = false, controlled = false } =
|
||||
content: `<div class="ringbrp-chat">${game.i18n.format("RINGBRP.Fall.Short", { m: metres })}</div>` });
|
||||
return { damage: 0 };
|
||||
}
|
||||
const roll = rbDressDice(await new Roll(plan.formula).evaluate(), actor);
|
||||
const roll = await new Roll(plan.formula).evaluate();
|
||||
await roll.toMessage({ speaker: ChatMessage.getSpeaker({ actor }),
|
||||
flavor: game.i18n.format("RINGBRP.Fall.Flavor", { m: metres, dice: plan.formula }) });
|
||||
// armourApplied true: the fall ignores armour, and this is how we say so.
|
||||
@@ -2417,7 +2416,7 @@ export async function asphyxiate(actor, { held = 0, prepared = true } = {}) {
|
||||
export async function burn(actor, bandId = "clothes", { round = 1 } = {}) {
|
||||
if (!actor) return null;
|
||||
const band = fireBandFor(bandId);
|
||||
const roll = rbDressDice(await new Roll(band.damage).evaluate(), actor);
|
||||
const roll = await new Roll(band.damage).evaluate();
|
||||
// Armour on the round you catch, and never after. A sealed suit is the exception
|
||||
// and the GM should say so by running it as a `spark` that never spreads.
|
||||
const armour = (band.armour && round === 1) ? (Number(actor.system.armourPoints) || 0) : 0;
|
||||
@@ -2436,7 +2435,7 @@ export async function burn(actor, bandId = "clothes", { round = 1 } = {}) {
|
||||
* armour is only ever half of one, at contact.
|
||||
*/
|
||||
export async function detonate({ formula = "4d6", radius = 6, targets = [] } = {}) {
|
||||
const roll = rbDressDice(await new Roll(formula).evaluate(), null);
|
||||
const roll = await new Roll(formula).evaluate();
|
||||
await roll.toMessage({ flavor: game.i18n.format("RINGBRP.Blast.Flavor",
|
||||
{ formula, r: radius }) });
|
||||
const out = [];
|
||||
@@ -4292,7 +4291,7 @@ export async function rollCharacteristicsDialog({ actorName = "" } = {}) {
|
||||
const rollOne = async k => {
|
||||
const cell = el.querySelector(`tr[data-ch="${k}"] .rb-roll-value`);
|
||||
if (!cell || cell.dataset.value) return; // rolled is rolled
|
||||
const roll = rbDressDice(await new Roll(CHARACTERISTIC_DICE[k]).evaluate());
|
||||
const roll = await new Roll(CHARACTERISTIC_DICE[k]).evaluate();
|
||||
await roll.toMessage({
|
||||
flavor: game.i18n.format("RINGBRP.Gen.RolledChar",
|
||||
{ ch: game.i18n.localize(`RINGBRP.Char.${k}`) })
|
||||
@@ -4538,105 +4537,6 @@ Hooks.on("preCreateItem", (item, data) => {
|
||||
* The colourset is chosen per roll from the roller's Coherence, so the dice
|
||||
* themselves report the one number the players are trying not to look at.
|
||||
* ============================================================ */
|
||||
const RB_DICE = {
|
||||
textures: {
|
||||
"custodians-registry": { name: "Registry", file: "custodians-registry.png" },
|
||||
"custodians-drift": { name: "Drift", file: "custodians-drift.png" },
|
||||
"custodians-displaced": { name: "Displaced", file: "custodians-displaced.png" },
|
||||
"custodians-agency": { name: "Agency", file: "custodians-agency.png" }
|
||||
},
|
||||
// one colourset per Coherence band, plus the plain agency set
|
||||
sets: [
|
||||
{ name: "custodians-anchored", description: "Custodians — Anchored",
|
||||
foreground: "#f2ea72", background: "#18268d", outline: "#0c125b",
|
||||
edge: "#6674d8", texture: "custodians-registry", material: "metal" },
|
||||
{ name: "custodians-loose", description: "Custodians — Loose",
|
||||
foreground: "#f2ea72", background: "#16215f", outline: "#0c125b",
|
||||
edge: "#6674d8", texture: "custodians-registry", material: "plastic" },
|
||||
{ name: "custodians-adrift", description: "Custodians — Adrift",
|
||||
foreground: "#ecefee", background: "#141e78", outline: "#0c125b",
|
||||
edge: "#7a6cc8", texture: "custodians-drift", material: "plastic" },
|
||||
{ name: "custodians-unmoored", description: "Custodians — Unmoored",
|
||||
foreground: "#ff7a5c", background: "#101a6e", outline: "#08103f",
|
||||
edge: "#ff7a5c", texture: "custodians-drift", material: "glass" },
|
||||
{ name: "custodians-displaced", description: "Custodians — Displaced",
|
||||
foreground: "#c9b6ff", background: "#0c0e3c", outline: "#000000",
|
||||
edge: "#8a6cc0", texture: "custodians-displaced", material: "glass" },
|
||||
{ name: "custodians-agency", description: "Custodians — Agency issue",
|
||||
foreground: "#f2ea72", background: "#16215f", outline: "#0c125b",
|
||||
edge: "#a8b2e3", texture: "custodians-agency", material: "metal" }
|
||||
]
|
||||
};
|
||||
|
||||
/** Which set an agent rolls, from the band they are in. */
|
||||
export function diceSetFor(actor) {
|
||||
const band = coherenceBand(actor?.system?.coherence?.value ?? 10);
|
||||
return `custodians-${band?.id ?? "anchored"}`;
|
||||
}
|
||||
|
||||
/** Tag a Roll so Dice So Nice draws it in the roller's colours. */
|
||||
function rbDressDice(roll, actor) {
|
||||
if (!roll?.dice?.length) return roll;
|
||||
const colorset = diceSetFor(actor);
|
||||
for (const d of roll.dice) {
|
||||
d.options.colorset = colorset;
|
||||
d.options.appearance = { ...(d.options.appearance ?? {}), colorset };
|
||||
}
|
||||
return roll;
|
||||
}
|
||||
|
||||
/**
|
||||
* The custom Dice So Nice skins are OFF.
|
||||
*
|
||||
* Registering them with "preferred" makes them the default for everybody in the world,
|
||||
* which overrides whatever dice a player had already chosen for themselves — the system
|
||||
* helping itself to a setting that belongs to the user. Turned off at the author's
|
||||
* request; flip this to true to put them back, and everything below still works.
|
||||
*
|
||||
* The textures in dice/ and the generator behind `npm run dice` are left alone. Nothing
|
||||
* reads them while this is false, and deleting work to turn a feature off is a poor
|
||||
* trade.
|
||||
*/
|
||||
const RB_DICE_ENABLED = false;
|
||||
|
||||
Hooks.once("diceSoNiceReady", async (dice3d) => {
|
||||
if (!RB_DICE_ENABLED) return;
|
||||
try {
|
||||
dice3d.addSystem({ id: "custodians", name: "The Custodians" }, "preferred");
|
||||
|
||||
// addTexture returns a Promise and the colourset that references a texture must
|
||||
// not be registered before it resolves, or the die renders untextured.
|
||||
await Promise.all(Object.entries(RB_DICE.textures).map(([id, t]) =>
|
||||
dice3d.addTexture(id, {
|
||||
name: t.name, composite: "source-over",
|
||||
source: `systems/ringbrp/dice/${t.file}`
|
||||
})));
|
||||
|
||||
for (const set of RB_DICE.sets) {
|
||||
dice3d.addColorset({
|
||||
...set, category: "The Custodians",
|
||||
font: "Michroma", visibility: "visible"
|
||||
}, set.name === "custodians-anchored" ? "default" : "showcase");
|
||||
}
|
||||
|
||||
// The percentile pair is the game's core roll, so it is the one worth dressing.
|
||||
dice3d.addDicePreset({
|
||||
type: "d100", system: "custodians",
|
||||
labels: ["00", "10", "20", "30", "40", "50", "60", "70", "80", "90"]
|
||||
});
|
||||
dice3d.addDicePreset({ type: "d10", system: "custodians",
|
||||
labels: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] });
|
||||
for (const t of ["d4", "d6", "d8", "d12", "d20"]) {
|
||||
const n = Number(t.slice(1));
|
||||
dice3d.addDicePreset({ type: t, system: "custodians",
|
||||
labels: Array.from({ length: n }, (_, i) => String(i + 1)) });
|
||||
}
|
||||
console.log("Custodians | dice registered with Dice So Nice");
|
||||
} catch (err) {
|
||||
console.warn("Custodians | could not register dice", err);
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.on("renderItemDirectory", (app, html) => {
|
||||
const root = html instanceof HTMLElement ? html : html?.[0];
|
||||
if (!root || !game.user.isGM || root.querySelector(".rb-genbar")) return;
|
||||
@@ -5890,7 +5790,6 @@ export async function rollWeaponDamage({ actor = null, weapon, level = "success"
|
||||
roll = await new Roll(usesDm ? formula + dm : formula).evaluate();
|
||||
}
|
||||
|
||||
rbDressDice(roll, actor);
|
||||
const note = level === "critical" ? game.i18n.localize("RINGBRP.Chat.DamageCritical")
|
||||
: level === "special" ? game.i18n.localize("RINGBRP.Chat.DamageSpecial")
|
||||
: "";
|
||||
@@ -6204,7 +6103,7 @@ Hooks.once("init", () => {
|
||||
usePower, grantPower, ARTS, powerCostFor, crossBack,
|
||||
generateMissionCase, missionDialog, salvageFrom,
|
||||
crossingArtPoolFor, crossingGrantsArt, crossingCostFor, returnCrossing,
|
||||
rollWeaponDamage, diceSetFor,
|
||||
rollWeaponDamage,
|
||||
restFor, endOfCase, gearBonusFor,
|
||||
hasTalent, talentAvailable, useTalent, requisition, armCleanScene, resetTalents,
|
||||
generateCharacter, generateNPC, generateDialog, installContent, grantFullSkillList,
|
||||
|
||||
@@ -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.6",
|
||||
"version": "1.0.7",
|
||||
"compatibility": {
|
||||
"minimum": 13,
|
||||
"verified": "14.364"
|
||||
|
||||
@@ -1,176 +0,0 @@
|
||||
/**
|
||||
* Dice textures for The Custodians, generated rather than drawn.
|
||||
*
|
||||
* Written as raw RGBA and encoded to PNG with node:zlib, so the repo gains no
|
||||
* image dependency and the textures can be regenerated deterministically. Every
|
||||
* pattern is tileable — Dice So Nice wraps these across a die face, and a seam
|
||||
* shows up immediately on a d20.
|
||||
*
|
||||
* The set is the setting: the Custodians keep a filing system, and the game is
|
||||
* about that filing system being wrong. So the dice carry a ruled grid, and the
|
||||
* further an agent drifts from their own reality the more the grid stops agreeing
|
||||
* with itself.
|
||||
*
|
||||
* node tools/make-dice-textures.mjs
|
||||
*/
|
||||
import { deflateSync } from "node:zlib";
|
||||
import { writeFile, mkdir } from "node:fs/promises";
|
||||
|
||||
const SIZE = 256;
|
||||
|
||||
/* ---------- a minimal PNG encoder: RGBA, no interlace ---------- */
|
||||
function crc32(buf) {
|
||||
let c, table = crc32.table;
|
||||
if (!table) {
|
||||
table = crc32.table = new Int32Array(256);
|
||||
for (let n = 0; n < 256; n++) {
|
||||
c = n;
|
||||
for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
||||
table[n] = c;
|
||||
}
|
||||
}
|
||||
c = -1;
|
||||
for (let i = 0; i < buf.length; i++) c = table[(c ^ buf[i]) & 0xff] ^ (c >>> 8);
|
||||
return (c ^ -1) >>> 0;
|
||||
}
|
||||
|
||||
function chunk(type, data) {
|
||||
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
|
||||
const td = Buffer.concat([Buffer.from(type, "ascii"), data]);
|
||||
const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(td));
|
||||
return Buffer.concat([len, td, crc]);
|
||||
}
|
||||
|
||||
function png(width, height, rgba) {
|
||||
const ihdr = Buffer.alloc(13);
|
||||
ihdr.writeUInt32BE(width, 0); ihdr.writeUInt32BE(height, 4);
|
||||
ihdr[8] = 8; ihdr[9] = 6; ihdr[10] = 0; ihdr[11] = 0; ihdr[12] = 0;
|
||||
// each scanline is prefixed with filter byte 0
|
||||
const raw = Buffer.alloc(height * (width * 4 + 1));
|
||||
for (let y = 0; y < height; y++) {
|
||||
raw[y * (width * 4 + 1)] = 0;
|
||||
rgba.copy(raw, y * (width * 4 + 1) + 1, y * width * 4, (y + 1) * width * 4);
|
||||
}
|
||||
return Buffer.concat([
|
||||
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]),
|
||||
chunk("IHDR", ihdr),
|
||||
chunk("IDAT", deflateSync(raw, { level: 9 })),
|
||||
chunk("IEND", Buffer.alloc(0))
|
||||
]);
|
||||
}
|
||||
|
||||
/* ---------- deterministic noise, so a rebuild is byte-identical ---------- */
|
||||
let seed = 20260810;
|
||||
const rnd = () => (seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296;
|
||||
|
||||
const canvas = () => Buffer.alloc(SIZE * SIZE * 4);
|
||||
const put = (buf, x, y, r, g, b, a = 255) => {
|
||||
const i = ((y & (SIZE - 1)) * SIZE + (x & (SIZE - 1))) * 4;
|
||||
buf[i] = r; buf[i + 1] = g; buf[i + 2] = b; buf[i + 3] = a;
|
||||
};
|
||||
const fill = (buf, r, g, b, a = 255) => {
|
||||
for (let i = 0; i < buf.length; i += 4) { buf[i] = r; buf[i + 1] = g; buf[i + 2] = b; buf[i + 3] = a; }
|
||||
};
|
||||
|
||||
/* ============================================================
|
||||
1. REGISTRY — the ruled grid of a filing system, kept true.
|
||||
============================================================ */
|
||||
function registry() {
|
||||
const b = canvas();
|
||||
fill(b, 24, 38, 141); // ultramarine ground
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
for (const step of [32]) {
|
||||
for (let k = 0; k < SIZE; k += step) {
|
||||
put(b, i, k, 102, 116, 216); // --rb-line
|
||||
put(b, k, i, 102, 116, 216);
|
||||
}
|
||||
}
|
||||
for (let k = 16; k < SIZE; k += 32) { // fainter half-lines
|
||||
put(b, i, k, 60, 78, 175);
|
||||
put(b, k, i, 60, 78, 175);
|
||||
}
|
||||
}
|
||||
// registry ticks at the intersections
|
||||
for (let x = 0; x < SIZE; x += 64) for (let y = 0; y < SIZE; y += 64) {
|
||||
for (let d = -3; d <= 3; d++) { put(b, x + d, y, 242, 234, 114); put(b, x, y + d, 242, 234, 114); }
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
2. DRIFT — the same grid, no longer agreeing with itself.
|
||||
Used for agents who have come loose from their own reality.
|
||||
============================================================ */
|
||||
function drift() {
|
||||
const b = canvas();
|
||||
fill(b, 20, 30, 120);
|
||||
const wobble = y => Math.round(6 * Math.sin(y / 14) + 3 * Math.sin(y / 5.5));
|
||||
for (let i = 0; i < SIZE; i++) {
|
||||
for (let k = 0; k < SIZE; k += 32) {
|
||||
put(b, i + wobble(i), k, 122, 108, 200);
|
||||
put(b, k, i + wobble(i), 122, 108, 200);
|
||||
}
|
||||
}
|
||||
for (let n = 0; n < 900; n++) { // grain where it has torn
|
||||
const x = (rnd() * SIZE) | 0, y = (rnd() * SIZE) | 0;
|
||||
put(b, x, y, 200, 120, 150, 255);
|
||||
}
|
||||
for (let x = 0; x < SIZE; x += 64) for (let y = 0; y < SIZE; y += 64) {
|
||||
const ox = ((rnd() * 7) | 0) - 3, oy = ((rnd() * 7) | 0) - 3;
|
||||
for (let d = -3; d <= 3; d++) { put(b, x + d + ox, y + oy, 255, 122, 92); put(b, x + ox, y + d + oy, 255, 122, 92); }
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
3. DISPLACED — the grid is gone. What is left is not filed.
|
||||
============================================================ */
|
||||
function displaced() {
|
||||
const b = canvas();
|
||||
fill(b, 12, 14, 60);
|
||||
for (let n = 0; n < 5200; n++) {
|
||||
const x = (rnd() * SIZE) | 0, y = (rnd() * SIZE) | 0;
|
||||
const v = 40 + ((rnd() * 90) | 0);
|
||||
put(b, x, y, v, v * 0.5, v * 0.7);
|
||||
}
|
||||
// a few surviving fragments of rule, going nowhere
|
||||
for (let n = 0; n < 26; n++) {
|
||||
const x = (rnd() * SIZE) | 0, y = (rnd() * SIZE) | 0, len = 6 + ((rnd() * 22) | 0);
|
||||
const horiz = rnd() > 0.5;
|
||||
for (let d = 0; d < len; d++) {
|
||||
if (horiz) put(b, x + d, y, 150, 120, 190); else put(b, x, y + d, 150, 120, 190);
|
||||
}
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
4. AGENCY — brushed plate, for the agency-issue set.
|
||||
============================================================ */
|
||||
function agency() {
|
||||
const b = canvas();
|
||||
for (let y = 0; y < SIZE; y++) {
|
||||
for (let x = 0; x < SIZE; x++) {
|
||||
const grain = 6 * Math.sin(x / 1.7 + y / 90) + (rnd() * 7 - 3.5);
|
||||
const v = 30 + grain;
|
||||
put(b, x, y, 22 + v, 33 + v, 95 + v);
|
||||
}
|
||||
}
|
||||
for (let k = 0; k < SIZE; k += 48) for (let i = 0; i < SIZE; i++) put(b, i, k, 90, 104, 190);
|
||||
return b;
|
||||
}
|
||||
|
||||
const TEXTURES = {
|
||||
"custodians-registry": registry,
|
||||
"custodians-drift": drift,
|
||||
"custodians-displaced": displaced,
|
||||
"custodians-agency": agency
|
||||
};
|
||||
|
||||
await mkdir(new URL("../dice/", import.meta.url), { recursive: true });
|
||||
for (const [name, make] of Object.entries(TEXTURES)) {
|
||||
const buf = png(SIZE, SIZE, make());
|
||||
await writeFile(new URL(`../dice/${name}.png`, import.meta.url), buf);
|
||||
console.log(` ${name}.png ${SIZE}x${SIZE} ${(buf.length / 1024).toFixed(1)} kB`);
|
||||
}
|
||||
console.log("Custodians | dice textures written");
|
||||