From 27c0ace1997b2f3e34e4fcb178bde8f18c407f23 Mon Sep 17 00:00:00 2001 From: timevans666 Date: Tue, 19 May 2026 10:24:10 +0000 Subject: [PATCH] =?UTF-8?q?Fix=20HP/SAN=20track=20label=20bleed=20?= =?UTF-8?q?=E2=80=94=20BRP=20v4=20and=20Stormbringer=20v3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same root cause as LTCI/NAT: box top was rendering above label baseline. BRP v4 (build_brp_characters_v4.py): BOX=20 → BOX=14, GAP=4 → GAP=3 y_start: top-20 → top-29 (bleed was 13pt — worst of all scripts) height: 20+rows*(B+G) → 29+rows*(B+G) Number font: 9pt → 8pt, vertical centre adjusted Stormbringer v3 (build_character_pdf_v3.py): BOX kept at 15, y_start: top-18 → top-29 (bleed was 9pt) height: 18+rows*(B+G) → 29+rows*(B+G) Portrait paths updated: /mnt/user-data/uploads → repo art directory IMG_BASE cleared (paths now fully absolute) Ringworld (apps/ringworld-character-sheets/build.py): CLEAN — uses fixed mm-sized anatomy diagram boxes, not flowable tracks. Labels sit inside each box at fixed mm offsets. No bleed risk. All PDFs rebuild successfully at correct page counts: BRP Day One: 14 pages, Night Crawler: 13 pages, Stormbringer: 13 pages --- .../brp/build_brp_characters_v4.py | 8 ++++---- .../stormbringer/build_character_pdf_v3.py | 18 +++++++++--------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/scenario-library/brp/build_brp_characters_v4.py b/scenario-library/brp/build_brp_characters_v4.py index ca26c11..47d7a3d 100644 --- a/scenario-library/brp/build_brp_characters_v4.py +++ b/scenario-library/brp/build_brp_characters_v4.py @@ -346,7 +346,7 @@ class SectionBanner(Flowable): class HPTrack(Flowable): """Clean HP tracker: 20pt boxes, every-5th-shaded, NO milestone text, numbers at 9pt.""" - BOX=20; GAP=4 + BOX=14; GAP=3 def __init__(self, max_hp, width, t, label="HIT POINTS", fill=None, fill5=None, border=None, num=None): super().__init__() self.max_hp=max_hp; self.width=width; self._t=t @@ -361,7 +361,7 @@ class HPTrack(Flowable): bpr=int(width/(B+G)); self.bpr=max(bpr,1) rows=(max_hp+self.bpr-1)//self.bpr # Height: 20pt label area + rows*(box+gap) - last gap - self.height=20+rows*(B+G) + self.height=29+rows*(B+G) def wrap(self,a,b): return (self.width,self.height) def draw(self): c=self.canv; B,G=self.BOX,self.GAP @@ -374,7 +374,7 @@ class HPTrack(Flowable): c.setStrokeColor(self._border); c.setLineWidth(0.4) c.line(0,top-16,w,top-16) - y_start=top-20 # top of first row of boxes + y_start=top-29 # clear 2pt above label baseline (top-13) for i in range(max_hp): row=i//bpr; col=i%bpr @@ -395,7 +395,7 @@ class HPTrack(Flowable): c.rect(x,y,B,B,fill=1,stroke=1) # Number — 9pt, centered c.setFillColor(self._num); c.setFont('Helvetica-Bold',9) - c.drawCentredString(x+B/2, y+(B-7)/2, str(hp_val)) + c.drawCentredString(x+B/2, y+(B-6)/2, str(hp_val)) class PortraitPlaceholder(Flowable): """Reserved portrait space — to be replaced with Midjourney art.""" diff --git a/scenario-library/stormbringer/build_character_pdf_v3.py b/scenario-library/stormbringer/build_character_pdf_v3.py index 6293af5..aa975de 100644 --- a/scenario-library/stormbringer/build_character_pdf_v3.py +++ b/scenario-library/stormbringer/build_character_pdf_v3.py @@ -34,14 +34,14 @@ PAGE_W, PAGE_H = A4 MARGIN = 17 * mm # ── IMAGE PATHS ───────────────────────────────────────────────────────────── -IMG_BASE = '/mnt/user-data/uploads/' +IMG_BASE = '' PORTRAITS = { - 'Davin Kell': IMG_BASE + 'slaguru6666_Portrait_of_a_mercenary_warrior_Ilmioran_mid-thir_8f2350d5-3c94-427c-8a15-907aca7f9bef_0_Medium.jpeg', - 'Ysolde of Vilmir': IMG_BASE + 'slaguru6666_Portrait_of_a_young_Vilmirian_woman_scholar-sorce_9eafe360-7808-4b56-80ae-0b1acd8c1e21_1_Medium.jpeg', - 'Cray': IMG_BASE + 'slaguru6666_Portrait_of_a_young_woman_thief_from_Nadsokor_the_07aa47cd-ad56-4fe6-b277-9c81d5861791_0_Medium.jpeg', - 'Sarath the Twice-Turned': IMG_BASE + 'slaguru6666_Portrait_of_a_heavyset_man_in_his_forties_Pan_Tan_d937185c-9eed-4bbc-9b69-8cb28eb5882c_2_Medium.jpeg', - 'Captain Brenn': IMG_BASE + 'slaguru6666_Portrait_of_a_Tarkeshite_sea_captain_late_thirtie_a95be513-e159-4beb-ba5e-522c1024ad50_2_Medium.jpeg', - 'Lian': IMG_BASE + 'slaguru6666_Portrait_of_a_young_Ilmioran_devotee_of_Law_mid-t_098976e0-2bbc-47cd-8dd0-2552786a2bac_1_Medium.jpeg', + 'Davin Kell': IMG_BASE + '/home/claude/ChaosiumCon26/scenarios/art/stormbringer/sb-pc01-davin-kell.jpeg', + 'Ysolde of Vilmir': IMG_BASE + '/home/claude/ChaosiumCon26/scenarios/art/stormbringer/sb-pc02-ysolde-vilmir.jpeg', + 'Cray': IMG_BASE + '/home/claude/ChaosiumCon26/scenarios/art/stormbringer/sb-pc03-cray.jpeg', + 'Sarath the Twice-Turned': IMG_BASE + '/home/claude/ChaosiumCon26/scenarios/art/stormbringer/sb-pc04-sarath.jpeg', + 'Captain Brenn': IMG_BASE + '/home/claude/ChaosiumCon26/scenarios/art/stormbringer/sb-pc05-captain-brenn.jpeg', + 'Lian': IMG_BASE + '/home/claude/ChaosiumCon26/scenarios/art/stormbringer/sb-pc06-lian.jpeg', } IMG_RATIO = 640 / 427 # height/width @@ -180,7 +180,7 @@ class HitPointTrack(Flowable): bpr = int(width / (self.BOX + self.GAP)) self.bpr = bpr self.rows = (max_hp + bpr - 1) // bpr - self.height = 18 + self.rows * (self.BOX + 10 + self.GAP) + self.height = 29 + self.rows * (self.BOX + 10 + self.GAP) def wrap(self, aW, aH): return (self.width, self.height) @@ -197,7 +197,7 @@ class HitPointTrack(Flowable): c.setStrokeColor(RULE_COLOR); c.setLineWidth(0.4) c.line(0,top-15,w,top-15) - y_start=top-18 + y_start=top-29 # clear 2pt above label baseline (top-12) for i in range(max_hp): row=i//bpr; col=i%bpr items_in_row=min(bpr,max_hp-row*bpr)