sea affinity scroll log: personalized per-Role prose with pronouns (draws POSS Sea of cards, where the CARD ...) — TDD
Replaces the generic 'finding affinity with the X in the Crown' SEA_DRAWN prose with a role-specific clause per user-spec 2026-06-09 — the @handle is template-prepended, pronouns (subj/poss) resolve via the actor: PC: draws POSS Sea of cards, where the CARD crowns all POSS loftiest illusions. NC: ...the CARD traces all the narratives SUBJ leaves behind. EC: ...always looms before POSS calling. SC: ...covers all POSS righteous conduct. AC: ...always crosses POSS sinister connections. BC: ...lays all POSS foundational work. to_prose branches on data['role']; 'The ' still stripped so a qualifier butts the proper name; an unknown role falls back to a generic 'marks POSS affinity' clause. The stored position_label/corner_rank/suit_icon are now unused by the prose but left in the event data (harmless). TDD: drama prose tests reworked (per-role clauses + bawlmorese pronoun substitution) + the epic affinity-prose IT updated to the PC crown clause. 49 drama+epic ITs green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -163,26 +163,22 @@ class GameEvent(models.Model):
|
||||
f"which yields {obj} equal {joined} capacities."
|
||||
)
|
||||
if self.verb == self.SEA_DRAWN:
|
||||
# Affinity statement: the card drawn into the gamer's Role-correlated
|
||||
# Celtic position. `card_name`/`corner_rank`/`suit_icon` mirror
|
||||
# SIG_READY (qualifier-prefixed name + abbrev, "The " stripped so a
|
||||
# qualifier butts the proper name); `position_label` is the
|
||||
# spread-specific label for the role's position key.
|
||||
card_name = d.get("card_name", "a card")
|
||||
corner_rank = d.get("corner_rank", "")
|
||||
suit_icon = d.get("suit_icon", "")
|
||||
position_label = d.get("position_label", "spread")
|
||||
if corner_rank:
|
||||
icon_html = f' <i class="fa-solid {suit_icon}"></i>' if suit_icon else ""
|
||||
abbrev = f" ({corner_rank}{icon_html})"
|
||||
else:
|
||||
abbrev = ""
|
||||
card_name = card_name.replace("The ", "", 1)
|
||||
_, _, poss = _actor_pronouns(self.actor)
|
||||
return (
|
||||
f"draws {poss} Celtic Cross, finding affinity with "
|
||||
f"the {card_name}{abbrev} in the {position_label}."
|
||||
)
|
||||
# Personalized per-Role affinity (user-spec 2026-06-09): the card drawn
|
||||
# into the gamer's Role-correlated Celtic position, woven into a
|
||||
# role-specific clause. The actor's @handle is prepended by the
|
||||
# template; pronouns resolve via the actor (subj/poss). "The " is
|
||||
# stripped so a levity/gravity qualifier butts the proper name.
|
||||
card = d.get("card_name", "a card").replace("The ", "", 1)
|
||||
subj, _, poss = _actor_pronouns(self.actor)
|
||||
clause = {
|
||||
"PC": f"the {card} crowns all {poss} loftiest illusions",
|
||||
"NC": f"the {card} traces all the narratives {subj} leaves behind",
|
||||
"EC": f"the {card} always looms before {poss} calling",
|
||||
"SC": f"the {card} covers all {poss} righteous conduct",
|
||||
"AC": f"the {card} always crosses {poss} sinister connections",
|
||||
"BC": f"the {card} lays all {poss} foundational work",
|
||||
}.get(d.get("role", ""), f"the {card} marks {poss} affinity")
|
||||
return f"draws {poss} Sea of cards, where {clause}."
|
||||
if self.verb == self.SEA_RELINQUISHED:
|
||||
card_name = d.get("card_name", "a card").replace("The ", "", 1)
|
||||
_, _, poss = _actor_pronouns(self.actor)
|
||||
|
||||
@@ -181,24 +181,43 @@ class GameEventModelTest(TestCase):
|
||||
)
|
||||
|
||||
# ── to_prose — SEA_DRAWN / SEA_RELINQUISHED ──────────────────────────
|
||||
def test_sea_drawn_prose_states_affinity_and_position(self):
|
||||
def test_sea_drawn_prose_pc_crown_clause(self):
|
||||
event = record(self.room, GameEvent.SEA_DRAWN, actor=self.user,
|
||||
role="PC", slot_number=1, position="crown",
|
||||
position_label="Crown", card_name="The Magician",
|
||||
corner_rank="I", suit_icon="")
|
||||
prose = event.to_prose()
|
||||
# Default pronouns = pluralism → "their"; "The " is stripped before "the".
|
||||
self.assertIn(
|
||||
"draws their Celtic Cross, finding affinity with the Magician (I) "
|
||||
"in the Crown.", prose)
|
||||
role="PC", slot_number=1, card_name="The Magician")
|
||||
# Default pronouns = pluralism → "their"; "The " stripped before "the".
|
||||
self.assertEqual(
|
||||
event.to_prose(),
|
||||
"draws their Sea of cards, where the Magician crowns all "
|
||||
"their loftiest illusions.")
|
||||
|
||||
def test_sea_drawn_prose_uses_spread_label(self):
|
||||
# Escape-Velocity calls the `loom` position "Loom"; the label is passed
|
||||
# in `position_label`, so to_prose stays spread-agnostic.
|
||||
def test_sea_drawn_prose_per_role_clauses(self):
|
||||
# Each Role gets its own clause (user-spec 2026-06-09). Default pronouns:
|
||||
# subj "they", poss "their".
|
||||
cases = {
|
||||
"PC": "the Maid crowns all their loftiest illusions",
|
||||
"NC": "the Maid traces all the narratives they leaves behind",
|
||||
"EC": "the Maid always looms before their calling",
|
||||
"SC": "the Maid covers all their righteous conduct",
|
||||
"AC": "the Maid always crosses their sinister connections",
|
||||
"BC": "the Maid lays all their foundational work",
|
||||
}
|
||||
for role, clause in cases.items():
|
||||
event = record(self.room, GameEvent.SEA_DRAWN, actor=self.user,
|
||||
role=role, card_name="Maid")
|
||||
self.assertEqual(
|
||||
event.to_prose(),
|
||||
f"draws their Sea of cards, where {clause}.", f"role={role}")
|
||||
|
||||
def test_sea_drawn_prose_uses_actor_pronouns(self):
|
||||
self.user.pronouns = "bawlmorese"
|
||||
self.user.save(update_fields=["pronouns"])
|
||||
event = record(self.room, GameEvent.SEA_DRAWN, actor=self.user,
|
||||
role="EC", slot_number=3, position="loom",
|
||||
position_label="Loom", card_name="Maid of Brands")
|
||||
self.assertIn("in the Loom.", event.to_prose())
|
||||
role="EC", card_name="The Nomad")
|
||||
# Bawlmorese → poss "yos".
|
||||
self.assertEqual(
|
||||
event.to_prose(),
|
||||
"draws yos Sea of cards, where the Nomad always looms before "
|
||||
"yos calling.")
|
||||
|
||||
def test_sea_relinquished_prose(self):
|
||||
event = record(self.room, GameEvent.SEA_RELINQUISHED, actor=self.user,
|
||||
|
||||
@@ -4330,11 +4330,13 @@ class PickSeaPersistTest(TestCase):
|
||||
crown_card = TarotCard.objects.get(id=crown_id)
|
||||
self.assertIn(crown_card.name_title, ev.data["card_name"])
|
||||
|
||||
def test_affinity_prose_names_the_waite_smith_position_label(self):
|
||||
def test_affinity_prose_is_the_pc_crown_clause(self):
|
||||
# PC → the crown-card crowns clause (user-spec 2026-06-09). Founder has
|
||||
# default pronouns → "their".
|
||||
self._post_hand()
|
||||
prose = self._drawn()[0].to_prose()
|
||||
self.assertIn("finding affinity with", prose)
|
||||
self.assertIn("in the Crown.", prose)
|
||||
self.assertIn("draws their Sea of cards, where the", prose)
|
||||
self.assertIn("crowns all their loftiest illusions.", prose)
|
||||
|
||||
def test_no_affinity_logged_before_hand_complete(self):
|
||||
self._post_hand(self._hand()[:5]) # only 5 of 6 placed
|
||||
|
||||
Reference in New Issue
Block a user