mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-16 08:52:42 +00:00
Adv 2.0 D&D Phase 11 D3b: Tier 2 trap catalog
Adds Flame Jet (3d6 fire), Collapsing Ceiling (4d6 bludgeoning), and
Glyph of Warding (5d8 lightning) per design doc §6 Tier 2. Detect DCs
match the table (Perception/Investigation/Arcana 14). Save mechanics
("DEX DC X half") are not modeled — full dice resolve on a failed
detect, matching the Tier 1 convention from D2a.
trapsByTier now dispatches Apprentice → tier2TrapCatalog. Added
deterministic-pick + variety + range tests for the new tier.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -278,6 +278,69 @@ func TestPickZoneTrap_DeterministicAndTier1Only(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPickZoneTrap_Tier2UsesTier2Catalog(t *testing.T) {
|
||||||
|
zone, ok := getZone(ZoneForestShadows)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("forest_shadows zone missing")
|
||||||
|
}
|
||||||
|
if zone.Tier != ZoneTierApprentice {
|
||||||
|
t.Fatalf("forest_shadows expected tier 2, got %d", zone.Tier)
|
||||||
|
}
|
||||||
|
seen := map[ZoneTrapKind]bool{}
|
||||||
|
for room := 0; room < 64; room++ {
|
||||||
|
tr, ok := pickZoneTrap(zone, "t2-run", room)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("pickZoneTrap !ok at room %d", room)
|
||||||
|
}
|
||||||
|
if tr.Tier != ZoneTierApprentice {
|
||||||
|
t.Errorf("tier 2 zone picked tier %d trap (%s)", tr.Tier, tr.Kind)
|
||||||
|
}
|
||||||
|
seen[tr.Kind] = true
|
||||||
|
}
|
||||||
|
if len(seen) < 2 {
|
||||||
|
t.Errorf("expected variety across 64 rooms in tier 2, only saw %d kinds: %v", len(seen), seen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRollTrapDamage_GlyphOfWardingIn5d8Range(t *testing.T) {
|
||||||
|
var glyph zoneTrapDef
|
||||||
|
for _, tr := range tier2TrapCatalog {
|
||||||
|
if tr.Kind == TrapGlyphOfWarding {
|
||||||
|
glyph = tr
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if glyph.Kind != TrapGlyphOfWarding {
|
||||||
|
t.Fatal("glyph of warding not in tier 2 catalog")
|
||||||
|
}
|
||||||
|
for room := 0; room < 64; room++ {
|
||||||
|
dmg := rollTrapDamage(glyph, "glyph-run", room)
|
||||||
|
if dmg < 5 || dmg > 40 {
|
||||||
|
t.Errorf("glyph damage out of 5..40 range at room %d: %d", room, dmg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTier2TrapCatalog_AllValid(t *testing.T) {
|
||||||
|
if len(tier2TrapCatalog) < 3 {
|
||||||
|
t.Fatalf("tier 2 catalog should have at least 3 traps, got %d", len(tier2TrapCatalog))
|
||||||
|
}
|
||||||
|
for _, tr := range tier2TrapCatalog {
|
||||||
|
if tr.Tier != ZoneTierApprentice {
|
||||||
|
t.Errorf("trap %s tagged tier %d, expected 2", tr.Kind, tr.Tier)
|
||||||
|
}
|
||||||
|
if tr.Display == "" || tr.Trigger == "" {
|
||||||
|
t.Errorf("trap %s missing display/trigger text", tr.Kind)
|
||||||
|
}
|
||||||
|
if !tr.AlertsEnemies && (tr.DamageDiceN == 0 || tr.DamageDiceD == 0) {
|
||||||
|
t.Errorf("trap %s deals no damage and is not an alarm", tr.Kind)
|
||||||
|
}
|
||||||
|
if tr.DetectDC <= 12 {
|
||||||
|
t.Errorf("trap %s detect DC %d looks tier 1; tier 2 should be 13+", tr.Kind, tr.DetectDC)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPickZoneTrap_VariesAcrossRooms(t *testing.T) {
|
func TestPickZoneTrap_VariesAcrossRooms(t *testing.T) {
|
||||||
zone, _ := getZone(ZoneGoblinWarrens)
|
zone, _ := getZone(ZoneGoblinWarrens)
|
||||||
seen := map[ZoneTrapKind]bool{}
|
seen := map[ZoneTrapKind]bool{}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ const (
|
|||||||
TrapPit ZoneTrapKind = "pit"
|
TrapPit ZoneTrapKind = "pit"
|
||||||
TrapTripwireAlarm ZoneTrapKind = "tripwire_alarm"
|
TrapTripwireAlarm ZoneTrapKind = "tripwire_alarm"
|
||||||
TrapPoisonDart ZoneTrapKind = "poison_dart"
|
TrapPoisonDart ZoneTrapKind = "poison_dart"
|
||||||
|
TrapFlameJet ZoneTrapKind = "flame_jet"
|
||||||
|
TrapCollapsingCeil ZoneTrapKind = "collapsing_ceiling"
|
||||||
|
TrapGlyphOfWarding ZoneTrapKind = "glyph_of_warding"
|
||||||
)
|
)
|
||||||
|
|
||||||
// zoneTrapDef — static definition of a zone trap. Damage dice are in
|
// zoneTrapDef — static definition of a zone trap. Damage dice are in
|
||||||
@@ -81,12 +84,56 @@ var tier1TrapCatalog = []zoneTrapDef{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// trapsByTier returns all traps registered at the given tier. Currently
|
// tier2TrapCatalog — design doc §6 Tier 2 table. Save mechanics ("DEX DC X
|
||||||
// only Tier 1 ships; higher tiers append to this switch in later phases.
|
// half") are not modeled at this layer; full dice resolve on a failed
|
||||||
|
// detect, matching the Tier 1 convention.
|
||||||
|
var tier2TrapCatalog = []zoneTrapDef{
|
||||||
|
{
|
||||||
|
Kind: TrapFlameJet,
|
||||||
|
Display: "Flame Jet",
|
||||||
|
Trigger: "a pressure plate sinks under your boot and gouts of flame roar up",
|
||||||
|
DetectSkill: SkillPerception,
|
||||||
|
DetectDC: 14,
|
||||||
|
DamageDiceN: 3,
|
||||||
|
DamageDiceD: 6,
|
||||||
|
DamageType: "fire",
|
||||||
|
Tier: ZoneTierApprentice,
|
||||||
|
Weight: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: TrapCollapsingCeil,
|
||||||
|
Display: "Collapsing Ceiling",
|
||||||
|
Trigger: "a rigged support snaps and a slab of ceiling crashes down",
|
||||||
|
DetectSkill: SkillInvestigation,
|
||||||
|
DetectDC: 14,
|
||||||
|
DamageDiceN: 4,
|
||||||
|
DamageDiceD: 6,
|
||||||
|
DamageType: "bludgeoning",
|
||||||
|
Tier: ZoneTierApprentice,
|
||||||
|
Weight: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: TrapGlyphOfWarding,
|
||||||
|
Display: "Glyph of Warding",
|
||||||
|
Trigger: "a sigil flares on the doorframe and arcs of lightning lash out",
|
||||||
|
DetectSkill: SkillArcana,
|
||||||
|
DetectDC: 14,
|
||||||
|
DamageDiceN: 5,
|
||||||
|
DamageDiceD: 8,
|
||||||
|
DamageType: "lightning",
|
||||||
|
Tier: ZoneTierApprentice,
|
||||||
|
Weight: 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// trapsByTier returns all traps registered at the given tier. Tier 3+
|
||||||
|
// catalogs append to this switch in later sub-phases (D4a).
|
||||||
func trapsByTier(t ZoneTier) []zoneTrapDef {
|
func trapsByTier(t ZoneTier) []zoneTrapDef {
|
||||||
switch t {
|
switch t {
|
||||||
case ZoneTierBeginner:
|
case ZoneTierBeginner:
|
||||||
return tier1TrapCatalog
|
return tier1TrapCatalog
|
||||||
|
case ZoneTierApprentice:
|
||||||
|
return tier2TrapCatalog
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user