Phase 5: breeding with Cobbreeding, Ditto a rare Deep Dark find

Adrian (2026-10-31): add Cobbreeding without a gate; only Ditto should
be rare, in an endgame area or gated behind something else.

- Cobbreeding 2.4.1 (Modrinth only) and Cloth Config (client) in the
  pack; Cobblebase's hatchery takes Cobbreeding's eggs.
- Ditto moved from Button Isle to the Deep Dark with both of Cobblemon's
  entries (rare in slime chunks, ultra-rare elsewhere): zone_spawns.py
  pins take {"zone", "all_entries"}; Deep Dark target +1, Button Isle -1
  so no other line moved; Southwold and Frostpeak guardians pinned (they
  tie on base stats and flipped between runs).
- HMs' Trainer Ditto is made uncatchable; it still trades.
- check_deps.py skips other-side dependencies; Cobbreeding's harmless
  client-mixin log line allowlisted.
- Self-tests cobbreeding_eggs_fit_the_hatchery, trainer_ditto_cant_be_caught;
  docs, balance table, Phase 5 checklist.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
claudeandClaude Opus 5.5 committed 2026-10-31 19:13:55 +01:00
1 parent 5b304025c6
commit 5cae9252c3
20 files changed
+274 -148

No files matched your search

@@ -0,0 +1,47 @@
package dev.cobblesurvival.selftest
import dev.cobblesurvival.world.SpecialPokemon
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
/**
* Breeding (Adrian, 2026-10-31): Cobbreeding is in, open from the start, and its eggs are what Cobblebase's hatchery
* takes (Cobblebase finds them through its own bridge, by reflection). Ditto is a rare Deep Dark find, so HMs' visiting
* Trainer Ditto can't be caught.
*/
object BreedingTests {
private val EGG = ResourceLocation.parse("cobbreeding:pokemon_egg")
private const val BRIDGE = "notlown.cobblebase.core.CobbreedingBridge"
fun all() = listOf(
SelfTestCase("cobbreeding_eggs_fit_the_hatchery", 20, ::eggsFitTheHatchery),
SelfTestCase("trainer_ditto_cant_be_caught", 40, ::trainerDittoCantBeCaught),
)
private fun eggsFitTheHatchery(s: Scenario) {
val egg = BuiltInRegistries.ITEM.get(EGG)
s.check(egg != Items.AIR) { "Cobbreeding isn't installed: no $EGG" }
val type = Class.forName(BRIDGE)
val bridge = type.getField("INSTANCE").get(null)
s.check(type.getMethod("isLoaded").invoke(bridge) as Boolean) { "Cobblebase doesn't see Cobbreeding" }
s.check(type.getMethod("isPokemonEgg", ItemStack::class.java).invoke(bridge, ItemStack(egg)) as Boolean) {
"Cobblebase's hatchery doesn't take Cobbreeding's eggs"
}
s.helper.succeed()
}
private fun trainerDittoCantBeCaught(s: Scenario) {
s.player()
val pokemon = s.pokemon("ditto", 20)
pokemon.persistentData.putBoolean("dittohms_trainer", true)
val ordinary = s.pokemon("ditto", 20)
s.wild(pokemon, s.at(4.5, 1.0, 4.5))
s.wild(ordinary, s.at(10.5, 1.0, 10.5))
s.helper.startSequence()
.thenWaitUntil { s.check(pokemon.isUncatchable()) { "HMs' Trainer Ditto can be caught" } }
.thenExecute { s.check(!ordinary.isUncatchable() && !SpecialPokemon.isTrainerDitto(ordinary)) { "an ordinary wild Ditto became uncatchable" } }
.thenSucceed()
}
}
@@ -54,7 +54,7 @@ object SelfTest {
private val cases: List<SelfTestCase> get() =
PermadeathTests.all() + SurvivalRulesTests.all() + ZoneTests.all() + HazardTests.all() + GuardianTests.all() +
VeinTests.all() + OreNodeTests.all() + DropTests.all() + ProgressionTests.all() +
CatchingTests.all() + WorldRulesTests.all() + FieldMoveTests.all() + ShoulderTests.all() + BaseLootTests.all()
CatchingTests.all() + WorldRulesTests.all() + FieldMoveTests.all() + ShoulderTests.all() + BaseLootTests.all() + BreedingTests.all()
private var running: List<Scenario> = emptyList()
private var forced: List<ChunkPos> = emptyList()